Learning WASM #1
June 3, 2021•191 words
dotnetからwasmを実行するのが面白そう。これがモノになったら多言語間をまたいだプラグインシステムが作るんじゃないかな。
まだ、File IOとかNetwork Stackとかは使えないらしいけど、専用関数用意すればよさそう。FastlyとかCloudflareとかはエッジ用のエンジンでwasm実行できるようにしてるしね。
まずは、wasmになれたい。
https://github.com/bytecodealliance/wasmtime-dotnet/blob/main/examples/memory/memory.wat
(module
(type $t0 (func (param i32 i32)))
(import "" "log" (func $.log (type $t0)))
(memory (export "mem") 1 2)
(data (i32.const 0) "Hello World")
(func $run
i32.const 0
i32.const 11
call $.log
)
(export "run" (func $run))
)
これはテキスト形式、本当はバイナリだけど、読みやすいテキスト形式で学習していく。
見た目は、S式だね。func $run ~ callのところを見た感じ、値、値、callと続いてるからスタックマシンぽいね。
https://developer.mozilla.org/en-US/docs/WebAssembly/Understanding_the_text_format
MDNのドキュメントを読みながら解読してく。
(module)
モジュールの塊を表す式(module)
が最小のモジュールらしい。中身は空。
モジュールに入ってないコードは不正なんだろうか? module式で纏まってなかったらそれは文って感じだろうし違反しそう。
(func (param i32) (param i32) (result f64) ... )
モジュールに関数を生やすには、 func
式を使う。 任意の(引数)
、(戻り値)
が続き、そのあと式の中身が続くぽい。
名前はどこだ? 一番最初のサンプルみるに、引数の前っぽいけど。名前の先頭に$
がつくのは作法なのかな。
(module
(func $myadd (param $l i32) (param $r i32) (result i32)
local.get $l
local.get $r
i32.add)
)
こんな感じかな。引数は local.get
でスタックに載せられるみたい。 i32.add
はcallしなくていいから組み込み命令なのかな。
スタックマシンだから結果はスタックに乗る感じかな。
戻り値はどうやって指定するんだろ? スタックの一番上が自動的に返されるのかな。
便利なページ見つけた。
https://webassembly.github.io/spec/core/exec/index.html
これで構文がすぐわかるね。
作った関数は、 export
でモジュールのそとに公開できる。
(module
(func $myadd (param $l i32) (param $r i32) (result i32)
local.get $l
local.get $r
i32.add)
(export "add" (func $myadd))
)
逆に外の関数を使えるようにするには import
が必要。
import ::= {module name, name name, desc importdesc}
importdesc ::= func typeidxtable
| tabletypemem
| memtypeglobal
| globaltype
importにはモジュール名、対象名、説明が必要で説明の種類を見る限り、関数だけじゃななさそう。memoryとかglobalにも使う。tableはちょっとまだわからん。
プリミティブ以外の値はどうすればいいのか、memory
を使って、バッファーを確保して使うみたい。
js側からしたらArrayBufferをwasmに渡して、処理が終わったらそのArrayBufferからデータを取り出す感じ。
function consoleLogString(offset, length) {
var bytes = new Uint8Array(memory.buffer, offset, length);
var string = new TextDecoder('utf8').decode(bytes);
console.log(string);
}
なるほどね。
(memory 1 2)
みたいにかけるけど、これは、1が最小ページサイズ、2が最大ページサイズらしい。1ページ64KB。
この共有メモリにprotocol bufferとかmessage packのバイナリ置けば、言語間でオブジェクトをやり取りできそう。もちろんJSONでもいいわけだけど。