https://emsn.connpass.com/event/290430/
RustでWasm Runtimeを書いた(株)テックリード ゴリラ
View Slide
QSPGJMF\OBNFΰϦϥ KPC<ΤϯδχΞ ձࣾܦӦ> MJLFT<3VTU (P 5ZQF4DSJQU %FOP 7JN 8BTN %PDLFS LT> TOT<9 5XJUUFSIUUQTUXJUUFSDPNHPSJMMB (JU)VCIUUQTHJUIVCDPNTLBOFIJSB [FOOIUUQT[FOOEFWTLBOFIJSB >>
Wasm Runtimeを書いたモチベ
Wasm 面白そうだけど どう動いているのか よくわからん
実装して完全理解するしかない↓実装してみたhttps://github.com/skanehira/chibiwasm※https://www.w3.org/TR/wasm-core-1/ の仕様まで
デモ
あらためて...Wasm(WebAssembly)とは?
https://developer.mozilla.org/ja/docs/WebAssembly より
狭義的にWasmはブラウザでも動く仮想命令セット※文脈によってエコシステム周辺も含めてwasmと呼ぶこともある
命令セットはCPU命令の集まりx86・ARMなどがある
仮想命令セットはCPUに依存しない命令セット
RubyやJVMといった仮想マシンで動作する言語はそれぞれが定義した仮想命令セットで動いている
Wasmの仮想命令セットは ただのバイトコード
00000000: 0061 736d 0100 0000 0107 0160 027f 7f01 .asm.......`....00000010: 7f03 0201 000a 0901 0700 2000 2001 6a0b .......... . .j.00000020: 0a .WATからWasmバイナリを生成
Wasm Runtimeは Ruby VMやJVMのような仮想マシン
Wasm Runtimeはスタックベースの仮想マシン
*.wasmが実行されるまでの流れ
1. *.wasmからRustのデータ構造に落とし込む 2. 1のデータ構造から実行用のデータ構造を生成する 3. Wasm Runtimeのメモリの確保や命令処理などを行う
*.wasmのデータ構造
先頭8バイトがmagic numberとversion情報 残りが各種セクション情報 各種セクションは実行時に必要な情報を持つ8BTNNPEVMFE NBHJDOVNCFS aBTN WFSTJPO TFDUJPODPEF TFDUJPOTJ[FBYY TFDUJPOEBUB TFDUJPODPEF TFDUJPOTJ[FYY TFDUJPOEBUBEYY
例) type_section関数シグネチャ情報code_section 関数の命令群
TFDUJPO5ZQF TFDUJPODPEF TFDUJPOTJ[FB OVNUZQFTC GVODD OVNQBSBNTEG JFG JG OVNSFTVMUTG JTFDUJPO$PEF B TFDUJPODPEF TFDUJPOTJ[F OVNGVODUJPOT GVODCPEZTJ[F MPDBMEFDMDPVOUB MPDBMHFUC MPDBMJOEFYD MPDBMHFUE MPDBMJOEFYFB JBEEGC FOE
例) type_section関数シグネチャ情報code_section 関数の命令群import_section他モジュールからインポートする関数やメモリなどの情報
code section をデコード処理する例TFDUJPO$PEF B TFDUJPODPEF TFDUJPOTJ[F OVNGVODUJPOT GVODCPEZTJ[F MPDBMEFDMDPVOUB MPDBMHFUC MPDBMJOEFYD MPDBMHFUE MPDBMJOEFYFB JBEEGC FOE
実行時データ構造
Store実行時に必要なインスタンス達を持つ 例)関数やメモリのインスタンスRuntimeVMそのものと思ってOKRuntime::stack処理時の値を保持するRuntime::call_stack関数呼び出しのフレームを保持するフレームごとに命令などの情報をもつ
命令処理の実装を説明する前にスタックマシンについて復習
スタックマシン10 + 13 をスタックを使って計算する場合
これをRustで表現する
*OTUSVDUJPO 8BTNͷ໋ྩ܈7BMVF 8BTNͰѻ͑Δ JJɺGͳͲ3VOUJNFTUBDL ॲཧ࣌ͷΛอ࣋3VOUJNFFYFDVUF ໋ྩΛॲཧ͢ΔؔQD ϓϩάϥϜΧϯλ ࣍ͷ໋ྩͷ൪ʢΠϯσοΫεʣ
'SBNF ؔͷݺͼग़͠ͷʹੜ͢Δ ྫ͑ɺ" # ͷΑ͏ͳؔݺͼग़͠ ͕͋Δ߹ɺ" ͷॲཧதʹ# ͷ໋ྩΛ ॲཧ͠ɺͦΕ͕ऴΘͬͨΒ" ʹΔࡍʹ " ͷQDʹ͢ඞཁ͕͋Δ ؔ͝ͱʹQDͱ໋ྩΛ·ͱΊͯ'SBNFͰ ཧ͢Δ͜ͱͰɺݺͼग़͠ݩʹΔͱ͖ ͷॲཧ'SBNFΛΓସ͑Δ͚ͩͰࡁΉ
最終的なFrame
-BCFM JGMPPQͱ͍ͬͨͷ੍ޚߏจΛॲཧ͢ΔͨΊͷใ ྫ͑ελοΫΛר͖ͨ͢ΊͷελοΫϙΠϯλ ͳͲΛ࣋ͭBSJUZ Γͷ MPPQΓΛฦ͢͜ͱ͕Ͱ͖ΔͨΊɺ -BCFMBSJUZΛ࣋ͭMPDBMT ؔͷҾϩʔΧϧมͷͦͷଞɺॾʑͷ࣮ͷৄࡉˣͷهࣄΛࢀর IUUQT[FOOEFWTLBOFIJSBBSUJDMFTSVTUXBTNSVOUJNF
ͪͳΈʹ໋ྩͷҎ্͋Δ ͷͰϚΫϩΛۦ࣮ͯ͠ྔΛ͑ͨ3VTUͷϚΫϩ͜͏͍͏ͱ͖ʹศར
実はWasm Runtimeは 数値の計算とメモリの操作 しかできない
ファイルといったOSのリソースの操作は仕様にない
WebAssembly System Interface通称 WASI
WASIは雑にいうとシステムコールの仕様
POSIX Likeな関数の集まり例)sock_accept(fd, flags) : ソケット新規接続受け入れfd_write(fd, iovec_array): fdにデータを書き込むfd_read(fd, iovec_array) : fdからデータを読み取る
WASIを使えば、ファイルといったOSのリソースを扱えるようになる
つまり...WASIをRuntimeに実装することでファイルIOの処理が可能になる
WASIはpreview 1と2がある
preview 1の関数一覧 https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/docs.md
文字を出力するにはpreview 1のfd_write()を実装する必要がある
fd_write()はWasm Runtimeが持つメモリ上のデータをfdに書き込む
ϝϞϦ͔ΒσʔλΛಡΈऔΔಡΈऔͬͨόΠτྻΛGEʹॻ͖ग़͢
Wasmから見てWASIはwasi_snapshot_preview1というモジュールそのモジュールが持っているfd_write()関数を使うfd_write()を使うとき
ドキュメントを読んでもどう実装したら良いのかさっぱり分からなかったWASIの実装の余談なので↓のdenoのwasiモジュールを見ながらRustで実装した https://deno.land/std/wasi/snapshot_preview1.ts今なら、GoのWASI実装を参考にできるかも? https://github.com/golang/go/blob/master/src/net/fd_wasip1.go
宣伝同人誌「作って理解 Wasm Runtimeのしくみ」を執筆中ですRustで"Hello World"を出力できるWasm RuntimeをRustで実装していく本になります来年のどこかの技術書典などに出せたらと思っています
ありがとうございました