MDN’s new design is in Beta! A sneak peek: https://blog.mozilla.org/opendesign/mdns-new-design-beta/

WebAssemblyコードのロードと実行

JavaScript から WebAssembly を使用するため、コンパイル/インスタンス化の前のモジュールをメモリに読み込む必要があります。モジュールは XMLHttpRequest や Fetch などを使用して型付き配列として取得しますが、将来的には別の方法が提供される予定です。 この記事では WebAssembly バイトコードのフェッチ方法と、コンパイル/インスタンス化して実行する方法について説明します。

何が問題なの?

WebAssembly は <script type='module'> や ES6の import 文と統合されていないため、現在ブラウザでモジュールをフェッチするための組み込みの方法はありません。現在の唯一の方法はWebAssembly モジュールのバイナリをが含まれる ArrayBuffer を作成して、たとえば WebAssembly.instantiate() を使用してコンパイルすることです。これは文字列 (JavaScript ソースコード) をバイト列の配列バッファ (WebAssembly ソースコード) で置き換える点を除き new Function(string) と似ています。

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

Fetchを使用する

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

ネットワーク上に simple.wasm というWebAssemblyモジュールがあるとします:

  • fetch() グローバル関数を使用して、簡単にモジュールをフェッチできます。これは解決時に Response オブジェクトを結果として渡すプロミスを返します。
  • レスポンスを arrayBuffer() 関数を使用して型付き配列に変換できます。これは解決時に型付き配列を結果として渡すプロミスを返します。
  • 最後に、WebAssembly.instantiate() 関数を使用して型付き配列を1アクションでコンパイル、インスタンス化することができます。

必要なコードブロックはこのようになります:

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
}

: 普通はインスタンス (WebAssembly.Instance) のみ気にするだけでよいですが、キャッシュしたり、他のワーカーやウィンドウと postMessage() と共有する、もしくは追加でインスタンスを作成する場合はモジュールが便利です。

: 2つ目のオーバーロードでは WebAssembly.Module オブジェクトを1つ受け取って直接インスタンス (WebAssembly.Instance) を結果として持つプロミスを返します。2つ目のオーバーロード例 を参照してください。

フェッチとインスタンス化をするユーティリティ関数

上記のコードパターンは動きますが、複数のモジュールをロードしたい場合など、毎回書き出すのは面倒です。簡単にするために fetchAndInstantiate() というユーティリティ関数を作成しました。1つのプロミスを返し、バックグラウンドで処理をします。この関数は wasm-utils.js で見つけることができます。そして内容は以下のようになっています:

function fetchAndInstantiate(url, importObject) {
  return fetch(url).then(response =>
    response.arrayBuffer()
  ).then(bytes =>
    WebAssembly.instantiate(bytes, importObject)
  ).then(results =>
    results.instance
  );
}

これを HTMLに 加えたあとは、シンプルな1行のコードでフェッチ、インスタンス化、インスタンスへのアクセスができるようになります:

fetchAndInstantiate('module.wasm', importObject).then(function(instance) {
  ...
})

: このドキュメントの実際の動作例を見ることができます (例えば index.html を参照してください) — これはモジュールをロードする際に推奨するスタンダードなパターンです。

WebAssembly コードを実行する

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

fetchAndInstantiate('myModule.wasm', importObject).then(function(instance) {
  // エクスポートされた関数を呼び出す:
  instance.exports.exported_func();

  // エクスポートされたメモリーのバッファにアクセスする:
  var i32 = new Uint32Array(instance.exports.memory.buffer);

  // エクスポートされたテーブルの要素にアクセスする:
  var table = 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 を参照してください。

ドキュメントのタグと貢献者

 このページの貢献者: syu_kato
 最終更新者: syu_kato,