WebAssemblyのload命令のロジックを確認してみる

作ってみたいものが決まりつつあるので、WebAssemblyの方に戻ってきました。 メモリ周辺の命令を実装したいので、まずはloadからじっくりと読み直しています。

仕様上、命令の実行時の動作が書かれているのは4章です。仕様上、複数の命令の動作がまとめて一つのセクションで書かれているんですが、ややこしいので手始めにi32.loadに限った形でなぞり直してみましょう。

WebAssemblyの命令は、詳細なロジックが手続型で書かれています。これとは別途、以下のように、形式的な規則も書かれています。

f:id:ironoir:20210221231430p:plain

一度手続き的な仕様を理解してしまえば、そのあとはこの形式的な仕様を見た方が思い出すのは早いかもしれません。命令の実行前と実行後状態が比較して書かれているので、何がどう変化するのかがわかりやすいからです。

load命令の実行は、実質以下の部分だけです。他は事前準備と後片付け、ですね。

  1. Let 𝑏* be the byte sequence mem.data[ea : 𝑁/8].

1~5: メモリ部分の確認

1. Let 𝐹 be the current frame.
2. Assert: due to validation, 𝐹.module.memaddrs[0] exists.
3. Let 𝑎 be the memory address 𝐹.module.memaddrs[0].
4. Assert: due to validation, 𝑆.mems[𝑎] exists.
5. Let mem be the memory instance 𝑆.mems[𝑎].

実行時、メモリが正しく存在しているかどうかのチェックです。将来的にはモジュール内に複数の線形メモリを持てるようになるのですが、現時点では1つだけなので、それを指すアドレスも1つだけ(𝐹.module.memaddrs[0])です。

6,7: ロード先をスタックから取得

6. Assert: due to validation, a value of value type i32 is on the top of the stack. 
7. Pop the value i32.const 𝑖 from the stack.

WebAssemblyのメモリ空間は最大232バイトなので、スタックトップに予めそのアドレスを表す即値を置いておきます。これが値のロード先になるわけですね。

8: オフセットも設定

8. Let ea be the integer 𝑖 + memarg.offset.

メモリに関する命令の場合、memargという設定を後ろにつけます。これにはオフセットとアラインメントに関する情報が含まれていて、ここではそのオフセットをさきほどスタックから取得したアドレスに足します。

9, 10: メモリ境界チェック

9. If 𝑁 is not part of the instruction, then:
    a. Let 𝑁 be the bit width |𝑡| of value type 𝑡.
10. If ea + 𝑁/8 is larger than the length of mem.data, then:
    a. Trap.

書き込み先がメモリ長を溢れていないかのチェックです。溢れているとtrapします。ちなみにメモリは最大値を設定することもしないこともできます。

11: 書き込み

11. Let 𝑏* be the byte sequence mem.data[ea : 𝑁/8].

上記の通り、本体の処理です。

12, 13: 結果を決める

12. If 𝑁 and sx are part of the instruction, then:
    a. Let𝑛betheintegerforwhichbytesi𝑁(𝑛)=𝑏 .
    b. Let 𝑐 be the result of computing extend_sx𝑁,|𝑡|(𝑛). 
13. Else:
    a. Let 𝑐 be the constant for which bytes𝑡(𝑐) = 𝑏* .

load命令の結果ってどうなるの?と思われるかもしれません。WebAssemblyの仕様では、スタックにロードした値が新しく積まれるみたいです。手順12は今回対象にしているi32.loadでは通らず、13だけを気にすればOKで、まさにロードした値をcとして取得しています。よく読んでみると、メモリ内はトルエンディアンで値を格納するように明記されています(記述は仕様書の他の場所ですが)。

14: 結果をスタックに積む

 14. Push the value 𝑡.const 𝑐 to the stack.

これで完了です。

特に難しいことはないですが、validation含めて、詳細なロジックが書かれているのは個人的には好感度大です。書いてみよう。