From a088a420ae7f4c3d18b4535911288d818b9f4ef7 Mon Sep 17 00:00:00 2001 From: Masahiro FUJIMOTO Date: Sun, 9 Jan 2022 00:25:21 +0900 Subject: WebAssembly の一部の記事を変換準備 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../understanding_the_text_format/index.html | 562 --------------------- .../understanding_the_text_format/index.md | 562 +++++++++++++++++++++ .../using_the_javascript_api/index.html | 283 ----------- .../webassembly/using_the_javascript_api/index.md | 283 +++++++++++ 4 files changed, 845 insertions(+), 845 deletions(-) delete mode 100644 files/ja/webassembly/understanding_the_text_format/index.html create mode 100644 files/ja/webassembly/understanding_the_text_format/index.md delete mode 100644 files/ja/webassembly/using_the_javascript_api/index.html create mode 100644 files/ja/webassembly/using_the_javascript_api/index.md (limited to 'files/ja/webassembly') diff --git a/files/ja/webassembly/understanding_the_text_format/index.html b/files/ja/webassembly/understanding_the_text_format/index.html deleted file mode 100644 index ea6a24cf7b..0000000000 --- a/files/ja/webassembly/understanding_the_text_format/index.html +++ /dev/null @@ -1,562 +0,0 @@ ---- -title: WebAssembly テキストフォーマットを理解する -slug: WebAssembly/Understanding_the_text_format -tags: - - Functions - - JavaScript - - S-expressions - - WebAssembly - - calls - - memory - - shared address - - table - - text format - - was - - wasm -translation_of: WebAssembly/Understanding_the_text_format ---- -
{{WebAssemblySidebar}}
- -

人間が WebAssembly を読んだり編集するための wasm バイナリ形式のテキスト表現が存在します。これはテキストエディター、ブラウザーの開発者ツールなどで見せるために設計された中間表現です。この記事では、テキスト形式のしくみ、生の構文、および元のバイトコードの表現との関係 (と JavaScript で wasm を表現したラッパーオブジェクト) について説明します。

- -
-

: この記事は、あなたがウェブ開発者で wasm モジュールをページにロードしてコード内で使用するだけなら過剰なものかもしれません (WebAssembly JavaScript API を使用する を参照)。しかし、例えば、パフォーマンスを最適化するために wasm モジュールを書きたいときや、あなた自身で WebAssembly コンパイラを作るときに役に立ちます。

-
- -

S 式

- -

バイナリ、テキスト形式の両方で、 WebAssembly の基本的なコードの単位はモジュールです。テキスト形式ではモジュールは1つの大きなS式として表現されます。S式はツリー構造を表現するための非常に古くてシンプルなテキスト形式で、モジュールはモジュールの構造とそのコードを記述するノードツリーとして考えることができます。しかし、プログラミング言語の AST (抽象構文木) とは異なり、WebAssembly のツリーはかなり平坦で、ほとんどは命令の列で構成されています。

- -

はじめに、 S 式がどういうものか見てみましょう。ツリー内の各ノードは1組の括弧内に入れられます — ( ... )。 括弧内の最初のラベルは、それがどのノードタイプかを示し、スペースで区切られた属性、または子ノードのリストが続きます。次のコードは WebAssembly の S 式を意味します:

- -
(module (memory 1) (func))
- -

ルートノード "module" と2つの子ノード、"1" を属性に持つ "memory" ノード、"func" ノードを表します。これらのノードが実際にどういう意味なのかを見ていきましょう。

- -

最もシンプルなモジュール

- -

最もシンプルで短い実行可能な wasm モジュールから始めてみましょう。

- -
(module)
- -

このモジュールは完全に空ですが、モジュールとしては有効です。

- -

いま、このモジュールをバイナリに変換すると (WebAssembly テキストフォーマットから wasm に変換する を参照) 、 バイナリ形式 で記述された8バイトのモジュールヘッダーだけになります:

- -
0000000: 0061 736d              ; WASM_BINARY_MAGIC
-0000004: 0100 0000              ; WASM_BINARY_VERSION
- -

モジュールに機能を追加する

- -

Ok、これは全然面白くないですね。モジュールに実行可能なコードを追加していきましょう。

- -

全ての WebAssembly モジュール内のコードは次の擬似コード構造を持つ関数にグループ化されます:

- -
( func <signature> <locals> <body> )
- - - -

S式であるために違って見えますが、これは、他の言語の関数に似ています。

- -

シグネチャと引数

- -

シグネチャは返値の型宣言のリストが後に続く、引数の型宣言のシーケンスです。ここで注目すべきは:

- - - -

各引数は明示的に宣言された型を持ちます。wasm では現在4つの型が有効です:

- - - -

単体の引数は (param i32) 、返値は (result i32) のように書きます。したがって、2つの32ビット整数を引数にとり、64ビット浮動小数点数を返すバイナリ関数は次のように記述します:

- -
(func (param i32) (param i32) (result f64) ... )
- -

シグネチャのあとに型付けされたローカル変数のリストが続きます (例: (local i32)) 。 引数は基本的には呼び出し元から渡された、対応する引数の値で初期化された、ただのローカル変数です。

- -

ローカル変数と引数を取得/設定する

- -

ローカル変数と引数は関数本体から local.getlocal.set 命令を使用して読み書きすることができます。

- -

local.get/local.get コマンドは数値のインデックスから取得/設定される項目を参照します。最初に引数が宣言順に、その後に、ローカル変数が宣言順に参照されます。次の関数を見てください:

- -
(func (param i32) (param f32) (local f64)
-  local.get 0
-  local.get 1
-  local.get 2)
- -

命令 local.get 0 は i32 の引数, local.get 1 は f32 の引数、そして local.get 2 は f64 のローカル変数を取得します。

- -

ここで別の問題があります。数値のインデックスを使用して項目を参照すると、混乱したり、困ってしまうことがあります。そこで、テキストフォーマットでは、単純に型宣言の直前に ($) をプレフィックスとして付けた名前を、引数、ローカル変数や他の多くの項目につけることができます。

- -

したがって、上記のシグネチャを次のように書き直すことができます:

- -
(func (param $p1 i32) (param $p2 f32) (local $loc f64) …)
- -

そして、local.get 0 の代わりに local.get $p1 と書くことができるようになります (このテキストがバイナリに変換されたとき、バイナリには整数値だけが残されることに注意してください) 。

- -

スタックマシン

- -

関数本体を書く前に、もう1つ、スタックマシンについて話をする必要があります。ブラウザはそれを更に効率的な形にコンパイルしますが、wasm の実行はスタックマシンとして定義されます。スタックマシンの基本的なアイデアは全ての命令がスタックから特定の数の i32/i64/f32/f64 値をプッシュ、ポップするようにすることです。

- -

例えば、 local.get はローカル変数の値をスタックにプッシュするように定義されます。そして、i32.add は2つの i32 値 (スタックにプッシュされた前の2つの値を暗黙的に取得します) をポップし、合計を計算して (2^32 の剰余として) 結果の i32 値をプッシュします。

- -

関数が呼び出されたとき、空のスタックから開始され、徐々に積まれてゆき、本体の命令が実行されると空になります。例として、次の関数の実行後について見てみましょう:

- -
(func (param $p i32)
-  (result i32)
-  local.get $p
-  local.get $p
-  i32.add)
- -

スタックには i32.add よって処理された式 ($p + $p) の結果として、ただ1つの i32 値が積まれています。関数の返値はスタックに残った最後の値になります。

- -

WebAssembly のバリデーションルールはスタックが正確に一致することを保証します。もし、(result f32) と宣言した場合、最終的にスタックに1つだけ f32 値が積まれている状態である必要があります。結果の型がない場合は、スタックは空でなければなりません。

- -

はじめての関数本体

- -

前述の通り、関数本体は関数が呼び出された後に続く単純な命令列です。 これまでに学んだことと一緒にして、最終的にはシンプルな関数を含むモジュールを定義することができるようになります:

- -
(module
-  (func (param $lhs i32) (param $rhs i32) (result i32)
-    local.get $lhs
-    local.get $rhs
-    i32.add))
- -

この関数は2つの引数を受け取って、それらを足して、その結果を返します。

- -

関数本体に置けるものはもっとたくさんありますが、いまはシンプルなもので始めます。進むにつれてもっと多くの例を見ていきます。全ての有効なオペコードのリストについては webassembly.org Semantics reference を調べてみてください。

- -

関数を呼び出す

- -

私達が定義した関数は自身では大したことはしません。いまはそれを呼び出す必要があります。どのようにすればよいでしょうか? ES2015 モジュールのように、wasm 関数はモジュール内の export ステートメントによって明示的にエクスポートしなくてはいけません。

- -

ローカル変数と同じように、関数もデフォルトではインデックスで識別されますが、便宜上の関数名を付けることができます。func キーワードの直後にドル記号で始まる名前を付けてみましょう。

- -
(func $add … )
- -

ここでエクスポート宣言を追加する必要があります。次のようになります:

- -
(export "add" (func $add))
- -

ここで add は JavaScript で認識される関数名であるのに対して、$add はモジュール内の、どの WebAssembly 関数をエクスポートするのかを選択します。

- -

最終的なモジュール (いまのところ) は次のようになります:

- -
(module
-  (func $add (param $lhs i32) (param $rhs i32) (result i32)
-    local.get $lhs
-    local.get $rhs
-    i32.add)
-  (export "add" (func $add))
-)
- -

