(2023-06-28追記: 本記事の解決編を書きました)
TL;DR
- WebAssembly(Wasm)フォーマットは文字列リテラルなどのデータを保持するために、
data
セクションという領域を持つことができる -
data
セクションのうち、passive
モードのものはプログラム実行時にmemory.init
命令でメモリにロードできる - Rust 1 には LLVM の任意の命令を関数として使用する機能 2 がある
- LLVM の
wasm
系バックエンドには、 Wasm のmemory
系の命令にそのまま変換される命令がある - Wasm の
memory.init
にそのまま変換されるllvm.wasm.memory.init
という命令が LLVM IR に存在したが、こっそり消されてしまった - 現状 Rust から
memory.init
命令を直接叩く手段はないっぽい?
背景
Wasmファイルの data
セクション内のデータを後から書き換えたいようなケースを考えます。
Rust で生成したWasmファイルの場合3、active
モードの data
セクションが一つ以上あり、それが実行前にロードされるようになっています。そのため、この data
セクションを書き換えてやればいいのではと考えますが、これはうまくいきません。
なぜなら、この data
セクションがメモリにロードされたすぐ後ろの領域を LLVM がリンクした関数4が使用しており、この data
セクションの長さが変わってしまうと、この後ろの領域に触ってしまい、実行時に Wasm ファイルがエラー終了するようになってしまうからです。
解決方法
そこで、あらかじめ Wasm ファイルに passive
モードの data
セクションを追加しておき、実行中にこの data
セクションをメモリにロードするようにします。
passive
モードの data
セクションをメモリにロードするためには、
スタックにロード先のメモリアドレス、ロードするデータの data
セクションでの開始位置、データ長をスタックに積んだ後、 memory.init
命令を実行する必要があります。
Rust で Wasm をターゲットにする場合、Wasm 固有の命令の多くは core::arch::wasm32
内の関数を通じて使えるようになっていますが、 memory.init
命令はこの中に含まれません。
そこで、Rustにある5 link_llvm_intrinsics
という feature を使用することを考えます。この feature は、LLVM IR の命令を Rust から関数として呼び出せるようになるものです6。
新たな問題
ネットを検索すると、LLVM IR には Wasm の memory.init
命令に変換される、 llvm.wasm.memory.init
という命令があるらしいという資料が見つかりました。
そこで、上記 link_llvm_intrinsics
を使用して、Rust コード上にこの llvm.wasm.memory.init
命令を直接記述することを試みましたが、なぜかうまくいきませんでした。
さらに調査した結果、このようなコミット を発見しました。その説明に曰く、
Also removes unused init/drop intrinsics rather than trying to make them work for 64-bit.
どうやら LLVM の Wasm64 対応時に、使用されていないし対応が面倒だからと、命令ごと消されてしまったようです7。
終わりに
どうやら Rust (などの Wasm 生成のバックエンドとして LLVM を使用している処理系)から memory.init
命令を叩くことはできないようです。
この回避策としては、memory.init
命令を使用したい箇所をダミー関数として括りだしておき、後からこのダミー関数に正しい中身を直接注入するのが手っ取り早そうです。