7
2

More than 1 year has passed since last update.

Rust から WebAssembly の memory.init 命令を使いたかった話

Last updated at Posted at 2022-12-30

(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ファイルの場合3active モードの 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 命令を使用したい箇所をダミー関数として括りだしておき、後からこのダミー関数に正しい中身を直接注入するのが手っ取り早そうです。

  1. 厳密にはRustのデファクトスタンダード実装のrustc

  2. https://github.com/rust-lang/rust/issues/29602

  3. Rust に限らず、LLVM の wasm 系ターゲットを使用している処理系ならすべて同じだと思います

  4. 具体的にはメモリ確保系の関数が使用しているようです

  5. おそらく stable にはありません。もしかしたら nightly 限定かも

  6. core::arch::wasm32 の各関数も、実装上はこの仕組みを使用しているようです

  7. コミットメッセージに "Added 64-bit memory.grow/size/copy/fill" としか書いてないのに、別の命令をしれっと削除してるのはなんでなんですかね…

7
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
2