例に従うなら、上のモジュールを add.wat という名前で保存して、wabt を使用して (詳細は WebAssembly テキストフォーマットから wasm に変換する を参照してください) 、add.wasm というファイルに変換します。

- -

次に、 addCode という名前の型付き配列にバイナリをロードし (WebAssembly コードのロードと実行 で説明されています) 、コンパイル、インスタンス化して、JavaScript で add 関数を実行します (add() はインスタンスの exports プロパティから見つけることができます):

- -
WebAssembly.instantiateStreaming(fetch('add.wasm'))
-  .then(obj => {
-    console.log(obj.instance.exports.add(1, 2));  // "3"
-  });
- -
-

: この例は GitHub のadd.html (動作例) にあります。関数のインスタンス化についての詳細は {{JSxRef("WebAssembly.instantiateStreaming()")}} も合わせて参照してください。

-
- -

基礎を探る

- -

ここでは実際の基本的な例を取り上げてから、いくつかの高度な機能について見てみましょう。

- -

同じモジュールの他の関数から関数を呼び出す

- -

call 命令はインデックスか名前を指定して単一の関数を呼び出します。例えば、次のモジュールには2つの関数が含まれています。1つ目はただ42を返すだけ、もう1つは1つ目のものに1を足した値を返します:

- -
(module
-  (func $getAnswer (result i32)
-    i32.const 42)
-  (func (export "getAnswerPlus1") (result i32)
-    call $getAnswer
-    i32.const 1
-    i32.add))
- -
-

: i32.const は32ビット整数を定義してスタックにプッシュするだけです。 i32 以外の有効な型に変えて、const の値を好きなものに変えることができます (ここでは 42 に設定しました)。

-
- -

この例で、あなたは func の直後に宣言された (export "getAnswerPlus1") セクションに気づくでしょう。これはこの関数をエクスポートするための宣言をして、さらにそれに名前をつけるために使用するショートカットです。

- -

これは、上で行ったように、モジュール内の関数外の別の場所で、関数ステートメントと分けて定義するのと同等の機能です。

- -
(export "getAnswerPlus1" (func $functionName))
- -

上のモジュールを呼び出す JavaScript コードは次のようになります:

- -
WebAssembly.instantiateStreaming(fetch('call.wasm'))
- .then(obj => {
-    console.log(obj.instance.exports.getAnswerPlus1());  // "43"
-  });
- -
-

: この例は GitHub の call.html (動作例) から参照してください。また、 fetchAndInstantiate() のソースは wasm-utils.js を参照してください。

-
- -

JavaScript から関数をインポートする

- -

すでに、JavaScript から WebAssembly 関数を呼び出すことについては確認しましたが、WebAssembly から JavaScript 関数を呼び出すことについてはどうでしょうか? WebAssembly は実際に JavaScript のビルトインの情報を持っていませんが、JavaScript か wasm 関数をインポートするための一般的な方法があります。例を見てみましょう:

- -
(module
-  (import "console" "log" (func $log (param i32)))
-  (func (export "logIt")
-    i32.const 13
-    call $log))
- -

WebAssembly は2階層の名前空間のインポートステートメントを持ちます。ここでは、console モジュールから log 関数をインポートすることを要求しています。また、エクスポートされた logIt 関数から、上で紹介した call 命令を使用して、インポートされた関数を呼ぶ出すことができます。

- -

インポートされた関数は通常の関数と同じようなものです。WebAssembly のバリデーションによって静的にチェックするシグネチャを持ち、インデックスか名前を付けて呼び出すことができます。

- -

JavaScript 関数にはシグネチャの概念がないため、インポート宣言のシグネチャに関係なく、どの JavaScript 関数も渡すことができます。モジュールがインポート宣言をすると、 {{jsxref("WebAssembly.instantiate()")}} を呼び出す側は、対応したプロパティを持ったインポートオブジェクトを渡す必要があります。

- -

上の場合、 importObject.console.log が JavaScript 関数であるようなオブジェクト(importObject と呼びましょう) が必要になります。

- -

これは次のようになります。

- -
var importObject = {
-  console: {
-    log: function(arg) {
-      console.log(arg);
-    }
-  }
-};
-
-WebAssembly.instantiateStreaming(fetch('logger.wasm'), importObject)
-  .then(obj => {
-    obj.instance.exports.logIt();
-  });
- -
-

: この例は GitHub の logger.html (動作例)を参照してください。

-
- -

WebAssembly でのグローバルの宣言

- -

WebAssembly には、 JavaScript からアクセス可能なグローバル変数インスタンスを作成する機能と、1つ以上の {{JSxRef("WebAssembly.Module")}} インスタンスにまたがってインポート/エクスポート可能なグローバル変数インスタンスを作成する機能があります。これは、複数のモジュールを動的にリンクすることができるので、非常に便利です。

- -

WebAssembly のテキスト形式では、次のようになります (GitHub のリポジトリにある global.wat を参照してください。JavaScript の例は global.html も参照してください)。

- -
(module
-   (global $g (import "js" "global") (mut i32))
-   (func (export "getGlobal") (result i32)
-        (global.get $g))
-   (func (export "incGlobal")
-        (global.set $g
-            (i32.add (global.get $g) (i32.const 1))))
-)
- -

これは、キーワード global を使用してグローバルな値を指定していることと、値のデータ型と一緒にキーワード mut を指定して変更可能にしたい場合に指定していることを除いて、以前に見たものと似ています。

- -

JavaScript を使用して同等の値を作成するには、 {{JSxRef("WebAssembly.Global()")}} コンストラクターを使用してください。

- -
const global = new WebAssembly.Global({value: "i32", mutable: true}, 0);
- -

WebAssembly メモリ

- -

上の例はとてもひどいロギング関数です。たった1つの整数値を表示するだけです! 文字列を表示するためにはどうしたらよいでしょうか? 文字列やさらに複雑なデータ型を扱うために WebAssembly は メモリ を提供します。WebAssembly によると、メモリは徐々に拡張することのできるただの大きなバイト列です。WebAssembly には 線形メモリ から読み書きするための i32.loadi32.store のような命令を持っています。

- -

JavaScript から見ると、メモリは全て1つの大きな (リサイズ可能な) {{domxref("ArrayBuffer")}} の内部にあるように見えます。それはまさに、asm.js とともに動かさなければならないもの全てです (ただしリサイズは出来ません。asm.js の プログラミングモデル を参照してください) 。

- -

したがって、文字列は線形メモリ内部のどこかに存在するただのバイト列です。適切なバイト列の文字列をメモリに書き込んだとしましょう。その文字列をどのように JavaScript に渡すのでしょうか?

- -

鍵は {{jsxref("WebAssembly.Memory()")}} インターフェースを使用して JavaScript から WebAssembly の線形メモリを作成し、関連するインスタンスメソッドを使用して既存の Memory インスタンス (現在は1モジュールごとに1つだけ持つことができます) にアクセスできることです。Memory インスタンスは buffer ゲッターを持ち、これは線形メモリ全体を指し示す ArrayBuffer を返します。

- -

Memory インスタンスは、例えば JavaScript から Memory.grow() メソッドを使用して拡張することもできます。拡張したとき、ArrayBuffer はサイズを変更することができないため、現在の ArrayBuffer は切り離され、新しく作成された、より大きな ArrayBuffer を指し示すようになります。これは、JavaScript に文字列を渡すために必要なことは、線形メモリ内での文字列のオフセットと長さを指定する方法を渡すことだけであることを意味します。

- -

文字列自身に文字列の長さの情報をエンコードするさまざまな方法 (例えば、C言語の文字列) がありますが、簡単にするためにここではオフセットと長さの両方を引数として渡します:

- -
(import "console" "log" (func $log (param i32) (param i32)))
- -

JavaScript 側では、バイト列を簡単に JavaScript 文字列にデコードするために TextDecoder API を使用することができます (ここでは utf8 を指定していますが、他の多くのエンコーディングをサポートしています) 。

- -
function consoleLogString(offset, length) {
-  var bytes = new Uint8Array(memory.buffer, offset, length);
-  var string = new TextDecoder('utf8').decode(bytes);
-  console.log(string);
-}
- -

最後のに欠けているのは、 consoleLogString が WebAssembly の memory にアクセスできる場所です。このあたり WebAssembly は柔軟です。JavaScript から Memory オブジェクトを作成して WebAssembly モジュールでメモリをインポートするか、WebAssembly モジュールでメモリを作成して JavaScript で使用するためにエクスポートすることができます。

- -

簡単にするために、JavaScript で作成したメモリを WebAssembly にインポートしてみましょう。import ステートメントは次のようになります。

- -
(import "js" "mem" (memory 1))
- -

1 はインポートされたメモリに少なくとも1ページ分のメモリが必要であることを示します(WebAssembly では1ページを 64KB と定義しています)。

- -

文字列 "Hi" を出力する完全なモジュールを見てみましょう。通常のコンパイルされたCのプログラムでは文字列にメモリを割り当てる関数を呼び出しますが、ここでは独自のアセンブリを書くだけで、全ての線形メモリを所有しているので、data セクションを使用してグローバルメモリに文字列の内容を書きこむことができます。データセクションではインスタンス化時にオフセットを指定してバイト列の文字列を書きこむことができます。これはネイティブの実行可能形式の .data セクションに似ています。

