WebAssemblyコードのロードと実行

JavaScript で WebAssembly を使用するには、まずコンパイル/インスタンス化の前にモジュールをメモリにプルする必要があります。この記事では、WebAssembly バイトコードをフェッチするために使用できるさまざまなメカニズムのリファレンスと、それをコンパイル/インスタンス化して実行する方法について説明します。

何が問題なの?

WebAssemblyは <script type='module'> または ES2015 の  import 文とまだ統合されていないため、インポートを使用してブラウザでモジュールをフェッチする組み込みの方法はありません。

以前の WebAssembly.compile/WebAssembly.instantiate メソッドでは、生のバイトをフェッチした後 WebAssembly モジュールのバイナリを含む ArrayBuffer を作成し、コンパイル/インスタンス化する必要があります。これは文字列(JavaScript ソースコード) をバイトの配列バッファ (WebAssembly ソースコード)で置き換えることを除いて、new Function(string) に似ています。

新しい WebAssembly.compileStreaming/WebAssembly.instantiateStreaming  メソッドは、より効率的です。ネットワークからの生のバイトストリームに対して直接アクションを実行し、 ArrayBuffer ステップの必要性がなくなりました。

では、どのようにバイト列を配列バッファに読み込んでコンパイルするのでしょうか? 次の章で説明します。

Fetchを使用する

Fetch はネットワークリソースを取得するための便利でモダンな API です。

wasm モジュールをフェッチする最も簡単で効率的な方法は、新しい WebAssembly.instantiateStreaming() メソッドを使用することです。このメソッドは最初の引数として fetch() を呼び出すことができ、1つのステップでフェッチ、モジュールをインスタンス化し、サーバからストリームされる生のバイトコードにアクセスします。

WebAssembly.instantiateStreaming(fetch('simple.wasm'), importObject)
.then(results => {
  // Do something with the results!
});

直接ストリームでは動作しない古い WebAssembly.instantiate() メソッドを使用した場合、フェッチされたバイトコードを ArrayBuffer に変換する必要があります。次のようにです:

fetch('module.wasm').then(response =>
  response.arrayBuffer()
).then(bytes =>
  WebAssembly.instantiate(bytes, importObject)
).then(results => {
  // コンパイルされた結果(results)で何かする!
});

余談: instantiate() のオーバーロード

WebAssembly.instantiate() 関数は2つのオーバーロードを持ちます — 1つ目 (上の例を参照)はバイトコードを受け取ってプロミスを返します。解決されたプロミスでコンパイルされたモジュールと、それをインスタンス化したものを含むオブジェクトとして受け取ります。オブジェクトの構造は以下のようになります:

{
  module : Module // コンパイルされた WebAssembly.Module オブジェクト,
  instance : Instance // モジュールオブジェクトから生成された WebAssembly.Instance
}

: 通常はインスタンスのみを気にしますが、キャッシュする場合や、postMessage() を使用して別のワーカーやウィンドウと共有する場合や、インスタンスをさらに作成したい場合に備えて、モジュールを用意すると便利です。

: 2番目のオーバーロードフォームは WebAssembly.Module オブジェクトを引数としてとり、結果としてインスタンスオブジェクトを直接含む Promise を返します。2番目のオーバーロードの例を参照してください。

WebAssembly コードを実行する

JavaScript 内で WebAssembly インスタンスが 有効になったら WebAssembly.Instance.exports プロパティを通してエクスポートされた機能を使い始める事ができます。コードは以下のようになるでしょう:

WebAssembly.instantiateStreaming(fetch('myModule.wasm'), importObject)
.then(obj => {
  // Call an exported function:
  obj.instance.exports.exported_func();

  // or access the buffer contents of an exported memory:
  var i32 = new Uint32Array(obj.instance.exports.memory.buffer);

  // or access the elements of an exported table:
  var table = obj.instance.exports.table;
  console.log(table.get(0)());
})

: WebAssembly モジュールからのエクスポートの仕組みの詳細については WebAssembly JavaScript APIを使用する と WebAssemblyテキストフォーマットを理解する を参照してください。

XMLHttpRequest を使用する

XMLHttpRequest は Fetch よりやや古いですが、引き続き型付き配列を取得するために適切に使用することができます。繰り返しますが、モジュール名は simple.wasm とします:

  1. XMLHttpRequest() インスタンスを生成して、open() メソッドでリクエストをオープン、リクエストメソッドを GET に設定し、フェッチするためのパスを宣言します。
  2. キーは responseType を使用してレスポンスタイプを 'arraybuffer' にすることです。
  3. 次に XMLHttpRequest.send() を使用してリクエストします。
  4. そのあと、ダウンロードが終了したときに onload イベントハンドラから関数を実行します — この関数内で response プロパティから array buffer を取得し、Fetchで行ったように WebAssembly.instantiate() メソッドに渡します。

最終的なコードは以下のようになります:

request = new XMLHttpRequest();
request.open('GET', 'simple.wasm');
request.responseType = 'arraybuffer';
request.send();

request.onload = function() {
  var bytes = request.response;
  WebAssembly.instantiate(bytes, importObject).then(results => {
    results.instance.exports.exported_func();
  });
};

: 動作例は xhr-wasm.html を参照してください。