JavaScript/WebAssembly

WebAssemblyオブジェクト

WebAssemblyとは

WebAssemblyは、最新のウェブブラウザで動作する低レベルなバイナリ形式の命令セットです。C、C++、Rustなどの言語からコンパイルされたコードをブラウザ上で効率的に実行できるよう設計されています。JavaScriptと比較して、処理速度が必要な計算集約型アプリケーションで優れたパフォーマンスを発揮します。

WebAssemblyの基本的な使い方

JavaScriptからWebAssemblyモジュールを利用するには、主に以下のステップを踏みます。

// WebAssemblyバイナリをフェッチする
fetch('simple.wasm')
  .then(response => response.arrayBuffer())
  .then(bytes => WebAssembly.instantiate(bytes))
  .then(result => {
    // WebAssemblyモジュールのインスタンスを取得
    const instance = result.instance;
    
    // エクスポートされた関数を呼び出す
    const sum = instance.exports.add(5, 7);
    console.log('5 + 7 =', sum); // 12が出力される
  });

上記のコードでは、WebAssemblyファイル(.wasm)をフェッチし、それをインスタンス化して、エクスポートされた関数(ここではadd)を呼び出しています。

WebAssemblyオブジェクトの主要メソッド

JavaScriptのグローバルオブジェクトとして利用できるWebAssemblyオブジェクトには、以下のような重要なメソッドがあります。

メソッド 説明 使用例
WebAssembly.instantiate() WebAssemblyモジュールをコンパイルしてインスタンス化する WebAssembly.instantiate(wasmBuffer)
WebAssembly.compile() WebAssemblyモジュールをコンパイルするが、インスタンス化はしない WebAssembly.compile(wasmBuffer)
WebAssembly.instantiateStreaming() ストリーミングソースからWebAssemblyをコンパイルしてインスタンス化する WebAssembly.instantiateStreaming(fetch('module.wasm'))
WebAssembly.compileStreaming() ストリーミングソースからWebAssemblyをコンパイルする WebAssembly.compileStreaming(fetch('module.wasm'))
WebAssembly.validate() バイナリがWebAssemblyモジュールとして有効かどうかを検証する WebAssembly.validate(wasmBuffer)

ストリーミングAPIを使用した効率的な読み込み

WebAssemblyモジュールを効率的に読み込むために、ストリーミングAPIを使用することをお勧めします。

// ストリーミングAPIを使用してWebAssemblyモジュールを読み込む
WebAssembly.instantiateStreaming(fetch('complex.wasm'), importObject)
  .then(obj => {
    // WebAssemblyモジュールのインスタンスを取得
    const instance = obj.instance;
    
    // エクスポートされた関数を呼び出す
    const result = instance.exports.calculateFibonacci(10);
    console.log('Fibonacci(10) =', result);
  })
  .catch(err => {
    console.error('WebAssemblyモジュールの読み込みに失敗しました:', err);
  });

ストリーミングAPIを使用すると、ファイルのダウンロード、コンパイル、インスタンス化を並行して行うことができ、読み込み時間を短縮できます。

WebAssemblyとJavaScriptの連携

WebAssemblyとJavaScriptは相互に連携して動作できます。以下の例では、JavaScriptからWebAssemblyに値を渡し、結果を受け取る方法と、メモリの共有方法を示しています。

// importObjectを使用してJavaScriptの関数をWebAssemblyに渡す
const importObject = {
  env: {
    memoryBase: 0,
    tableBase: 0,
    memory: new WebAssembly.Memory({ initial: 256 }),
    table: new WebAssembly.Table({ initial: 0, element: 'anyfunc' }),
    // WebAssemblyから呼び出せるJavaScript関数
    logNumber: function(num) {
      console.log('WebAssemblyから渡された数値:', num);
    },
    getCurrentTime: function() {
      return Date.now();
    }
  }
};

// WebAssemblyモジュールをインスタンス化
WebAssembly.instantiateStreaming(fetch('interop.wasm'), importObject)
  .then(obj => {
    const instance = obj.instance;
    
    // WebAssemblyのメモリにアクセス
    const memory = instance.exports.memory;
    const buffer = new Uint8Array(memory.buffer);
    
    // WebAssemblyに文字列を渡す
    const message = 'Hello, WebAssembly!';
    const messageBytes = new TextEncoder().encode(message);
    
    // メモリに文字列をコピー
    for (let i = 0; i < messageBytes.length; i++) {
      buffer[i] = messageBytes[i];
    }
    
    // WebAssemblyの関数に文字列のポインタと長さを渡す
    instance.exports.processMessage(0, messageBytes.length);
    
    // 計算関数を呼び出す
    const result = instance.exports.complexCalculation(42);
    console.log('計算結果:', result);
  });

WebAssemblyモジュールの非同期インスタンス化

WebAssembly.instantiateメソッドは非同期で動作し、Promiseを返します。これにより、モジュールの読み込み中にブラウザのメインスレッドをブロックすることなく、他の処理を継続できます。

async function loadAndRunWasm() {
  try {
    // WebAssemblyモジュールを非同期で読み込む
    const response = await fetch('math.wasm');
    const wasmBytes = await response.arrayBuffer();
    
    const importObj = {
      env: {
        memory: new WebAssembly.Memory({ initial: 1 }),
        abort: function() { console.error('Abort called from WASM'); }
      }
    };
    
    // WebAssemblyモジュールをインスタンス化
    const result = await WebAssembly.instantiate(wasmBytes, importObj);
    const instance = result.instance;
    
    // エクスポートされた関数を使用
    console.log('平方根計算: sqrt(25) =', instance.exports.sqrt(25));
    console.log('累乗計算: pow(2, 8) =', instance.exports.pow(2, 8));
    
    return instance;
  } catch (error) {
    console.error('WebAssemblyの読み込みエラー:', error);
  }
}