- -

最終的な wasm モジュールは次のようになります。

- -
(module
-  (import "console" "log" (func $log (param i32 i32)))
-  (import "js" "mem" (memory 1))
-  (data (i32.const 0) "Hi")
-  (func (export "writeHi")
-    i32.const 0  ;; pass offset 0 to log
-    i32.const 2  ;; pass length 2 to log
-    call $log))
- -
-

: 上記の2重のセミコロン構文 (;;) は WebAssembly ファイル内でコメントを書くためのものです。

-
- -

ここで、JavaScript から 1ページ分のサイズを持つ Memory を作成してそれに渡すことができます。結果としてコンソールに "Hi" と出力されます:

- -
var memory = new WebAssembly.Memory({initial:1});
-
-var importObject = { console: { log: consoleLogString }, js: { mem: memory } };
-
-WebAssembly.instantiateStreaming(fetch('logger2.wasm'), importObject)
-  .then(obj => {
-    obj.instance.exports.writeHi();
-  });
- -
-

: 完全なソースは GitHub の logger2.html (動作例) を参照してください。

-
- -

WebAssembly テーブル

- -

WebAssembly テキストフォーマットのツアーを終了するために、WebAssemblyで最も複雑でしばしば混乱する部分 (テーブル) を見てみましょう。テーブルは基本的に WebAssembly コードからインデックスでアクセスできるリサイズ可能な参照の配列です。

- -

なぜテーブルが必要なのかを見るために、最初に観察する必要があります。さきほど見た call 命令 ({{anch("同じモジュールの他の関数から関数を呼び出す")}} を参照) は静的な関数インデックスをとり、結果として1つの関数しか呼び出せません。しかし、呼び出し先がランタイム値の場合はどうなるでしょうか?

- - - -

WebAssembly にはこれを実現するための一種の呼び出し命令が必要だったため、動的な関数をオペランドに受け取る call_indirect を与えました。問題は WebAssembly ではオペランドに指定できる型が (現在) i32/i64/f32/f64 だけなことです。

- -

WebAssembly は anyfunc 型 (任意のシグニチャの関数を保持できるため "any") を追加することができましたが、あいにくセキュリティ上の理由から anyfunc 型は線形メモリに格納できませんでした。線形メモリは格納された値の生の内容をバイト列として公開し、これによって wasm コンテンツが生の関数ポインタを自由に観察できて破損させることができてしまいます。これはウェブ上では許可できません。

- -

解決方法は関数参照をテーブルに格納し、代わりにテーブルのインデックスを渡すことでした。これは単なる i32 値です。call_indirect のオペランドは単純に i32 のインデックス値にすることができます。

- -

wasm でテーブルを定義する

- -

どのようにしてテーブルに wasm 関数を配置するのでしょうか? data セクションを使用して線形メモリの領域をバイト列で初期化するのと同じように、elem セクションを使用してテーブルの領域を関数の列で初期化することが出来ます:

- -
(module
-  (table 2 funcref)
-  (elem (i32.const 0) $f1 $f2)
-  (func $f1 (result i32)
-    i32.const 42)
-  (func $f2 (result i32)
-    i32.const 13)
-  ...
-)
- - - -
-

: 初期化されていない要素はデフォルトの throw-on-call 値が与えられます。

-
- -

JavaScript で同じようなテーブルのインスタンスを作成する場合、次のようになります:

- -
function() {
-  // table section
-  var tbl = new WebAssembly.Table({initial:2, element:"funcref"});
-
-  // function sections:
-  var f1 = ... /* some imported WebAssembly function */
-  var f2 = ... /* some imported WebAssembly function */
-
-  // elem section
-  tbl.set(0, f1);
-  tbl.set(1, f2);
-};
- -

テーブルを使用する

- -

先に進みましょう。いま、何らかの形で使用するために必要なテーブルを定義しました。このコードのセクションで使ってみましょう:

- -
(type $return_i32 (func (result i32))) ;; if this was f32, type checking would fail
-(func (export "callByIndex") (param $i i32) (result i32)
-  local.get $i
-  call_indirect (type $return_i32))
- - - -

call_indirect の引数はコマンド呼び出しの前に置く代わりに、次のように明示的に宣言することもできます:

- -
(call_indirect (type $return_i32) (local.get $i))
- -

より高級な、JavaScript のような表現力の高い言語では、関数を含む配列 (あるいはオブジェクトかもしれません) で同じことができることが想像できますよね。擬似コードだとこれは tbl[i]() のようになります。

- -

型チェックの話に戻ります。WebAssembly は型チェックされていて、anyfunc は「任意の関数シグネチャ」を意味するので、呼び出し先の (推定される) シグネチャを指定する必要があります。そのため、プログラムに関数が i32 を返すはずだ、と知らせるために $return_i32 型を指定しています。もし呼び出し先のシグネチャがマッチしない (代わりに f32 が返されるような) 場合は {{jsxref("WebAssembly.RuntimeError")}} 例外がスローされます。

- -

さて、呼び出しを行うときにどのようにテーブルに call_indirect をリンクさせているのでしょうか? 答えは、現在モジュールインスタンスごとに1つのテーブルしか許容されないため、call_indirect はそれを暗黙的に呼び出します。将来的に複数のテーブルを持てるようになったとき、以下の行のように、何らかのテーブル識別子を指定する必要があるでしょう。

- -
call_indirect $my_spicy_table (type $i32_to_void)
- -

完全なモジュールは次のようになります。例は wasm-table.wat を参照してください:

- -
(module
-  (table 2 funcref)
-  (func $f1 (result i32)
-    i32.const 42)
-  (func $f2 (result i32)
-    i32.const 13)
-  (elem (i32.const 0) $f1 $f2)
-  (type $return_i32 (func (result i32)))
-  (func (export "callByIndex") (param $i i32) (result i32)
-    local.get $i
-    call_indirect (type $return_i32))
-)
- -

次の JavaScript を使用してウェブページに読み込んでみましょう:

- -
WebAssembly.instantiateStreaming(fetch('wasm-table.wasm'))
-  .then(obj => {
-    console.log(obj.instance.exports.callByIndex(0)); // returns 42
-    console.log(obj.instance.exports.callByIndex(1)); // returns 13
-    console.log(obj.instance.exports.callByIndex(2)); // returns an error, because there is no index position 2 in the table
-  });
- -
-

注: 例は GitHub の wasm-table.html (動作例) を参照してください。

-
- -
-

注: Memory と同じように Table も JavaScript から作成すること (WebAssembly.Table() を参照) 、別の wasm モジュール間でインポートすることができます。

-
- -

テーブルの変更と動的リンク

- -

JavaScript は関数参照にフルアクセスできるため、Table オブジェクトは JavaScript から grow(), get()set() メソッドを使用して変更することができます。WebAssembly が 参照型 を得たとき、WebAssembly コードは get_elem/set_elem 命令を使用してテーブル自身を変更することができるようになるでしょう。

- -

テーブルは変更可能であるため、それらは複雑なロード時、実行時の 動的リンクスキーム の実装で使用することができます。プログラムが動的にリンクされたとき、複数のインスタンスで同じメモリとテーブルを共有することができます。これは複数のコンパイル済み .dll が単一のプロセスのアドレス空間を共有するネイティブアプリケーションと対称的です。

- -

この動作を確認するために、Memory オブジェクトと Table オブジェクトを含む単一のインポートオブジェクトを作成し、同じインポートオブジェクトを複数の instantiate() の呼び出しで渡してみましょう。

- -

.wat ファイルの例は次のようになります。

- -

shared0.wat:

- -
(module
-  (import "js" "memory" (memory 1))
-  (import "js" "table" (table 1 funcref))
-  (elem (i32.const 0) $shared0func)
-  (func $shared0func (result i32)
-   i32.const 0
-   i32.load)
-)
- -

shared1.wat:

- -
(module
-  (import "js" "memory" (memory 1))
-  (import "js" "table" (table 1 funcref))
-  (type $void_to_i32 (func (result i32)))
-  (func (export "doIt") (result i32)
-   i32.const 0
-   i32.const 42
-   i32.store  ;; store 42 at address 0
-   i32.const 0
-   call_indirect (type $void_to_i32))
-)
- -

These work as follows:

- -
    -
  1. 関数 shared0funcshared0.wat で定義され、インポートされたテーブルに格納されます。
  2. -
  3. この関数は定数値 0 を作成して、次に i32.load コマンドを使用して指定したメモリのインデックスから値をロードします。そのインデックスは 0 になります 。先と同様に、前の値をスタックから暗黙的にポップします。つまり、shared0func はメモリのインデックス 0 の位置に格納された値をロードして返します。
  4. -
  5. shared1.wat では、 doIt という関数をエクスポートします。この関数は2つの定数値 042 を作成して i32.store を呼び出して、インポートされたメモリの指定したインデックスに指定した値を格納します。ここでも、これらの値はスタックから暗黙的にポップされます。したがって、結果的にメモリのインデックスが 0 の位置に、値として 42 が格納されます。
  6. -
  7. 関数の最後では、定数値 0 を作成し、テーブルのインデックスが 0 の位置にある関数を呼び出します。これは shared0func で、先に shared0.watelem ブロックで格納されたものです。
  8. -
  9. 呼び出されたとき、shared0funcshared1.wat 内で i32.store コマンドを使用してメモリに格納された 42 をロードします。
  10. -
