この章ではwasmモジュールのバイナリ表現について解説します。まずは内部で使用されるデータ型、次にモジュールの構造について見ていきます。この章の内容は公式の資料[5]を翻訳、補足したものです。
uintN符号なしの N bitの整数値、リトルエンディアンで表現されます。uint8、 uint16、 uint32 の3種類が使用されています。
varuintNLEB128[6]で表現される N bitの符号なし整数です。
現在 varuint1, varuint7, varuint32のみ使用されてます。前者2つは将来的な拡張機能と互換性のために使用されています。
varintN符号付きLEB128で表現される N bitの整数です。
現在 varint7, varint32, varint64が使用されています。
MVPでは、オペコードの個数はは256以下であるため1バイトで表現されます。将来的にSIMDやアトミック操作の命令を追加すると256を超えるため、拡張スキーマが必要になります。マルチバイトオペコード用に1バイトのプレフィックス値を策定中です。
全ての型(型コンストラクタを表す)は先頭の負のvarint7値によって識別されます。
表3.1:
オペコード(varint7値) | オペコード(バイト値) | Type constructor |
|---|---|---|
-0x01 | 0x7f | i32 |
-0x02 | 0x7e | i64 |
-0x03 | 0x7d | f32 |
-0x04 | 0x7c | f64 |
-0x10 | 0x70 | anyfunc |
-0x20 | 0x60 | func |
-0x40 | 0x40 | 空のblock_typeを表現する擬似的な型 |
この中でいくつかは追加のフィールドが続くものがあります(後述)。
将来的な拡張性のために隙間の数値は予約されています。符号付きの数値(つまりここで負の値)を使っているは1バイトでtype sectionへのインデックス(後述)を設定できるようにするためです。型システムの将来的な拡張に関連しています。
value_type値型を表すvarint7値。i32、i64、f32、f64の中の一つです。
block_typeブロック(後述)が返す型を表すvarint7値。ブロックが値を返す場合はvalue_type、何も返さない場合は-0x40が設定されます。
elem_typeテーブル(後述)中の要素の型を表すvarint7値。MVPではanyfuncのみ有効です。
将来的に他の要素も有効になる可能性があります。
func_type関数シグネチャーを表します。この型コンストラクタには以下の記述が追加されます。
表3.2:
| フィールド | 型 | 説明 |
|---|---|---|
| form | varint7 | 上の表で定義したfunc型コンストラクタの値 |
| param_count | varuint32 | 関数のパラメータ数 |
| param_types | value_type* | 関数のパラメータの型(パラメータ数だけ用意) |
| return_count | varuint1 | 関数の返り値の数 |
| return_type | value_type? | 関数の返り値の型(return_countが1なら) |
将来的にreturn_countとreturn_typeは複数の値を許容するために一般化されるかもしれません。
global_typeグローバル変数の情報を表します。
表3.3:
| フィールド | 型 | 説明 |
|---|---|---|
| content_type | value_type | 値の型 |
| mutability | varuint1 | 0ならイミュータブル。1ならミュータブル |
table_typeテーブルの情報を表します。
表3.4:
| フィールド | 型 | 説明 |
|---|---|---|
| element_type | elem_type | 要素の型 |
| limits | resizable_limits | 後述 |
memory_typeメモリーの情報を表します。
表3.5:
| フィールド | 型 | 説明 |
|---|---|---|
| limits | resizable_limits | 後述 |
external_kindインポートまたはモジュール内で定義された定義の種類を表す1バイトの符号なし整数値。
表3.6:
| Value | Description |
|---|---|
0 | インポートまたはモジュール内で定義された Function |
1 | インポートまたはモジュール内で定義された Table |
2 | インポートまたはモジュール内で定義された Memory |
3 | インポートまたはモジュール内で定義された Global |
resizable_limitsテーブル、メモリーの初期、最大サイズを表します。テーブルの場合は要素数、メモリーの場合は64KBを1単位とします。
表3.7:
| フィールド | 型 | 説明 |
|---|---|---|
| flags | varuint1 | maximumフィールドがあるかどうか |
| initial | varuint32 | 初期の長さ |
| maximum | varuint32? | 最大値(flagsが1ならこのフィールドがある) |
flags フィールドはvaruint32に変わる可能性があります。例えばスレッド間で共有するためのフラグが含まれたりする。
init_exprイニシャライザー。code section(後述)で用いられている式とendオペコードによって構成されます。
イニシャライザーに含まれるget_globalはインポートされたイミュータブルなグローバル変数のみ参照でき、全てのinit_exprはimport sectionの後でのみ使用できます。
WebAssemblyモジュールの高レベルな構造(セクションの集合)と各セクションについて解説します。wast2wasmでモジュールの詳細情報を確認できるので、合わせて見てもらえると理解の助けになると思われます。
$ wast2wasm -v add.wast
図3.1: wast2wasm -v の実行例
図3.2: wasmモジュール概要図
モジュールは以下の2つのフィールドから開始します。
表3.8:
| フィールド | 型 | 説明 |
|---|---|---|
| magic number | uint32 | マジックナンバー 0x6d736100 ("\0asm") |
| version | uint32 | バージョンナンバー(0x1) |
モジュールプリアンブルのあとに一連のセクションが続きます。各セクションは既知のセクションまたはカスタムセクションを表す1バイトのセクションコードによって識別されます。そして、セクションの長さ、ペイロードが次に続きます。既知のセクションは非ゼロなidを持ちます。カスタムセクションのidは0で、その後にペイロードの一部として識別文字列が続きます。カスタムセクションはWebAssemblyの実装では無視されるので内部でのバリデーションエラーがあってもモジュールが無効になるわけではありません。
表3.9:
| フィールド | 型 | 説明 |
|---|---|---|
| id | varuint7 | セクションコード |
| payload_len | varuint32 | セクションのバイト長 |
| name_len | varuint32? | セクション名のバイト長。id == 0のときだけ存在します |
| name | bytes? | セクション名の本体。id == 0のときだけ存在します |
| payload_data | セクションの中身。長さは payload_len - sizeof(name) - sizeof(name_len) |
既知のセクションはオプショナルで最大で1個だけです。カスタムセクションは全て同じidを持ちユニークでない名前をつけることができます。以下の表で定義される既知のセクションは順不同で現れない場合もあります。各セクションのpayload_dataにエンコードされた内容が入ります。
表3.10:
| セクション名 | コード | 説明 |
|---|---|---|
| Type | 1 | 関数シグネチャー宣言 |
| Import | 2 | インポート宣言 |
| Function | 3 | 関数宣言 |
| Table | 4 | 間接的な関数テーブルと他のテーブル |
| Memory | 5 | メモリー属性 |
| Global | 6 | グローバル宣言 |
| Export | 7 | エクスポート |
| Start | 8 | 開始関数宣言 |
| Element | 9 | 要素セクション |
| Code | 10 | 関数本体(code) |
| Data | 11 | データセグメント |
最後のセクションの最終バイトはモジュールの最終バイトと一致する必要があります。最小のモジュールは8バイトです(magic number, versionのあとに何もない状態)。
Type sectionでは、モジュールで使用されている全ての関数シグネチャーを宣言します。
表3.11:
| フィールド | 型 | 説明 |
|---|---|---|
| count | varuint32 | エントリー数 |
| entries | func_type* | 関数シグネチャー列 |
将来的に、他の型を持つエントリーもここに入る可能性があります。func_typeのformフィールドで識別できます。
Import sectionではモジュールで使用されている全てのインポートを宣言します。
表3.12:
| フィールド | 型 | 説明 |
|---|---|---|
| count | varuint32 | エントリー数 |
| entries | import_entry* | import entry(後述)列 |
各import_entryは以下のような構造を持ちます。
表3.13:
| フィールド | 型 | 説明 |
|---|---|---|
| module_len | varuint32 | モジュール文字列の長さ |
| module_str | bytes | module_lenバイトのモジュールの文字列 |
| フィールド_len | varuint32 | フィールド名の長さ |
| フィールド_str | bytes | フィールド_lenバイトのフィールド名 |
| kind | external_kind | インポートされた定義の種類 |
kindの種類によって以下の項目が続きます。kindがFunctionの場合は
表3.14:
| フィールド | 型 | 説明 |
|---|---|---|
| type | varuint32 | type sectionにある関数シグネチャーのインデックス |
kindがTableの場合は
表3.15:
| フィールド | 型 | 説明 |
|---|---|---|
| type | table_type | インポートされたテーブルの情報 |
kindがMemoryの場合は
表3.16:
| フィールド | 型 | 説明 |
|---|---|---|
| type | memory_type | インポートされたメモリーの情報 |
kindがGlobalの場合は
表3.17:
| フィールド | 型 | 説明 |
|---|---|---|
| type | global_type | インポートされたグローバル変数の情報 |
MVPではイミュータブルなグローバル変数のみインポートできます。
Function sectionではモジュール内のすべての関数シグネチャーを宣言します(関数の定義はcode sectionに置かれます)。
表3.18:
| フィールド | 型 | 説明 |
|---|---|---|
| count | varuint32 | エントリー数 |
| types | varuint32* | type sectionに置いてある対象のfunc_typeのインデックス列 |
Table sectionではモジュール内で使用するテーブル(参照を要素として持つ型付き配列)を定義します。
表3.19:
| フィールド | 型 | 説明 |
|---|---|---|
| count | varuint32 | モジュールに定義されているテーブルの数 |
| entries | table_type* | table_typeエントリー列 |
MVPでは、テーブルの数は1以下でなければいけません。table_typeで使用できる型はanyfuncだけで、関数テーブルとして機能します。
Memory sectionではモジュール内で使用する線形メモリーを定義します。
表3.20:
| フィールド | 型 | 説明 |
|---|---|---|
| count | varuint32 | モジュールで定義されているメモリーの数 |
| entries | memory_type* | memory_type エントリー列 |
MVPでは、メモリーの数は1以下でなければいけません。
Global sectionではモジュール内で使用するグローバル変数を定義します。
表3.21:
| フィールド | 型 | 説明 |
|---|---|---|
| count | varuint32 | グローバル変数エントリーの数 |
| globals | global_variable* | 後述のグローバル変数列 |
各global_variableは以下のような構造を持ちます。
表3.22:
| フィールド | 型 | 説明 |
|---|---|---|
| type | global_type | 変数の型 |
| init | init_expr | グローバル変数のイニシャライザー |
global_typeによって型とミュータビリティー、init_exprによって初期値が与えられます。
MVPではイミュータブルなグローバル変数だけエクスポートできます。
Export sectionではモジュール外にエクスポートするエントリーを宣言します。
表3.23:
| フィールド | 型 | 説明 |
|---|---|---|
| count | varuint32 | エントリー数 |
| entries | export_entry* | 後述のエクスポートエントリー列 |
export_entryは以下のような構造を持ちます。
表3.24:
| フィールド | 型 | 説明 |
|---|---|---|
| フィールド_len | varuint32 | フィールド名の長さ |
| フィールド_str | bytes | フィールド_lenバイトのフィールド名 |
| kind | external_kind | エクスポートされた定義の種類 |
| index | varuint32 | 対応するindex space(後述)へのインデックス |
例えば、kindがFunctionのときはindexはfunction section中でのインデックス(function index)になります。MVPではメモリ、テーブルは0だけです。
Start sectionではモジュールのインスタンス化が完了したときに呼ばれる関数を宣言します。
表3.25:
| フィールド | 型 | 説明 |
|---|---|---|
| index | varuint32 | 開始する関数のfunction index |
Element sectionではモジュールをインスタンス化するときに初期値としてテーブルに設定する要素を宣言します。
表3.26:
| フィールド | 型 | 説明 |
|---|---|---|
| count | varuint32 | エレメントセグメントの数 |
| entries | elem_segment* | 後述のエレメントセグメント列 |
elem_segmentは以下ような構造を持ちます。
表3.27:
| フィールド | 型 | 説明 |
|---|---|---|
| index | varuint32 | table index(MVPでは0) |
| offset | init_expr | 要素の場所のオフセットを返すイニシャライザー(i32値) |
| num_elem | varuint32 | 要素数 |
| elems | varuint32* | function index列 |
Code sectionにはモジュール内の全ての関数の本体が含まれています。function sectionで宣言された関数の数と関数本体の定義は必ず一致しなければいけません。i番目の関数宣言はi番目の関数本体に対応しています。
表3.28:
| フィールド | 型 | 説明 |
|---|---|---|
| count | varuint32 | function_bodyの数 |
| bodies | function_body* | 関数本体の列(Function bodiesで説明) |
Data sectionではモジュールをインスタンス化するときにロードされる初期化データを宣言します。
表3.29:
| フィールド | 型 | 説明 |
|---|---|---|
| count | varuint32 | データセグメントの数 |
| entries | data_segment* | 後述のデータセグメント列 |
data_segmentは以下のような構造を持ちます。
表3.30:
| フィールド | 型 | 説明 |
|---|---|---|
| index | varuint32 | 線形メモリーのインデックス(MVPでは0) |
| offset | init_expr | データの場所のオフセットを返すイニシャライザー(i32値) |
| size | varuint32 | dataのバイト数 |
| data | bytes | データ本体 |
Name sectionはカスタムセクションの1つです。nameフィールドに"name"が設定されています。全てのカスタムセクション同様、このセクションでのバリデーションエラーは全体のモジュールには影響しません。Name sectionは(あれば)データセクションのあとに1回だけ現れます。wasmモジュールやその他、開発環境で表示する場合、Name sectionで定義される関数名やローカル変数名はWebAssemblyのテキスト表現などで使用されることが期待されます。影響しないのでここでは構造についての説明は省略します。
関数本体は一連のローカル変数宣言の後にバイトコード命令が続きます。これは構造化されたスタックマシンの命令列とみなすことができます。命令はオペコードと0個以上の即値オペランド(immediates)にエンコードされます。各関数本体はendオペコードで終わらなければなりません。
表3.31:
| フィールド | 型 | 説明 |
|---|---|---|
| body_size | varuint32 | 関数本体のバイト数 |
| local_count | varuint32 | ローカル変数の数 |
| locals | local_entry | ローカル変数列 |
| code | byte* | 関数のバイトコード |
| end | byte | 0x0b、関数本体の終了 |
各ローカルエントリーは与えられた型のローカル変数リストを宣言します。同じ型のエントリーを複数持つことができます。
表3.32:
| フィールド | 型 | 説明 |
|---|---|---|
| count | varuint32 | ローカル変数の数 |
| type | value_type | 変数の型 |
wasmで定義されているオペコードを紹介します。オペコードがオペランドを受け取る場合はN番目のオペランドをopNとします。
表3.33:
| 名前 | オペコード | 即値 | 説明 |
|---|---|---|---|
unreachable | 0x00 | 即例外を発生 | |
nop | 0x01 | なにもしないオペコード | |
block | 0x02 | sig:block_type | 式のシーケンスを開始して、0個か1個の値をpush |
loop | 0x03 | sig:block_type | ループできるブロックを作る |
if | 0x04 | sig:block_type | if式。op1 != 0のときブロックに入る |
else | 0x05 | ifに対応するelse式 | |
end | 0x0b | block、if、loopの終了 | |
br | 0x0c | relative_depth:varuint32 | 対象の親ブロックに対して分岐命令を行う(以下補足) |
br_if | 0x0d | relative_depth:varuint32 | brの条件分岐あり版(以下補足) |
br_table | 0x0e | 後述 | 後述 |
return | 0x0f | 関数から0個か1個の値を返す |
br, br_if, br_table は分岐命令を行う対象のブロックによって挙動が変わります。block, if ブロックでは、対応する end に、loop ブロックではブロックの先頭に飛びます。
brrelative_depth のイメージ
block ;; 2
block ;; 1
block ;; 0
br 1 ;; 現在のブロックからの深さを指定して分岐
end
end
end
blockが値をpushしない場合、オペランドを受け取りません。blockが値をpushする場合、op1をpushします。
br_ifblockが値をpushしない場合、op1 != 0 のとき分岐します。blockが値をpushする場合、op2 != 0 のとき分岐して、op1 をpushします。
br_tablebr_tableオペコードは以下のような即値オペランドを持っています。
表3.34:
| フィールド | 型 | 説明 |
|---|---|---|
| target_count | varuint32 | target_tableのエントリー数 |
| target_table | varuint32* | 分岐する対象の親ブロック列 |
| default_target | varuint32 | デフォルトで分岐するブロック |
ブロックが値をpushしない場合、target_tableのop1番目のブロックに対して分岐命令を行います。ブロックが値をpushする場合はbr_ifなどと同様に最初のオペランドがpushされます。入力値が範囲外の場合、br_tableはdefault_targetに指定されているブロックに対して分岐命令を行います。
オペコードの開いてる場所は将来のために予約されてます。
表3.35:
| 名前 | オペコード | 即値 | 説明 |
|---|---|---|---|
call | 0x10 | function_index: varuint32 | function indexに対応する関数を呼ぶ |
call_indirect | 0x11 | type_index: varuint32, reserved: veruint1 | type_index の型の関数を間接的に呼ぶ |
call_indirectは入力として、関数の引数分のオペランド(type_indexから型を参照して引数の数を決定)とテーブルへのindexを受け取ります。テーブルのindex位置のelem_segmentの中で指定されているfunction indexから関数を参照して呼び出します。reservedは将来的に使うので予約されてます。MVPでは0です。
表3.36:
| 名前 | オペコード | 即値 | 説明 |
|---|---|---|---|
drop | 0x1a | スタックの1番上の値をpop | |
select | 0x1b | op3 != 0 ? op1 : op2 |
表3.37:
| 名前 | オペコード | 即値 | 説明 |
|---|---|---|---|
get_local | 0x20 | local_index: varuint32 | local_indexで指定されたローカル変数をスタックにpush |
set_local | 0x21 | local_index: varuint32 | op1の値をlocal_indexで指定されたローカル変数にセット |
tee_local | 0x22 | local_index: varuint32 | op1の値をlocal_indexで指定されたローカル変数にセットして、同じ値をpush |
get_global | 0x23 | global_index: varuint32 | global_indexで指定されたグローバル変数をスタックにpush |
set_global | 0x24 | global_index: varuint32 | op1の値をglobal_indexで指定されたグローバル変数にセット |
表3.38:
| 名前 | オペコード | 即値 | 説明 |
|---|---|---|---|
i32.load | 0x28 | memory_immediate | メモリーのオフセットop1 + memory_immediate.offset(後述)から32bit整数のデータを読み込んでスタックにpush |
i64.load | 0x29 | memory_immediate | 略 |
f32.load | 0x2a | memory_immediate | 略 |
f64.load | 0x2b | memory_immediate | 略 |
i32.load8_s | 0x2c | memory_immediate | 符号あり8bit整数を以下略 |
i32.load8_u | 0x2d | memory_immediate | 符号なし8bit整数を以下略 |
i32.load16_s | 0x2e | memory_immediate | 略 |
i32.load16_u | 0x2f | memory_immediate | 略 |
i64.load8_s | 0x30 | memory_immediate | 略 |
i64.load8_u | 0x31 | memory_immediate | 略 |
i64.load16_s | 0x32 | memory_immediate | 略 |
i64.load16_u | 0x33 | memory_immediate | 略 |
i64.load32_s | 0x34 | memory_immediate | 略 |
i64.load32_u | 0x35 | memory_immediate | 略 |
i32.store | 0x36 | memory_immediate | メモリーのオフセットop1 + memory_immediate.offset(後述)にop2の値を書き込む |
i64.store | 0x37 | memory_immediate | 略 |
f32.store | 0x38 | memory_immediate | 略 |
f64.store | 0x39 | memory_immediate | 略 |
i32.store8 | 0x3a | memory_immediate | 略 |
i32.store16 | 0x3b | memory_immediate | 略 |
i64.store8 | 0x3c | memory_immediate | 略 |
i64.store16 | 0x3d | memory_immediate | 略 |
i64.store32 | 0x3e | memory_immediate | 略 |
current_memory | 0x3f | reserved: varuint1 | メモリーのサイズを問い合わせる |
grow_memory | 0x40 | reserved: varuint1 | メモリーのサイズを拡張する |
memory_immediate は以下のようにエンコードされます。
表3.39:
| 名前 | 型 | 説明 |
|---|---|---|
| flags | varuint32 | フラグ。現在は最下位bitsがアライメントの値として使われている(log2(alignment)でエンコードされる) |
| offset | varuint32 | オフセット値 |
flagsはlog2(alignment)でエンコードされるので2の累乗である必要があります。追加のバリデーション基準として、アライメントは元々のアライメント以下でないといけません。log(memory-access-size)より下位のbitsは0である必要があります。これは将来のために予約されてます(例えば、共有メモリーの順序のために必要)。
current_memory、grow_memoryオペコードのreservedは将来的に使われる予定です(MVPでは0)。
表3.40:
| 名前 | オペコード | 即値 | 説明 |
|---|---|---|---|
i32.const | 0x41 | value:varint32 | i32と解釈される定数値をpush |
i64.const | 0x42 | value:varint64 | i64と解釈される定数値をpush |
f32.const | 0x43 | value:uint32 | f32と解釈される定数値をpush |
f64.const | 0x44 | value:uint64 | f64と解釈される定数値をpush |
真の場合は1、偽の場合は0をpushします。
表3.41:
| 名前 | オペコード | 即値 | 説明 |
|---|---|---|---|
i32.eqz | 0x45 | op1 == 0 | |
i32.eq | 0x46 | op1 == op2(符号に依存しない) | |
i32.ne | 0x47 | op1 != op2(符号に依存しない) | |
i32.lt_s | 0x48 | op1 < op2(符号付き整数として) | |
i32.lt_u | 0x49 | op1 < op2(符号なし整数として) | |
i32.gt_s | 0x4a | op1 > op2(符号付き整数として) | |
i32.gt_u | 0x4b | op1 > op2(符号なし整数として) | |
i32.le_s | 0x4c | op1 <= op2(符号付き整数として) | |
i32.le_u | 0x4d | op1 <= op2(符号なし整数として) | |
i32.ge_s | 0x4e | op1 >= op2(符号付き整数として) | |
i32.ge_u | 0x4f | op1 >= op2(符号なし整数として) | |
i64.eqz | 0x50 | op1 == 0 | |
i64.eq | 0x51 | op1 == op2(符号に依存しない) | |
i64.ne | 0x52 | op1 != op2(符号に依存しない) | |
i64.lt_s | 0x53 | op1 < op2(符号付き整数として) | |
i64.lt_u | 0x54 | op1 < op2(符号なし整数として) | |
i64.gt_s | 0x55 | op1 > op2(符号付き整数として) | |
i64.gt_u | 0x56 | op1 > op2(符号なし整数として) | |
i64.le_s | 0x57 | op1 <= op2(符号付き整数として) | |
i64.le_u | 0x58 | op1 <= op2(符号なし整数として) | |
i64.ge_s | 0x59 | op1 >= op2(符号付き整数として) | |
i64.ge_u | 0x5a | op1 >= op2(符号なし整数として) | |
f32.eq | 0x5b | op1 == op2 | |
f32.ne | 0x5c | op1 != op2 | |
f32.lt | 0x5d | op1 < op2 | |
f32.gt | 0x5e | op1 > op2 | |
f32.le | 0x5f | op1 <= op2 | |
f32.ge | 0x60 | op1 >= op2 | |
f64.eq | 0x61 | op1 == op2 | |
f64.ne | 0x62 | op1 != op2 | |
f64.lt | 0x63 | op1 < op2 | |
f64.gt | 0x64 | op1 > op2 | |
f64.le | 0x65 | op1 <= op2 | |
f64.ge | 0x66 | op1 >= op2 |
表3.42:
| 名前 | オペコード | 即値 | 説明 |
|---|---|---|---|
i32.clz | 0x67 | op1の値について最上位bitから0が連続する回数(符号に依存しない) | |
i32.ctz | 0x68 | op1の値について最下位bitから0が連続する回数(符号に依存しない) | |
i32.popcnt | 0x69 | op1の値について1になっているbitの数(符号に依存しない) | |
i32.add | 0x6a | op1 + op2(符号に依存しない) | |
i32.sub | 0x6b | op1 - op2(符号に依存しない) | |
i32.mul | 0x6c | op1 * op2(符号に依存しない, 下位32bitが結果になる) | |
i32.div_s | 0x6d | op1 / op2符号あり(結果は0方向に切り捨て) | |
i32.div_u | 0x6e | op1 / op2符号なし(切り捨て) | |
i32.rem_s | 0x6f | op1 % op2符号付き(符号は割られる方が使われる) | |
i32.rem_u | 0x70 | op1 % op2符号なし | |
i32.and | 0x71 | op1 & op2(符号に依存しない) | |
i32.or | 0x72 | op1 | op2(符号に依存しない) | |
i32.xor | 0x73 | op1 ^ op2(符号に依存しない) | |
i32.shl | 0x74 | op1 << op2(符号に依存しない) | |
i32.shr_s | 0x75 | op1 >> op2(算術シフト) | |
i32.shr_u | 0x76 | op1 >>> op2(論理シフト) | |
i32.rotl | 0x77 | 左ローテート(符号に依存しない) | |
i32.rotr | 0x78 | 右ローテート(符号に依存しない) | |
i64.clz | 0x79 | 最上位bitから0が連続する回数(符号に依存しない) | |
i64.ctz | 0x7a | 最下位bitから0が連続する回数(符号に依存しない) | |
i64.popcnt | 0x7b | 1になっているbitの数(符号に依存しない) | |
i64.add | 0x7c | op1 + op2(符号に依存しない) | |
i64.sub | 0x7d | op1 - op2(符号に依存しない) | |
i64.mul | 0x7e | op1 * op2(符号に依存しない, 下位64bitが結果になる) | |
i64.div_s | 0x7f | op1 / op2符号あり(結果は0方向に切り捨て) | |
i64.div_u | 0x80 | op1 / op2符号なし(切り捨て) | |
i64.rem_s | 0x81 | op1 % op2符号付き(符号は割られる方が使われる) | |
i64.rem_u | 0x82 | op1 % op2符号なし | |
i64.and | 0x83 | op1 & op2(符号に依存しない) | |
i64.or | 0x84 | op1 | op2(符号に依存しない) | |
i64.xor | 0x85 | op1 ^ op2(符号に依存しない) | |
i64.shl | 0x86 | op1 << op2(符号に依存しない) | |
i64.shr_s | 0x87 | op1 >> op2(算術シフト) | |
i64.shr_u | 0x88 | op1 >>> op2(論理シフト) | |
i64.rotl | 0x89 | 左ローテート(符号に依存しない) | |
i64.rotr | 0x8a | 右ローテート(符号に依存しない) | |
f32.abs | 0x8b | 絶対値 | |
f32.neg | 0x8c | 符号逆転 | |
f32.ceil | 0x8d | 切り上げ | |
f32.floor | 0x8e | 切り捨て | |
f32.trunc | 0x8f | 0方向に丸める | |
f32.nearest | 0x90 | 四捨五入(偶数丸め) | |
f32.sqrt | 0x91 | 平方根 | |
f32.add | 0x92 | op1 + op2 | |
f32.sub | 0x93 | op1 - op2 | |
f32.mul | 0x94 | op1 * op2 | |
f32.div | 0x95 | op1 / op2 | |
f32.min | 0x96 | 2つの値のうち小さい方をpush。どちらかのオペランドがNaNの場合NaN | |
f32.max | 0x97 | 2つの値のうち大きい方をpush。どちらかのオペランドがNaNの場合NaN | |
f32.copysign | 0x98 | 1つ目の値の符号を2つ目の値にコピーしたものをpush | |
f64.abs | 0x99 | 絶対値 | |
f64.neg | 0x9a | 符号逆転 | |
f64.ceil | 0x9b | 切り上げ | |
f64.floor | 0x9c | 切り捨て | |
f64.trunc | 0x9d | 0方向に丸める | |
f64.nearest | 0x9e | 四捨五入(偶数丸め) | |
f64.sqrt | 0x9f | 平方根 | |
f64.add | 0xa0 | op1 + op2 | |
f64.sub | 0xa1 | op1 - op2 | |
f64.mul | 0xa2 | op1 * op2 | |
f64.div | 0xa3 | op1 / op2 | |
f64.min | 0xa4 | 2つの値のうち小さい方をpush。どちらかのオペランドがNaNの場合NaN | |
f64.max | 0xa5 | 2つの値のうち大きい方をpush。どちらかのオペランドがNaNの場合NaN | |
f64.copysign | 0xa6 | 2つ目の値の符号を1つ目の値にコピーしたものをpush |
型変換を行います。
f64.convert_s/i32では最近接偶数へ丸められます。そして、IEEE 754-2008で定義された無限大、負の無限大にオーバーフローする可能性があります。
浮動小数点数から整数に変換する命令(trunc)の場合、0方向に丸められた整数値となります。オーバーフローするような数値を変換しようとするとエラーとなります。
表3.43:
| 名前 | オペコード | 即値 | 説明 |
|---|---|---|---|
i32.wrap/i64 | 0xa7 | 64bit整数を受け取って、それの下位32bitをpush | |
i32.trunc_s/f32 | 0xa8 | 32bit浮動小数点数を受け取って、0方向に丸めた32bitの符号付き整数をpush | |
i32.trunc_u/f32 | 0xa9 | 32bit浮動小数点数を受け取って、0方向に丸めた32bitの符号なし整数をpush | |
i32.trunc_s/f64 | 0xaa | 64bit浮動小数点数を受け取って、0方向に丸めた32bitの符号付き整数をpush | |
i32.trunc_u/f64 | 0xab | 64bit浮動小数点数を受け取って、0方向に丸めた32bitの符号なし整数をpush | |
i64.extend_s/i32 | 0xac | 32bit整数を64bit符号付き整数に拡張する | |
i64.extend_u/i32 | 0xad | 32bit整数を64bit符号なし整数に拡張する | |
i64.trunc_s/f32 | 0xae | 32bit浮動小数点数を受け取って、0方向に丸めた64bitの符号付き整数をpush | |
i64.trunc_u/f32 | 0xaf | 32bit浮動小数点数を受け取って、0方向に丸めた64bitの符号なし整数をpush | |
i64.trunc_s/f64 | 0xb0 | 64bit浮動小数点数を受け取って、0方向に丸めた64bitの符号付き整数をpush | |
i64.trunc_u/f64 | 0xb1 | 64bit浮動小数点数を受け取って、0方向に丸めた64bitの符号なし整数をpush | |
f32.convert_s/i32 | 0xb2 | 32bit整数を32bit浮動小数点数に変換する(符号ありとして) | |
f32.convert_u/i32 | 0xb3 | 32bit整数を32bit浮動小数点数に変換する(符号なしとして) | |
f32.convert_s/i64 | 0xb4 | 64bit整数を32bit浮動小数点数に変換する(符号ありとして) | |
f32.convert_u/i64 | 0xb5 | 64bit整数を32bit浮動小数点数に変換する(符号なしとして) | |
f32.demote/f64 | 0xb6 | 64bit浮動小数点数から32bit浮動小数点数に変換 | |
f64.convert_s/i32 | 0xb7 | 32bit整数を64bit浮動小数点数に変換する(符号ありとして) | |
f64.convert_u/i32 | 0xb8 | 32bit整数を64bit浮動小数点数に変換する(符号なしとして) | |
f64.convert_s/i64 | 0xb9 | 64bit整数を64bit浮動小数点数に変換する(符号ありとして) | |
f64.convert_u/i64 | 0xba | 64bit整数を64bit浮動小数点数に変換する(符号なしとして) | |
f64.promote/f32 | 0xbb | 32bit浮動小数点数から64bit浮動小数点数に変換 |
変換前の数値をただのbit列として扱って変換後の型として再解釈します。
表3.44:
| 名前 | オペコード | 即値 | 説明 |
|---|---|---|---|
i32.reinterpret/f32 | 0xbc | 32bit浮動小数点数を32bit整数として再解釈する | |
i64.reinterpret/f64 | 0xbd | 64bit浮動小数点数を64bit整数として再解釈する | |
f32.reinterpret/i32 | 0xbe | 32bit整数を32bit浮動小数点数として再解釈する | |
f64.reinterpret/i64 | 0xbf | 64bit整数を64bit浮動小数点数として再解釈する |