// 関数の実行
loadAndRunWasm().then(instance => {
  console.log('WebAssemblyモジュールが正常に読み込まれました');
  
  // 追加の計算を実行
  if (instance) {
    const results = [];
    for (let i = 0; i < 10; i++) {
      results.push(instance.exports.factorial(i));
    }
    console.log('階乗計算結果:', results);
  }
});

WebAssemblyモジュールの動的生成

テキスト形式のWebAssembly(WAT)から動的にWebAssemblyモジュールを生成することも可能です。

// WebAssemblyテキスト形式(WAT)
const watSource = `
(module
  (func $add (param $a i32) (param $b i32) (result i32)
    local.get $a
    local.get $b
    i32.add)
  (export "add" (func $add))
)`;

// WATをWASMにコンパイルする関数(WebAssembly Text Format)
async function compileWat(watSource) {
  // WATをWASMにコンパイルするため、wabt.jsなどのライブラリが必要です
  // このサンプルでは外部ライブラリを使用していると仮定します
  
  // 実際の実装では以下のような処理が必要です
  // 1. WATをパースしてWASMバイナリに変換
  // 2. バイナリからWebAssemblyモジュールをインスタンス化
  
  // 簡略化のため、既に実装されているadd関数を持つwasmを使用
  const response = await fetch('simple.wasm');
  const wasmBytes = await response.arrayBuffer();
  const result = await WebAssembly.instantiate(wasmBytes);
  
  return result.instance;
}

// WATからWASMをコンパイルして使用
compileWat(watSource).then(instance => {
  console.log('2 + 3 =', instance.exports.add(2, 3)); // 5が出力される
});

実際の環境では、WebAssemblyテキスト形式(WAT)をコンパイルするために、wabt.jsなどのライブラリを使用することになります。

WebAssemblyの性能最適化

WebAssemblyモジュールを効率的に使用するためのいくつかのベストプラクティスを以下に示します:

// WebAssemblyのインスタンスを再利用する
let wasmInstance = null;

async function getWasmInstance() {
  if (wasmInstance) {
    return wasmInstance;
  }
  
  const response = await fetch('optimized.wasm');
  const wasmBytes = await response.arrayBuffer();
  
  // 共有メモリの作成
  const memory = new WebAssembly.Memory({ initial: 10, maximum: 100 });
  
  const importObj = {
    env: {
      memory: memory,
      // その他の依存関数
    }
  };
  
  const result = await WebAssembly.instantiate(wasmBytes, importObj);
  wasmInstance = result.instance;
  return wasmInstance;
}

// 大量のデータを処理する関数
async function processLargeData(data) {
  const instance = await getWasmInstance();
  const { memory } = instance.exports;
  
  // TypedArrayを使用してメモリにアクセス
  const heap = new Uint8Array(memory.buffer);
  
  // データをWASMメモリに転送
  const dataOffset = instance.exports.allocate(data.length);
  heap.set(data, dataOffset);
  
  // WASM関数を呼び出してデータを処理
  instance.exports.processData(dataOffset, data.length);
  
  // 結果を取得
  const resultOffset = instance.exports.getResultPtr();
  const resultLength = instance.exports.getResultLen();
  
  // 結果をJavaScript環境にコピー
  const result = heap.slice(resultOffset, resultOffset + resultLength);
  
  // メモリを解放
  instance.exports.deallocate(dataOffset, data.length);
  instance.exports.deallocate(resultOffset, resultLength);
  
  return result;
}

// ベンチマーク関数
async function runBenchmark() {
  const testData = new Uint8Array(1000000);
  for (let i = 0; i < testData.length; i++) {
    testData[i] = i % 256;
  }
  
  console.time('WASM処理');
  const result = await processLargeData(testData);
  console.timeEnd('WASM処理');
  
  console.log(`処理結果: ${result.length} バイトのデータが処理されました`);
}

WebAssemblyの最新機能とブラウザサポート

WebAssemblyは継続的に進化しており、新しい機能が追加されています。以下の表は、主要なWebAssembly機能とそのブラウザサポート状況を示しています。

機能 説明 Chrome Firefox Safari Edge
基本機能基本的なWASMサポート
SIMDSingle Instruction Multiple Data部分的
スレッド共有メモリとアトミック操作
例外処理try/catchサポート実験的実験的×実験的
参照型ホスト参照型のサポート部分的
Tail Call末尾再帰最適化実験的××実験的
複数値返却複数の値を返す関数

まとめ

WebAssemblyは、ウェブブラウザ上で高性能な計算処理を実現するための強力な技術です。JavaScriptと連携して使用することで、グラフィック処理、暗号化、ゲーム開発、科学計算など、さまざまな分野でパフォーマンスを向上させることができます。

本章では、JavaScriptからWebAssemblyモジュールを利用するための基本的な方法と、より高度な使用例を紹介しました。これらの知識を活用して、ウェブアプリケーションのパフォーマンスを最大限に引き出しましょう。

附録

静的プロパティ

WebAssembly.JSTag
WebAssembly.name
WebAssembly [ Symbol.toStringTag ]

静的アクセサ

静的メソッド

WebAssembly.CompileError()
WebAssembly.Exception()
WebAssembly.Global()
WebAssembly.Instance()
WebAssembly.LinkError()
WebAssembly.Memory()
WebAssembly.Module()
WebAssembly.RuntimeError()
WebAssembly.Table()
WebAssembly.Tag()
WebAssembly.compile()
WebAssembly.compileStreaming()
WebAssembly.instantiate()
WebAssembly.instantiateStreaming()
WebAssembly.validate()
カテゴリ:JavaScript
カテゴリ:JavaScript