- -
-

: 上の式はスタックから値を暗黙的にポップしますが、代わりにコマンド呼び出しの中で明示的に宣言することができます:

- -
(i32.store (i32.const 0) (i32.const 42))
-(call_indirect (type $void_to_i32) (i32.const 0))
- - -
- -

アセンブリに変換した後、次のコードで JavaScript 内で shared0.wasmshared1.wasm を使用します:

- -
var importObj = {
-  js: {
-    memory : new WebAssembly.Memory({ initial: 1 }),
-    table : new WebAssembly.Table({ initial: 1, element: "funcref" })
-  }
-};
-
-Promise.all([
-  WebAssembly.instantiateStreaming(fetch('shared0.wasm'), importObj),
-  WebAssembly.instantiateStreaming(fetch('shared1.wasm'), importObj)
-]).then(function(results) {
-  console.log(results[1].instance.exports.doIt());  // prints 42
-});
- -

コンパイルされた各モジュールは同じメモリとテーブルオブジェクトをインポートし、その結果同じ線形メモリとテーブルの「アドレス空間」を共有することができます。

- -
-

: 例は GitHub の shared-address-space.html (動作例) を参照してください。

-
- -

WebAssembly の複数値

- -

もっと最近になって (例えば Firefox 78) 言語に追加されたものが WebAssembly 複数値であり、これは、WebAssembly 関数が複数の値を返すことができるようになり、一連の命令が複数のスタック値を消費して生成することができるようになったことを意味します。

- -

執筆時点 (2020年6月) において、これは初期段階であり、利用可能な多値命令は、それ自体が複数の値を返す関数の呼び出しのみです。例を示します。

- -
(module
-  (func $get_two_numbers (result i32 i32)
-    i32.const 1
-    i32.const 2
-  )
-  (func (export "add_to_numbers") (result i32)
-    call $get_two_numbers
-    i32.add
-  )
-)
- -

しかし、これはより有用な命令タイプやその他のものへの道を開くことになるでしょう。これまでの進捗状況や、これがどのように動作するかについては、 Nick Fitzgerald の Multi-Value All The Wasm! を参照してください。

- -

まとめ

- -

これで、WebAssembly テキストフォーマットの主要コンポーネントとそれらが WebAssembly JS API にどのように反映されるのかの高レベルなツアーが完了しました。

- -

関連情報

- - diff --git a/files/ja/webassembly/understanding_the_text_format/index.md b/files/ja/webassembly/understanding_the_text_format/index.md new file mode 100644 index 0000000000..ea6a24cf7b --- /dev/null +++ b/files/ja/webassembly/understanding_the_text_format/index.md @@ -0,0 +1,562 @@ +--- +title: WebAssembly テキストフォーマットを理解する +slug: WebAssembly/Understanding_the_text_format +tags: + - Functions + - JavaScript + - S-expressions + - WebAssembly + - calls + - memory + - shared address + - table + - text format + - was + - wasm +translation_of: WebAssembly/Understanding_the_text_format +--- +
{{WebAssemblySidebar}}
+ +

人間が WebAssembly を読んだり編集するための wasm バイナリ形式のテキスト表現が存在します。これはテキストエディター、ブラウザーの開発者ツールなどで見せるために設計された中間表現です。この記事では、テキスト形式のしくみ、生の構文、および元のバイトコードの表現との関係 (と JavaScript で wasm を表現したラッパーオブジェクト) について説明します。

+ +
+

: この記事は、あなたがウェブ開発者で wasm モジュールをページにロードしてコード内で使用するだけなら過剰なものかもしれません (WebAssembly JavaScript API を使用する を参照)。しかし、例えば、パフォーマンスを最適化するために wasm モジュールを書きたいときや、あなた自身で WebAssembly コンパイラを作るときに役に立ちます。

+
+ +

S 式

+ +

バイナリ、テキスト形式の両方で、 WebAssembly の基本的なコードの単位はモジュールです。テキスト形式ではモジュールは1つの大きなS式として表現されます。S式はツリー構造を表現するための非常に古くてシンプルなテキスト形式で、モジュールはモジュールの構造とそのコードを記述するノードツリーとして考えることができます。しかし、プログラミング言語の AST (抽象構文木) とは異なり、WebAssembly のツリーはかなり平坦で、ほとんどは命令の列で構成されています。

+ +

はじめに、 S 式がどういうものか見てみましょう。ツリー内の各ノードは1組の括弧内に入れられます — ( ... )。 括弧内の最初のラベルは、それがどのノードタイプかを示し、スペースで区切られた属性、または子ノードのリストが続きます。次のコードは WebAssembly の S 式を意味します:

+ +
(module (memory 1) (func))
+ +

ルートノード "module" と2つの子ノード、"1" を属性に持つ "memory" ノード、"func" ノードを表します。これらのノードが実際にどういう意味なのかを見ていきましょう。

+ +

最もシンプルなモジュール

+ +

最もシンプルで短い実行可能な wasm モジュールから始めてみましょう。

+ +
(module)
+ +

このモジュールは完全に空ですが、モジュールとしては有効です。

+ +

いま、このモジュールをバイナリに変換すると (WebAssembly テキストフォーマットから wasm に変換する を参照) 、 バイナリ形式 で記述された8バイトのモジュールヘッダーだけになります:

+ +
0000000: 0061 736d              ; WASM_BINARY_MAGIC
+0000004: 0100 0000              ; WASM_BINARY_VERSION
+ +

モジュールに機能を追加する

+ +

Ok、これは全然面白くないですね。モジュールに実行可能なコードを追加していきましょう。

+ +

全ての WebAssembly モジュール内のコードは次の擬似コード構造を持つ関数にグループ化されます:

+ +
( func <signature> <locals> <body> )
+ + + +

S式であるために違って見えますが、これは、他の言語の関数に似ています。

+ +

シグネチャと引数

+ +

シグネチャは返値の型宣言のリストが後に続く、引数の型宣言のシーケンスです。ここで注目すべきは:

+ + + +

各引数は明示的に宣言された型を持ちます。wasm では現在4つの型が有効です:

+ + + +

単体の引数は (param i32) 、返値は (result i32) のように書きます。したがって、2つの32ビット整数を引数にとり、64ビット浮動小数点数を返すバイナリ関数は次のように記述します:

+ +
(func (param i32) (param i32) (result f64) ... )
+ +

シグネチャのあとに型付けされたローカル変数のリストが続きます (例: (local i32)) 。 引数は基本的には呼び出し元から渡された、対応する引数の値で初期化された、ただのローカル変数です。

+ +

ローカル変数と引数を取得/設定する

+ +

ローカル変数と引数は関数本体から local.getlocal.set 命令を使用して読み書きすることができます。

+ +

local.get/local.get コマンドは数値のインデックスから取得/設定される項目を参照します。最初に引数が宣言順に、その後に、ローカル変数が宣言順に参照されます。次の関数を見てください:

+ +
(func (param i32) (param f32) (local f64)
+  local.get 0
+  local.get 1
+  local.get 2)
+ +

命令 local.get 0 は i32 の引数, local.get 1 は f32 の引数、そして local.get 2 は f64 のローカル変数を取得します。

+ +

ここで別の問題があります。数値のインデックスを使用して項目を参照すると、混乱したり、困ってしまうことがあります。そこで、テキストフォーマットでは、単純に型宣言の直前に ($) をプレフィックスとして付けた名前を、引数、ローカル変数や他の多くの項目につけることができます。

+ +

したがって、上記のシグネチャを次のように書き直すことができます:

+ +
(func (param $p1 i32) (param $p2 f32) (local $loc f64) …)
+ +

そして、local.get 0 の代わりに local.get $p1 と書くことができるようになります (このテキストがバイナリに変換されたとき、バイナリには整数値だけが残されることに注意してください) 。

+ +

スタックマシン

+ +

関数本体を書く前に、もう1つ、スタックマシンについて話をする必要があります。ブラウザはそれを更に効率的な形にコンパイルしますが、wasm の実行はスタックマシンとして定義されます。スタックマシンの基本的なアイデアは全ての命令がスタックから特定の数の i32/i64/f32/f64 値をプッシュ、ポップするようにすることです。

+ +

例えば、 local.get はローカル変数の値をスタックにプッシュするように定義されます。そして、i32.add は2つの i32 値 (スタックにプッシュされた前の2つの値を暗黙的に取得します) をポップし、合計を計算して (2^32 の剰余として) 結果の i32 値をプッシュします。

+ +

関数が呼び出されたとき、空のスタックから開始され、徐々に積まれてゆき、本体の命令が実行されると空になります。例として、次の関数の実行後について見てみましょう:

+ +
(func (param $p i32)
+  (result i32)
+  local.get $p
+  local.get $p
+  i32.add)
+ +

スタックには i32.add よって処理された式 ($p + $p) の結果として、ただ1つの i32 値が積まれています。関数の返値はスタックに残った最後の値になります。

+ +

WebAssembly のバリデーションルールはスタックが正確に一致することを保証します。もし、(result f32) と宣言した場合、最終的にスタックに1つだけ f32 値が積まれている状態である必要があります。結果の型がない場合は、スタックは空でなければなりません。

+ +

はじめての関数本体

+ +

前述の通り、関数本体は関数が呼び出された後に続く単純な命令列です。 これまでに学んだことと一緒にして、最終的にはシンプルな関数を含むモジュールを定義することができるようになります:

