--- title: WebAssembly JavaScript API を使用する slug: WebAssembly/Using_the_JavaScript_API translation_of: WebAssembly/Using_the_JavaScript_API ---
これまでに Emscriptenのようなツールを使用して他の言語からモジュールをコンパイルしたり、あなた自身のコードをロードして実行しました。次のステップは他のWebAssembly JavaScript APIの使い方について学ぶことです。この記事ではあなたが知る必要があることを説明します。
注: もし、この記事で説明している基本的なコンセプトがよくわからない場合、WebAssemblyのコンセプト をはじめに読んでからこの記事に戻ってきてください。
WebAssembly JavaScript API の使用方法と、wasm モジュールをロードしてウェブページ内で使用する方法をステップ・バイ・ステップの例を通して実行してみましょう。
注: サンプルコードは webassembly-examples GitHub レポジトリから参照してください。
index.html
という名前でシンプルな HTML ファイルを作成しましょう (もしも簡単に利用できるテンプレートを持っていない場合、simple template を使用できます) 。(module (func $i (import "imports" "imported_func") (param i32)) (func (export "exported_func") i32.const 42 call $i))
$i
は imports.imported_func
からインポートされています。wasm モジュールにインポートするオブジェクトを記述するときに、この2階層の名前空間を JavaScript に反映させる必要があります。<script></script>
要素を HTML 内に作成して、次のコードを追加します:
var importObject = { imports: { imported_func: function(arg) { console.log(arg); } } };
上で説明したように、 imports.imported_func
でインポート機能を利用できます。
注: ES6のアローファンクション を使用するとより簡潔に書くことができます:
var importObject = { imports: { imported_func: arg => console.log(arg) } };
スタイルはあなたが好きなものを選んでください。
インポートオブジェクトを用意して、wasm ファイルをフェッチして、ArrayBuffer に変換して、その後にエクスポートされた関数を使用します。
スクリプトに次のコードを追加します。以下は最初のブロックです:
fetch('simple.wasm').then(response => response.arrayBuffer() ).then(bytes => WebAssembly.instantiate(bytes, importObject) ).then(results => { results.instance.exports.exported_func(); });
注: WebAssemblyコードのロードと実行 でどのように機能するか詳しく説明しました。自信が無い場合、思い出すために参照してください。
この結果、エクスポートされた WebAssembly 関数 exported_func
を呼び出すとインポートされた JavaScript 関数 imported_func
が呼び出され、WebAssembly インスタンス内の提供される値 (42) がコンソールに表示されます。コード例を保存して WebAssembly をサポートするブラウザで読み込むと 実際に動作が確認できるでしょう!
注: WebAssembly は Firefox 52+ と Chrome 57+/最新のOpera でデフォルトで有効になっています (Firefox 47+ で about:config で javascript.options.wasm
フラグを有効化するか、Chrome (51+) と Opera (38+) で chrome://flags の Experimental WebAssembly フラグを有効化することによって動作させることができます)
これは複雑で長い例のほんの一部ですが、ウェブアプリケーション内でどのように JavaScript と WebAssembly を並行して動作させることができるかを説明しています。別の場所でも言及していますが、WebAssembly は JavaScript の置き換えを目指しているわけではありません。両方が協力して、お互いの強みを活かすことができます。
Firefox 54+ では、デベロッパーツールのデバッガパネルでウェブページに含まれる wasm コードのテキスト表現を表示する機能があります。これを表示するためには、デバッガパネルに移動して、“xxx > wasm” エントリをクリックしてください。
Firefox で、WebAssembly をテキストとして表示することに加えて、開発者は WebAssembly のテキスト表現を使用してすぐにデバッグを開始することができます (ブレークポイント、コールスタックの検査、ステップ実行など) 。WebAssembly debugging with Firefox DevTools の動画を参照してください。
Firefox 58からソース (wasm) から WebAssembly モジュールをコンパイル、インスタンス化する機能が新しく追加されました。これは、 {{jsxref("WebAssembly.compileStreaming()")}} 、 {{jsxref("WebAssembly.instantiateStreaming()")}} メソッドを使用して実現されます。これらのメソッドは、バイトコードを直接 Module
/Instance
インスタンスに変換することができます。 {{domxref("Response")}} を一旦 {{domxref("ArrayBuffer")}} にするステップを省略できるので、非ストリーミングなメソッドよりもシンプルになるでしょう。
次の例 (Github上のデモ compile-streaming.html と、動作例 を参照してください) では、ソースから直接 .wasm モジュールをストリームして、 {{jsxref("WebAssembly.Module")}} オブジェクトにコンパイルしています。compileStreaming()
関数は {{domxref("Response")}} オブジェクトを渡すプロミスを受け取るので、直接 {{domxref("WindowOrWorkerGlobalScope.fetch()")}} の呼び出し結果を渡すことができます。
var importObject = { imports: { imported_func: arg => console.log(arg) } }; WebAssembly.compileStreaming(fetch('simple.wasm')) .then(module => WebAssembly.instantiate(module, importObject)) .then(instance => instance.exports.exported_func());
結果として受け取ったモジュールインスタンスはその後 {{jsxref("WebAssembly.instantiate()")}} を使用してインスタンス化され、エクスポートされた関数が実行されます。
次の例 (Github上のデモ instantiate-streaming.html と、 動作例 を参照してください) は結果的には同じになりますが、 instantiateStreaming()
を使用してインスタンス化のステップを別々にする必要をなくしています。
var importObject = { imports: { imported_func: arg => console.log(arg) } }; WebAssembly.instantiateStreaming(fetch('simple.wasm'), importObject) .then(obj => obj.instance.exports.exported_func());
WebAssembly の低レベルのメモリモデルでは、メモリはモジュール内で ロード、ストア命令 を使用して読み書きされ る線形メモリ と呼ばれる型のない連続したバイト列として表現されます。このメモリモデルでは、任意のロード、ストア命令は線形メモリ全体の任意のバイトにアクセスすることができます。これはポインタなどの C/C++ の概念を忠実に表現するために必要なものです。
ただし、ネイティブの C/C++ プログラムでは使用可能なメモリ範囲がプロセス全体に及ぶ一方、個別の WebAssembly Instance がアクセス可能なメモリは、特定の WebAssembly Memory オブジェクトの (潜在的にとても小さい) 範囲だけになります。これにより単一のウェブアプリケーションで複数の独立した (WebAssembly を内部的に使用している) ライブラリが完全に分離された別々のメモリを持つことができます。
JavaScript では、Memory インスタンスはリサイズ可能な ArrayBuffer とみなすことができます。ArrayBuffer と同様に、単一のウェブアプリケーションで多くの独立した Memory オブジェクトを作成することができます。Memory オブジェクトは初期サイズと最大サイズ (オプショナル) を指定して、{{jsxref("WebAssembly.Memory()")}} コンストラクタから作成することができます。
簡単な例を見て探索を始めましょう。
memory.html
という名前の新しいシンプルな HTMLページ を作成します (simple template をコピーしてください) 。<script></script>
をページに追加します。
Memory インスタンスを作成するために次の1行をスクリプトに追加します:
var memory = new WebAssembly.Memory({initial:10, maximum:100});
initial
と maximum
は WebAssembly ページを1単位 (64KBに固定されています) とします。上の例では、Memory インスタンスは初期サイズが640KB、最大サイズが6.4MBを意味しています。
WebAssembly Memory が持つバイト列は ArrayBuffer として buffer ゲッター/セッターから公開されています。例えば、線形メモリの先頭ワードに直接、42を書きこむにはこのようにします:
new Uint32Array(memory.buffer)[0] = 42;
その後に同じ値を返すことができます:
new Uint32Array(memory.buffer)[0]
デモで試してみましょう。これまでに追加した内容を保存してブラウザで読み込んだ後、JavaScript コンソールで上の2行を入力してみてください。
Memory インスタンスは {{jsxref("Memory.prototype.grow()")}} を呼び出すことで拡張することができます。引数は WebAssembly ページ単位で指定します:
memory.grow(1);
Memory インスタンスの作成時に最大値が指定していて、この最大値を超えて拡張しようとすると {{jsxref("WebAssembly.RangeError")}} 例外がスローされます。エンジンは提供された上限を利用してメモリを事前に確保しておくことで、より効率的なリサイズが可能になります。
注: {{domxref("ArrayBuffer")}} の byteLength はイミュータブルであるため、 {{jsxref("Memory.prototype.grow()")}} 操作が成功した後、buffer ゲッターは新しい (新しい byteLength で) ArrayBufferを返します。そして、前の ArrayBuffer は「切り離された状態」になるか、メモリから切り離されます。
関数と同様に、線形メモリはモジュール内で定義することもインポートすることもできます。同じようにモジュールは任意でメモリをエクスポートすることも可能です。これは JavaScript が WebAssembly インスタンスに対して新しく作成した WebAssembly.Memory
をインポートで渡したり、Memory のエクスポートから (Instance.prototype.exports
を介して
) 受け取れることを意味しています。
より複雑な例 (整数の配列を合計するWebAssemblyモジュール) で、上で言っていることを明確にしましょう。例は memory.wasm を参照してください。
memory.wasm
のコピーを以前と同じディレクトリにコピーします。
注: モジュールのテキスト表現は memory.wat を参照してください。
memory.html
サンプルファイルに戻って、以前と同じように wasm モジュールをフェッチ、コンパイル、インスタンス化します (以下をスクリプトに追加してください):
fetch('memory.wasm').then(response => response.arrayBuffer() ).then(bytes => WebAssembly.instantiate(bytes) ).then(results => { // add your code here });
このモジュールはモジュール内部のメモリをエクスポートします。instance という名前でモジュールのInstanceが取得され、エクスポートされた関数 accumulate()
を使用してモジュールの線形メモリ (mem
) に直接入力された配列を合計する事ができます。指定された場所に、次のコードを追加してみましょう:
var i32 = new Uint32Array(results.instance.exports.mem.buffer); for (var i = 0; i < 10; i++) { i32[i] = i; } var sum = results.instance.exports.accumulate(0, 10); console.log(sum);
Memoryオブジェクト自体でなく、Memory オブジェクトの buffer (Memory.prototype.buffer
) から {{domxref("Uint32Array")}} ビューを作成していることに注意してください。
メモリのインポートは関数のインポートと同じように機能します。JavaScript 関数の代わりに Memory オブジェクトを渡すだけです。メモリのインポートは2つの理由で役に立ちます:
注: 完全なデモは memory.html (動作例) を参照してください。このバージョンでは fetchAndInstantiate()
関数を使用しています。
WebAssembly Table は JavaScript と WebAssembly コードの両方でアクセスできるリサイズ可能な 参照 の型付き配列です。Memory はリサイズ可能な生のバイト列を提供しますが、参照はエンジンに保証された値(このバイト列は安全性、移植性、安定性の理由からコンテンツによって直接読み書きしてはいけない)であるため、参照を格納するために使用することは安全ではありません。
テーブルは要素の型を持ち、テーブルに格納できる参照の型が制限されます。WebAssembly の現バージョンでは WebAssembly コード内で必要な参照の型は関数型の1つだけです。そして、これが唯一の正しい要素の型となります。将来のバージョンでは、さらに多くの要素の型が追加される予定です。
関数参照は関数ポインタを持つ C/C++ のような言語をコンパイルするために必要です。C/C++ のネイティブ実装では、関数ポインタはプロセスの仮想アドレス空間内の関数のコードの生のアドレスで表現されるため、安全性の理由から線形メモリに直接格納することはできません。代わりに、関数参照はテーブルに格納されます。整数値のインデックスは線形メモリに格納することができます。
関数ポインタを呼び出すときは、WebAssembly を呼び出す側でインデックスを指定します。インデックスを付けたり、インデックス付けされた関数参照を呼び出す前に安全な境界のチェックをすることができます。したがって、テーブルは現在、安全かつ移植可能に低レベルのプログラミング言語の機能をコンパイルするために使用される、低レベルのプリミティブです。
テーブルは Table.prototype.set()
を通してテーブル内の値を1つ更新することができます。さらに、
Table.prototype.grow()
でテーブルに格納できる値の数を増やすことができます。時間の経過とともに間接呼び出しされる関数を変更することを許容し、これは 動的リンク技術 のために必要なものです。変化した値に対してJavaScriptでは Table.prototype.get()
を通してすぐにアクセスできます。wasm モジュールからも同様です。
テーブルのシンプルな例を見てみましょう。紹介する WebAssembly モジュールは2つの要素 (要素0は13、要素1は42を返します) を持つテーブルをエクスポートするものです。モジュールは table.wasm から見つけられます。
table.wasm
をローカルの新しいディレクトリにコピーします。
注: このモジュールのテキスト表現は table.wat を参照してください。
HTML template を table.html
という名前で同じディレクトリにコピーします。
前と同じように、wasm モジュールをフェッチ、コンパイル、インスタンス化します。次のコードを HTML の body の末尾の {{htmlelement("script")}} 要素に追加します:
fetch('table.wasm').then(response => response.arrayBuffer() ).then(bytes => WebAssembly.instantiate(bytes) ).then(results => { // add your code here });
今度はテーブル内のデータにアクセスしてみましょう。コードの指定された場所に次の行を追加します:
var tbl = results.instance.exports.tbl; console.log(tbl.get(0)()); // 13 console.log(tbl.get(1)()); // 42
このコードはテーブルに格納されている各関数参照に順番にアクセスし、内包した値をコンソールに書き出すためにインスタンス化します。Table.prototype.get()
で各関数参照を取得した後、関数を実行するためには括弧を追加することに注意してください。
注: 完全なデモは table.html (動作例) を参照してください。このバージョンでは fetchAndInstantiate()
関数を使用しています。
ここでは、主な WebAssembly の構成要素のデモを見てきました。これは重複度の概念に言及するのに適しています。これはアーキテクチャ効率の点で多くの進歩がもたらされます:
Understanding text format article の記事で重複度の働きについてみることができます。その中の Mutating tables and dynamic linking の章を見てください (TBD)。
この記事では WebAssembly JavaScript API の基本的な使い方について説明しました。WebAssembly モジュールを JavaScript のコンテキストに組み込む方法、その関数を使えるようすること、JavaScript でのメモリとテーブルの使い方について。さらに、多重度の概念についても触れました。