+ +
(module
+  (func (param $lhs i32) (param $rhs i32) (result i32)
+    local.get $lhs
+    local.get $rhs
+    i32.add))
+ +

この関数は2つの引数を受け取って、それらを足して、その結果を返します。

+ +

関数本体に置けるものはもっとたくさんありますが、いまはシンプルなもので始めます。進むにつれてもっと多くの例を見ていきます。全ての有効なオペコードのリストについては webassembly.org Semantics reference を調べてみてください。

+ +

関数を呼び出す

+ +

私達が定義した関数は自身では大したことはしません。いまはそれを呼び出す必要があります。どのようにすればよいでしょうか? ES2015 モジュールのように、wasm 関数はモジュール内の export ステートメントによって明示的にエクスポートしなくてはいけません。

+ +

ローカル変数と同じように、関数もデフォルトではインデックスで識別されますが、便宜上の関数名を付けることができます。func キーワードの直後にドル記号で始まる名前を付けてみましょう。

+ +
(func $add … )
+ +

ここでエクスポート宣言を追加する必要があります。次のようになります:

+ +
(export "add" (func $add))
+ +

ここで add は JavaScript で認識される関数名であるのに対して、$add はモジュール内の、どの WebAssembly 関数をエクスポートするのかを選択します。

+ +

最終的なモジュール (いまのところ) は次のようになります:

+ +
(module
+  (func $add (param $lhs i32) (param $rhs i32) (result i32)
+    local.get $lhs
+    local.get $rhs
+    i32.add)
+  (export "add" (func $add))
+)
+ +

例に従うなら、上のモジュールを add.wat という名前で保存して、wabt を使用して (詳細は WebAssembly テキストフォーマットから wasm に変換する を参照してください) 、add.wasm というファイルに変換します。

+ +

次に、 addCode という名前の型付き配列にバイナリをロードし (WebAssembly コードのロードと実行 で説明されています) 、コンパイル、インスタンス化して、JavaScript で add 関数を実行します (add() はインスタンスの exports プロパティから見つけることができます):

+ +
WebAssembly.instantiateStreaming(fetch('add.wasm'))
+  .then(obj => {
+    console.log(obj.instance.exports.add(1, 2));  // "3"
+  });
+ +
+

: この例は GitHub のadd.html (動作例) にあります。関数のインスタンス化についての詳細は {{JSxRef("WebAssembly.instantiateStreaming()")}} も合わせて参照してください。

+
+ +

基礎を探る

+ +

ここでは実際の基本的な例を取り上げてから、いくつかの高度な機能について見てみましょう。

+ +

同じモジュールの他の関数から関数を呼び出す

+ +

call 命令はインデックスか名前を指定して単一の関数を呼び出します。例えば、次のモジュールには2つの関数が含まれています。1つ目はただ42を返すだけ、もう1つは1つ目のものに1を足した値を返します:

+ +
(module
+  (func $getAnswer (result i32)
+    i32.const 42)
+  (func (export "getAnswerPlus1") (result i32)
+    call $getAnswer
+    i32.const 1
+    i32.add))
+ +
+

: i32.const は32ビット整数を定義してスタックにプッシュするだけです。 i32 以外の有効な型に変えて、const の値を好きなものに変えることができます (ここでは 42 に設定しました)。

+
+ +

この例で、あなたは func の直後に宣言された (export "getAnswerPlus1") セクションに気づくでしょう。これはこの関数をエクスポートするための宣言をして、さらにそれに名前をつけるために使用するショートカットです。

+ +

これは、上で行ったように、モジュール内の関数外の別の場所で、関数ステートメントと分けて定義するのと同等の機能です。

+ +
(export "getAnswerPlus1" (func $functionName))
+ +

上のモジュールを呼び出す JavaScript コードは次のようになります:

+ +
WebAssembly.instantiateStreaming(fetch('call.wasm'))
+ .then(obj => {
+    console.log(obj.instance.exports.getAnswerPlus1());  // "43"
+  });
+ +
+

: この例は GitHub の call.html (動作例) から参照してください。また、 fetchAndInstantiate() のソースは wasm-utils.js を参照してください。

+
+ +

JavaScript から関数をインポートする

+ +

すでに、JavaScript から WebAssembly 関数を呼び出すことについては確認しましたが、WebAssembly から JavaScript 関数を呼び出すことについてはどうでしょうか? WebAssembly は実際に JavaScript のビルトインの情報を持っていませんが、JavaScript か wasm 関数をインポートするための一般的な方法があります。例を見てみましょう:

+ +
(module
+  (import "console" "log" (func $log (param i32)))
+  (func (export "logIt")
+    i32.const 13
+    call $log))
+ +

WebAssembly は2階層の名前空間のインポートステートメントを持ちます。ここでは、console モジュールから log 関数をインポートすることを要求しています。また、エクスポートされた logIt 関数から、上で紹介した call 命令を使用して、インポートされた関数を呼ぶ出すことができます。

+ +

インポートされた関数は通常の関数と同じようなものです。WebAssembly のバリデーションによって静的にチェックするシグネチャを持ち、インデックスか名前を付けて呼び出すことができます。

+ +

JavaScript 関数にはシグネチャの概念がないため、インポート宣言のシグネチャに関係なく、どの JavaScript 関数も渡すことができます。モジュールがインポート宣言をすると、 {{jsxref("WebAssembly.instantiate()")}} を呼び出す側は、対応したプロパティを持ったインポートオブジェクトを渡す必要があります。

+ +

上の場合、 importObject.console.log が JavaScript 関数であるようなオブジェクト(importObject と呼びましょう) が必要になります。

+ +

これは次のようになります。

+ +
var importObject = {
+  console: {
+    log: function(arg) {
+      console.log(arg);
+    }
+  }
+};
+
+WebAssembly.instantiateStreaming(fetch('logger.wasm'), importObject)
+  .then(obj => {
+    obj.instance.exports.logIt();
+  });
+ +
+

: この例は GitHub の logger.html (動作例)を参照してください。

+
+ +

WebAssembly でのグローバルの宣言

+ +

WebAssembly には、 JavaScript からアクセス可能なグローバル変数インスタンスを作成する機能と、1つ以上の {{JSxRef("WebAssembly.Module")}} インスタンスにまたがってインポート/エクスポート可能なグローバル変数インスタンスを作成する機能があります。これは、複数のモジュールを動的にリンクすることができるので、非常に便利です。

+ +

WebAssembly のテキスト形式では、次のようになります (GitHub のリポジトリにある global.wat を参照してください。JavaScript の例は global.html も参照してください)。

+ +
(module
+   (global $g (import "js" "global") (mut i32))
+   (func (export "getGlobal") (result i32)
+        (global.get $g))
+   (func (export "incGlobal")
+        (global.set $g
+            (i32.add (global.get $g) (i32.const 1))))
+)
+ +

これは、キーワード global を使用してグローバルな値を指定していることと、値のデータ型と一緒にキーワード mut を指定して変更可能にしたい場合に指定していることを除いて、以前に見たものと似ています。

+ +

JavaScript を使用して同等の値を作成するには、 {{JSxRef("WebAssembly.Global()")}} コンストラクターを使用してください。

+ +
const global = new WebAssembly.Global({value: "i32", mutable: true}, 0);
+ +

WebAssembly メモリ

+ +

上の例はとてもひどいロギング関数です。たった1つの整数値を表示するだけです! 文字列を表示するためにはどうしたらよいでしょうか? 文字列やさらに複雑なデータ型を扱うために WebAssembly は メモリ を提供します。WebAssembly によると、メモリは徐々に拡張することのできるただの大きなバイト列です。WebAssembly には 線形メモリ から読み書きするための i32.loadi32.store のような命令を持っています。

+ +

JavaScript から見ると、メモリは全て1つの大きな (リサイズ可能な) {{domxref("ArrayBuffer")}} の内部にあるように見えます。それはまさに、asm.js とともに動かさなければならないもの全てです (ただしリサイズは出来ません。asm.js の プログラミングモデル を参照してください) 。

+ +

したがって、文字列は線形メモリ内部のどこかに存在するただのバイト列です。適切なバイト列の文字列をメモリに書き込んだとしましょう。その文字列をどのように JavaScript に渡すのでしょうか?

+ +

鍵は {{jsxref("WebAssembly.Memory()")}} インターフェースを使用して JavaScript から WebAssembly の線形メモリを作成し、関連するインスタンスメソッドを使用して既存の Memory インスタンス (現在は1モジュールごとに1つだけ持つことができます) にアクセスできることです。Memory インスタンスは buffer ゲッターを持ち、これは線形メモリ全体を指し示す ArrayBuffer を返します。

+ +

Memory インスタンスは、例えば JavaScript から Memory.grow() メソッドを使用して拡張することもできます。拡張したとき、ArrayBuffer はサイズを変更することができないため、現在の ArrayBuffer は切り離され、新しく作成された、より大きな ArrayBuffer を指し示すようになります。これは、JavaScript に文字列を渡すために必要なことは、線形メモリ内での文字列のオフセットと長さを指定する方法を渡すことだけであることを意味します。

+ +

文字列自身に文字列の長さの情報をエンコードするさまざまな方法 (例えば、C言語の文字列) がありますが、簡単にするためにここではオフセットと長さの両方を引数として渡します:

+ +
(import "console" "log" (func $log (param i32) (param i32)))
+ +

JavaScript 側では、バイト列を簡単に JavaScript 文字列にデコードするために TextDecoder API を使用することができます (ここでは utf8 を指定していますが、他の多くのエンコーディングをサポートしています) 。

+ +
function consoleLogString(offset, length) {
+  var bytes = new Uint8Array(memory.buffer, offset, length);
+  var string = new TextDecoder('utf8').decode(bytes);
+  console.log(string);
+}
+ +

最後のに欠けているのは、 consoleLogString が WebAssembly の memory にアクセスできる場所です。このあたり WebAssembly は柔軟です。JavaScript から Memory オブジェクトを作成して WebAssembly モジュールでメモリをインポートするか、WebAssembly モジュールでメモリを作成して JavaScript で使用するためにエクスポートすることができます。

+ +

簡単にするために、JavaScript で作成したメモリを WebAssembly にインポートしてみましょう。import ステートメントは次のようになります。

+ +
(import "js" "mem" (memory 1))
+ +

1 はインポートされたメモリに少なくとも1ページ分のメモリが必要であることを示します(WebAssembly では1ページを 64KB と定義しています)。

+ +

文字列 "Hi" を出力する完全なモジュールを見てみましょう。通常のコンパイルされたCのプログラムでは文字列にメモリを割り当てる関数を呼び出しますが、ここでは独自のアセンブリを書くだけで、全ての線形メモリを所有しているので、data セクションを使用してグローバルメモリに文字列の内容を書きこむことができます。データセクションではインスタンス化時にオフセットを指定してバイト列の文字列を書きこむことができます。これはネイティブの実行可能形式の .data セクションに似ています。

+ +

最終的な wasm モジュールは次のようになります。

+ +
(module
+  (import "console" "log" (func $log (param i32 i32)))
+  (import "js" "mem" (memory 1))
+  (data (i32.const 0) "Hi")
+  (func (export "writeHi")
+    i32.const 0  ;; pass offset 0 to log
+    i32.const 2  ;; pass length 2 to log
+    call $log))
+ +
+

: 上記の2重のセミコロン構文 (;;) は WebAssembly ファイル内でコメントを書くためのものです。

+
+ +

ここで、JavaScript から 1ページ分のサイズを持つ Memory を作成してそれに渡すことができます。結果としてコンソールに "Hi" と出力されます:

+ +
var memory = new WebAssembly.Memory({initial:1});
+
+var importObject = { console: { log: consoleLogString }, js: { mem: memory } };
+
+WebAssembly.instantiateStreaming(fetch('logger2.wasm'), importObject)
+  .then(obj => {
+    obj.instance.exports.writeHi();
+  });
+ +
+

: 完全なソースは GitHub の logger2.html (動作例) を参照してください。

+
+ +

WebAssembly テーブル

+ +

WebAssembly テキストフォーマットのツアーを終了するために、WebAssemblyで最も複雑でしばしば混乱する部分 (テーブル) を見てみましょう。テーブルは基本的に WebAssembly コードからインデックスでアクセスできるリサイズ可能な参照の配列です。

+ +

なぜテーブルが必要なのかを見るために、最初に観察する必要があります。さきほど見た call 命令 ({{anch("同じモジュールの他の関数から関数を呼び出す")}} を参照) は静的な関数インデックスをとり、結果として1つの関数しか呼び出せません。しかし、呼び出し先がランタイム値の場合はどうなるでしょうか?

+ + + +

WebAssembly にはこれを実現するための一種の呼び出し命令が必要だったため、動的な関数をオペランドに受け取る call_indirect を与えました。問題は WebAssembly ではオペランドに指定できる型が (現在) i32/i64/f32/f64 だけなことです。

+ +

WebAssembly は anyfunc 型 (任意のシグニチャの関数を保持できるため "any") を追加することができましたが、あいにくセキュリティ上の理由から anyfunc 型は線形メモリに格納できませんでした。線形メモリは格納された値の生の内容をバイト列として公開し、これによって wasm コンテンツが生の関数ポインタを自由に観察できて破損させることができてしまいます。これはウェブ上では許可できません。

+ +

解決方法は関数参照をテーブルに格納し、代わりにテーブルのインデックスを渡すことでした。これは単なる i32 値です。call_indirect のオペランドは単純に i32 のインデックス値にすることができます。

+ +

wasm でテーブルを定義する

+ +

どのようにしてテーブルに wasm 関数を配置するのでしょうか? data セクションを使用して線形メモリの領域をバイト列で初期化するのと同じように、elem セクションを使用してテーブルの領域を関数の列で初期化することが出来ます:

+ +
(module
+  (table 2 funcref)
+  (elem (i32.const 0) $f1 $f2)
+  (func $f1 (result i32)
+    i32.const 42)
+  (func $f2 (result i32)
+    i32.const 13)
+  ...
+)
+ + + +
+

: 初期化されていない要素はデフォルトの throw-on-call 値が与えられます。

+
+ +

JavaScript で同じようなテーブルのインスタンスを作成する場合、次のようになります:

+ +
function() {
+  // table section
+  var tbl = new WebAssembly.Table({initial:2, element:"funcref"});
+
+  // function sections:
+  var f1 = ... /* some imported WebAssembly function */
+  var f2 = ... /* some imported WebAssembly function */
+
+  // elem section
+  tbl.set(0, f1);
+  tbl.set(1, f2);
+};
+ +

テーブルを使用する

+ +

先に進みましょう。いま、何らかの形で使用するために必要なテーブルを定義しました。このコードのセクションで使ってみましょう:

+ +
(type $return_i32 (func (result i32))) ;; if this was f32, type checking would fail
+(func (export "callByIndex") (param $i i32) (result i32)
+  local.get $i
+  call_indirect (type $return_i32))
+ + + +

call_indirect の引数はコマンド呼び出しの前に置く代わりに、次のように明示的に宣言することもできます:

+ +
(call_indirect (type $return_i32) (local.get $i))
+ +

より高級な、JavaScript のような表現力の高い言語では、関数を含む配列 (あるいはオブジェクトかもしれません) で同じことができることが想像できますよね。擬似コードだとこれは tbl[i]() のようになります。

+ +

型チェックの話に戻ります。WebAssembly は型チェックされていて、anyfunc は「任意の関数シグネチャ」を意味するので、呼び出し先の (推定される) シグネチャを指定する必要があります。そのため、プログラムに関数が i32 を返すはずだ、と知らせるために $return_i32 型を指定しています。もし呼び出し先のシグネチャがマッチしない (代わりに f32 が返されるような) 場合は {{jsxref("WebAssembly.RuntimeError")}} 例外がスローされます。

+ +

さて、呼び出しを行うときにどのようにテーブルに call_indirect をリンクさせているのでしょうか? 答えは、現在モジュールインスタンスごとに1つのテーブルしか許容されないため、call_indirect はそれを暗黙的に呼び出します。将来的に複数のテーブルを持てるようになったとき、以下の行のように、何らかのテーブル識別子を指定する必要があるでしょう。

+ +
call_indirect $my_spicy_table (type $i32_to_void)
+ +

完全なモジュールは次のようになります。例は wasm-table.wat を参照してください:

+ +
(module
+  (table 2 funcref)
+  (func $f1 (result i32)
+    i32.const 42)
+  (func $f2 (result i32)
+    i32.const 13)
+  (elem (i32.const 0) $f1 $f2)
+  (type $return_i32 (func (result i32)))
+  (func (export "callByIndex") (param $i i32) (result i32)
+    local.get $i
+    call_indirect (type $return_i32))
+)
+ +

次の JavaScript を使用してウェブページに読み込んでみましょう:

+ +
WebAssembly.instantiateStreaming(fetch('wasm-table.wasm'))
+  .then(obj => {
+    console.log(obj.instance.exports.callByIndex(0)); // returns 42
+    console.log(obj.instance.exports.callByIndex(1)); // returns 13
+    console.log(obj.instance.exports.callByIndex(2)); // returns an error, because there is no index position 2 in the table
+  });
+ +
+

注: 例は GitHub の wasm-table.html (動作例) を参照してください。

+
+ +
+

注: Memory と同じように Table も JavaScript から作成すること (WebAssembly.Table() を参照) 、別の wasm モジュール間でインポートすることができます。

+
+ +

テーブルの変更と動的リンク

+ +

JavaScript は関数参照にフルアクセスできるため、Table オブジェクトは JavaScript から grow(), get()set() メソッドを使用して変更することができます。WebAssembly が 参照型 を得たとき、WebAssembly コードは get_elem/set_elem 命令を使用してテーブル自身を変更することができるようになるでしょう。

+ +

テーブルは変更可能であるため、それらは複雑なロード時、実行時の 動的リンクスキーム の実装で使用することができます。プログラムが動的にリンクされたとき、複数のインスタンスで同じメモリとテーブルを共有することができます。これは複数のコンパイル済み .dll が単一のプロセスのアドレス空間を共有するネイティブアプリケーションと対称的です。

+ +

この動作を確認するために、Memory オブジェクトと Table オブジェクトを含む単一のインポートオブジェクトを作成し、同じインポートオブジェクトを複数の instantiate() の呼び出しで渡してみましょう。

+ +

.wat ファイルの例は次のようになります。

+ +

shared0.wat:

+ +
(module
+  (import "js" "memory" (memory 1))
+  (import "js" "table" (table 1 funcref))
+  (elem (i32.const 0) $shared0func)
+  (func $shared0func (result i32)
+   i32.const 0
+   i32.load)
+)
+ +

shared1.wat:

+ +
(module
+  (import "js" "memory" (memory 1))
+  (import "js" "table" (table 1 funcref))
+  (type $void_to_i32 (func (result i32)))
+  (func (export "doIt") (result i32)
+   i32.const 0
+   i32.const 42
+   i32.store  ;; store 42 at address 0
+   i32.const 0
+   call_indirect (type $void_to_i32))
+)
+ +

These work as follows:

+ +
    +
  1. 関数 shared0funcshared0.wat で定義され、インポートされたテーブルに格納されます。
  2. +
  3. この関数は定数値 0 を作成して、次に i32.load コマンドを使用して指定したメモリのインデックスから値をロードします。そのインデックスは 0 になります 。先と同様に、前の値をスタックから暗黙的にポップします。つまり、shared0func はメモリのインデックス 0 の位置に格納された値をロードして返します。
  4. +
  5. shared1.wat では、 doIt という関数をエクスポートします。この関数は2つの定数値 042 を作成して i32.store を呼び出して、インポートされたメモリの指定したインデックスに指定した値を格納します。ここでも、これらの値はスタックから暗黙的にポップされます。したがって、結果的にメモリのインデックスが 0 の位置に、値として 42 が格納されます。
  6. +
  7. 関数の最後では、定数値 0 を作成し、テーブルのインデックスが 0 の位置にある関数を呼び出します。これは shared0func で、先に shared0.watelem ブロックで格納されたものです。
  8. +
  9. 呼び出されたとき、shared0funcshared1.wat 内で i32.store コマンドを使用してメモリに格納された 42 をロードします。
  10. +
+ +
+

: 上の式はスタックから値を暗黙的にポップしますが、代わりにコマンド呼び出しの中で明示的に宣言することができます:

+ +
(i32.store (i32.const 0) (i32.const 42))
+(call_indirect (type $void_to_i32) (i32.const 0))
+ + +
+ +

アセンブリに変換した後、次のコードで JavaScript 内で shared0.wasmshared1.wasm を使用します:

+ +
var importObj = {
+  js: {
+    memory : new WebAssembly.Memory({ initial: 1 }),
+    table : new WebAssembly.Table({ initial: 1, element: "funcref" })
+  }
+};
+
+Promise.all([
+  WebAssembly.instantiateStreaming(fetch('shared0.wasm'), importObj),
+  WebAssembly.instantiateStreaming(fetch('shared1.wasm'), importObj)
+]).then(function(results) {
+  console.log(results[1].instance.exports.doIt());  // prints 42
+});
+ +

コンパイルされた各モジュールは同じメモリとテーブルオブジェクトをインポートし、その結果同じ線形メモリとテーブルの「アドレス空間」を共有することができます。

+ +
+

: 例は GitHub の shared-address-space.html (動作例) を参照してください。

+
+ +

WebAssembly の複数値

+ +

もっと最近になって (例えば Firefox 78) 言語に追加されたものが WebAssembly 複数値であり、これは、WebAssembly 関数が複数の値を返すことができるようになり、一連の命令が複数のスタック値を消費して生成することができるようになったことを意味します。

+ +

執筆時点 (2020年6月) において、これは初期段階であり、利用可能な多値命令は、それ自体が複数の値を返す関数の呼び出しのみです。例を示します。

+ +
(module
+  (func $get_two_numbers (result i32 i32)
+    i32.const 1
+    i32.const 2
+  )
+  (func (export "add_to_numbers") (result i32)
+    call $get_two_numbers
+    i32.add
+  )
+)
+ +

しかし、これはより有用な命令タイプやその他のものへの道を開くことになるでしょう。これまでの進捗状況や、これがどのように動作するかについては、 Nick Fitzgerald の Multi-Value All The Wasm! を参照してください。

+ +

まとめ

+ +

これで、WebAssembly テキストフォーマットの主要コンポーネントとそれらが WebAssembly JS API にどのように反映されるのかの高レベルなツアーが完了しました。

+ +

関連情報

+ + diff --git a/files/ja/webassembly/using_the_javascript_api/index.html b/files/ja/webassembly/using_the_javascript_api/index.html deleted file mode 100644 index ea6221a243..0000000000 --- a/files/ja/webassembly/using_the_javascript_api/index.html +++ /dev/null @@ -1,283 +0,0 @@ ---- -title: WebAssembly JavaScript API を使用する -slug: WebAssembly/Using_the_JavaScript_API -translation_of: WebAssembly/Using_the_JavaScript_API ---- -
{{WebAssemblySidebar}}
- -

これまでに Emscriptenのようなツールを使用して他の言語からモジュールをコンパイルしたりあなた自身のコードをロードして実行しました。次のステップは他のWebAssembly JavaScript APIの使い方について学ぶことです。この記事ではあなたが知る必要があることを説明します。

- -
-

: もし、この記事で説明している基本的なコンセプトがよくわからない場合、WebAssemblyのコンセプト をはじめに読んでからこの記事に戻ってきてください。

-
- -

シンプルな例

- -

WebAssembly JavaScript API の使用方法と、wasm モジュールをロードしてウェブページ内で使用する方法をステップ・バイ・ステップの例を通して実行してみましょう。

- -
-

: サンプルコードは webassembly-examples GitHub レポジトリから参照してください。

-
- -

例を準備する

- -
    -
  1. まずは、wasm モジュールが必要です! simple.wasm をコピーしてローカルマシンの新しいディレクトリの中に保存します。
  2. -
  3. 次に、使用しているブラウザが WebAssembly に対応しているか確認します。Firefox 52+ と Chrome 57+ では WebAssembly がデフォルトで有効になっています。
  4. -
  5. 次に、wasm ファイルと同じディレクトリに index.html という名前でシンプルな HTML ファイルを作成しましょう (もしも簡単に利用できるテンプレートを持っていない場合、simple template を使用できます) 。
  6. -
  7. ここで、何が起こっているのか理解を助けるために、wasm モジュールのテキスト表現を見てみましょう (テキストフォーマットから wasm に変換する も参照してください): -
    (module
    -  (func $i (import "imports" "imported_func") (param i32))
    -  (func (export "exported_func")
    -    i32.const 42
    -    call $i))
    -
  8. -
  9. 2行目に2階層の名前空間を持つインポートの宣言があります — 内部関数 $i は imports.imported_func からインポートされています。wasm モジュールにインポートするオブジェクトを記述するときに、この2階層の名前空間を JavaScript に反映させる必要があります。<script></script> 要素を HTML 内に作成して、次のコードを追加します: -
    var importObject = {
    -  imports: {
    -      imported_func: function(arg) {
    -        console.log(arg);
    -      }
    -    }
    -  };
    -
  10. -
- -

上で説明したように、 imports.imported_func でインポート機能を利用できます。

- -
-

ES6のアローファンクション を使用するとより簡潔に書くことができます:

- -
var importObject = { imports: { imported_func: arg => console.log(arg) } };
-
- -

スタイルはあなたが好きなものを選んでください。

- -

wasm モジュールをロードして使用する

- -

インポートオブジェクトを用意して、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 の置き換えを目指しているわけではありません。両方が協力して、お互いの強みを活かすことができます。

- -

デベロッパーツールで wasm を見る

- -

Firefox 54+ では、デベロッパーツールのデバッガパネルでウェブページに含まれる wasm コードのテキスト表現を表示する機能があります。これを表示するためには、デバッガパネルに移動して、“xxx > wasm” エントリをクリックしてください。

- -

- -

Firefox で、WebAssembly をテキストとして表示することに加えて、開発者は WebAssembly のテキスト表現を使用してすぐにデバッグを開始することができます (ブレークポイント、コールスタックの検査、ステップ実行など) 。WebAssembly debugging with Firefox DevTools の動画を参照してください。

- -

WebAssembly モジュールのストリーミング

- -

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()")}} コンストラクタから作成することができます。

- -

簡単な例を見て探索を始めましょう。

- -
    -
  1. -

    memory.html という名前の新しいシンプルな HTMLページ を作成します (simple template をコピーしてください) 。<script></script> をページに追加します。

    -
  2. -
  3. -

    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]
    -
  4. -
  5. -

    デモで試してみましょう。これまでに追加した内容を保存してブラウザで読み込んだ後、JavaScript コンソールで上の2行を入力してみてください。

    -
  6. -
- -

メモリを拡張する

- -

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 を参照してください。

- -
    -
  1. -

    memory.wasm のコピーを以前と同じディレクトリにコピーします。

    - -
    -

    : モジュールのテキスト表現は memory.wat を参照してください。

    -
    -
  2. -
  3. -

    memory.html サンプルファイルに戻って、以前と同じように wasm モジュールをフェッチ、コンパイル、インスタンス化します (以下をスクリプトに追加してください):

    - -
    fetch('memory.wasm').then(response =>
    -  response.arrayBuffer()
    -).then(bytes =>
    -  WebAssembly.instantiate(bytes)
    -).then(results => {
    -  // add your code here
    -});
    -
  4. -
  5. -

    このモジュールはモジュール内部のメモリをエクスポートします。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);
    -
  6. -
- -

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 から見つけられます。

- -
    -
  1. -

    table.wasm をローカルの新しいディレクトリにコピーします。

    - -
    -

    : このモジュールのテキスト表現は table.wat を参照してください。

    -
    -
  2. -
  3. -

    HTML template を table.html という名前で同じディレクトリにコピーします。

    -
  4. -
  5. -

    前と同じように、wasm モジュールをフェッチ、コンパイル、インスタンス化します。次のコードを HTML の body の末尾の {{htmlelement("script")}} 要素に追加します:

    - -
    fetch('table.wasm').then(response =>
    -  response.arrayBuffer()
    -).then(bytes =>
    -  WebAssembly.instantiate(bytes)
    -).then(results => {
    -  // add your code here
    -});
    -
  6. -
  7. -

    今度はテーブル内のデータにアクセスしてみましょう。コードの指定された場所に次の行を追加します:

    - -
    var tbl = results.instance.exports.tbl;
    -console.log(tbl.get(0)());  // 13
    -console.log(tbl.get(1)());  // 42
    -
  8. -
- -

このコードはテーブルに格納されている各関数参照に順番にアクセスし、内包した値をコンソールに書き出すためにインスタンス化します。Table.prototype.get() で各関数参照を取得した後、関数を実行するためには括弧を追加することに注意してください。

- -
-

: 完全なデモは table.html (動作例) を参照してください。このバージョンでは fetchAndInstantiate() 関数を使用しています。

-
- -

重複度

- -

ここでは、主な WebAssembly の構成要素のデモを見てきました。これは重複度の概念に言及するのに適しています。これはアーキテクチャ効率の点で多くの進歩がもたらされます:

- - - -

Understanding text format article の記事で重複度の働きについてみることができます。その中の Mutating tables and dynamic linking の章を見てください (TBD)。

- -

まとめ

- -

この記事では WebAssembly JavaScript API の基本的な使い方について説明しました。WebAssembly モジュールを JavaScript のコンテキストに組み込む方法、その関数を使えるようすること、JavaScript でのメモリとテーブルの使い方について。さらに、多重度の概念についても触れました。

- -

関連情報

- - diff --git a/files/ja/webassembly/using_the_javascript_api/index.md b/files/ja/webassembly/using_the_javascript_api/index.md new file mode 100644 index 0000000000..ea6221a243 --- /dev/null +++ b/files/ja/webassembly/using_the_javascript_api/index.md @@ -0,0 +1,283 @@ +--- +title: WebAssembly JavaScript API を使用する +slug: WebAssembly/Using_the_JavaScript_API +translation_of: WebAssembly/Using_the_JavaScript_API +--- +
{{WebAssemblySidebar}}
+ +

これまでに Emscriptenのようなツールを使用して他の言語からモジュールをコンパイルしたりあなた自身のコードをロードして実行しました。次のステップは他のWebAssembly JavaScript APIの使い方について学ぶことです。この記事ではあなたが知る必要があることを説明します。

+ +
+

: もし、この記事で説明している基本的なコンセプトがよくわからない場合、WebAssemblyのコンセプト をはじめに読んでからこの記事に戻ってきてください。

+
+ +

シンプルな例

+ +

WebAssembly JavaScript API の使用方法と、wasm モジュールをロードしてウェブページ内で使用する方法をステップ・バイ・ステップの例を通して実行してみましょう。

+ +
+

: サンプルコードは webassembly-examples GitHub レポジトリから参照してください。

+
+ +

例を準備する

+ +
    +
  1. まずは、wasm モジュールが必要です! simple.wasm をコピーしてローカルマシンの新しいディレクトリの中に保存します。
  2. +
  3. 次に、使用しているブラウザが WebAssembly に対応しているか確認します。Firefox 52+ と Chrome 57+ では WebAssembly がデフォルトで有効になっています。
  4. +
  5. 次に、wasm ファイルと同じディレクトリに index.html という名前でシンプルな HTML ファイルを作成しましょう (もしも簡単に利用できるテンプレートを持っていない場合、simple template を使用できます) 。
  6. +
  7. ここで、何が起こっているのか理解を助けるために、wasm モジュールのテキスト表現を見てみましょう (テキストフォーマットから wasm に変換する も参照してください): +
    (module
    +  (func $i (import "imports" "imported_func") (param i32))
    +  (func (export "exported_func")
    +    i32.const 42
    +    call $i))
    +
  8. +
  9. 2行目に2階層の名前空間を持つインポートの宣言があります — 内部関数 $i は imports.imported_func からインポートされています。wasm モジュールにインポートするオブジェクトを記述するときに、この2階層の名前空間を JavaScript に反映させる必要があります。<script></script> 要素を HTML 内に作成して、次のコードを追加します: +
    var importObject = {
    +  imports: {
    +      imported_func: function(arg) {
    +        console.log(arg);
    +      }
    +    }
    +  };
    +
  10. +
+ +

上で説明したように、 imports.imported_func でインポート機能を利用できます。

+ +
+

ES6のアローファンクション を使用するとより簡潔に書くことができます:

+ +
var importObject = { imports: { imported_func: arg => console.log(arg) } };
+
+ +

スタイルはあなたが好きなものを選んでください。

+ +

wasm モジュールをロードして使用する

+ +

インポートオブジェクトを用意して、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 の置き換えを目指しているわけではありません。両方が協力して、お互いの強みを活かすことができます。

+ +

デベロッパーツールで wasm を見る

+ +

Firefox 54+ では、デベロッパーツールのデバッガパネルでウェブページに含まれる wasm コードのテキスト表現を表示する機能があります。これを表示するためには、デバッガパネルに移動して、“xxx > wasm” エントリをクリックしてください。

+ +

+ +

Firefox で、WebAssembly をテキストとして表示することに加えて、開発者は WebAssembly のテキスト表現を使用してすぐにデバッグを開始することができます (ブレークポイント、コールスタックの検査、ステップ実行など) 。WebAssembly debugging with Firefox DevTools の動画を参照してください。

+ +

WebAssembly モジュールのストリーミング

+ +

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()")}} コンストラクタから作成することができます。

+ +

簡単な例を見て探索を始めましょう。

+ +
    +
  1. +

    memory.html という名前の新しいシンプルな HTMLページ を作成します (simple template をコピーしてください) 。<script></script> をページに追加します。

    +
  2. +
  3. +

    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]
    +
  4. +
  5. +

    デモで試してみましょう。これまでに追加した内容を保存してブラウザで読み込んだ後、JavaScript コンソールで上の2行を入力してみてください。

    +
  6. +
+ +

メモリを拡張する

+ +

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 を参照してください。

+ +
    +
  1. +

    memory.wasm のコピーを以前と同じディレクトリにコピーします。

    + +
    +

    : モジュールのテキスト表現は memory.wat を参照してください。

    +
    +
  2. +
  3. +

    memory.html サンプルファイルに戻って、以前と同じように wasm モジュールをフェッチ、コンパイル、インスタンス化します (以下をスクリプトに追加してください):

    + +
    fetch('memory.wasm').then(response =>
    +  response.arrayBuffer()
    +).then(bytes =>
    +  WebAssembly.instantiate(bytes)
    +).then(results => {
    +  // add your code here
    +});
    +
  4. +
  5. +

    このモジュールはモジュール内部のメモリをエクスポートします。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);
    +
  6. +
+ +

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 から見つけられます。

+ +
    +
  1. +

    table.wasm をローカルの新しいディレクトリにコピーします。

    + +
    +

    : このモジュールのテキスト表現は table.wat を参照してください。

    +
    +
  2. +
  3. +

    HTML template を table.html という名前で同じディレクトリにコピーします。

    +
  4. +
  5. +

    前と同じように、wasm モジュールをフェッチ、コンパイル、インスタンス化します。次のコードを HTML の body の末尾の {{htmlelement("script")}} 要素に追加します:

    + +
    fetch('table.wasm').then(response =>
    +  response.arrayBuffer()
    +).then(bytes =>
    +  WebAssembly.instantiate(bytes)
    +).then(results => {
    +  // add your code here
    +});
    +
  6. +
  7. +

    今度はテーブル内のデータにアクセスしてみましょう。コードの指定された場所に次の行を追加します:

    + +
    var tbl = results.instance.exports.tbl;
    +console.log(tbl.get(0)());  // 13
    +console.log(tbl.get(1)());  // 42
    +
  8. +
+ +

このコードはテーブルに格納されている各関数参照に順番にアクセスし、内包した値をコンソールに書き出すためにインスタンス化します。Table.prototype.get() で各関数参照を取得した後、関数を実行するためには括弧を追加することに注意してください。

+ +
+

: 完全なデモは table.html (動作例) を参照してください。このバージョンでは fetchAndInstantiate() 関数を使用しています。

+
+ +

重複度

+ +

ここでは、主な WebAssembly の構成要素のデモを見てきました。これは重複度の概念に言及するのに適しています。これはアーキテクチャ効率の点で多くの進歩がもたらされます:

+ + + +

Understanding text format article の記事で重複度の働きについてみることができます。その中の Mutating tables and dynamic linking の章を見てください (TBD)。

+ +

まとめ

+ +

この記事では WebAssembly JavaScript API の基本的な使い方について説明しました。WebAssembly モジュールを JavaScript のコンテキストに組み込む方法、その関数を使えるようすること、JavaScript でのメモリとテーブルの使い方について。さらに、多重度の概念についても触れました。

+ +

関連情報

+ + -- cgit v1.2.3-54-g00ecf