🔄

WebAssemblyとElixirとの間でデータをやり取りする

2023/02/03に公開

Elixirのコード上でWebAssembly(以下、Wasm)を動かしつつ、お互いでデータをやり取りしたかったので、以下のようなものを作ってみました。

https://github.com/kentaro/wasmtube

データのやり取りの仕組み

Elixirのコード内でWebAssemblyを動かすには、Wasmexが使えます。これは、Rustで書かれたWebAssemblyランタイムのWasmtimeを、拡張モジュール経由で呼び出して利用しています。Wasmtubeでも、このWasmexを利用してWasmとのやり取りを実現しています。簡単なラッパーのような感じです。

Wasmインスタンスには、WebAssembly.Memoryというメモリ領域があり、これを経由してデータを書き込むことで、ElixirとWasmとの間でデータをやり取りできます。あんまりよくわかってないので、「WebAssembly と JavaScript との間で自在にデータをやりとりする」などをご参照ください。

この仕組みを使って、JSONをやり取りすることで、リッチなデータのやり取りを簡単に実現してみました。

シンプルなWasmの例(Rust)

以下の通りのRustのコードを用意しました。引数として渡された内容を、そのまま返すだけのコードです。上述のメモリ内に、引数をJSONエンコードされた文字列として書き込んであるので、それを読み出して用いる感じです。Elixir側に対しては、JSONエンコードした文字列のポインタを返します。

https://github.com/kentaro/wasmtube/blob/main/test/wasm_test/src/lib.rs

ElixirからWasmを呼び出す

Elixirからは、Wasmtube.call_function/2でWasm側の関数をコールします。その際、2つ目の引数でMapにより引数を渡す仕様になっています。

Wasmtube.from_file("上記のファイル.wasm")
|> Wasmtube.call_function("echo", %{arg: "Hello, Wasm!"})

#=> %{"arg" => "Hello, Wasm!", "buffer_size" => 1024}

上記のRustのコードは、引数をそのままJSONとして返しているので、Elixir側にもそのまま値が返ってきていますね(buffer_sizeというのは、Elixirで読み取る際のバッファサイズを返すようにしてあります。今のところ、使う分には気にする必要はありません)。

これで、Elixirから引数としてデータを渡してWasm側で処理を実行し、その結果をElixir側に戻すことができるようになりました。ElixirとWasmとでランタイムを分けることで、柔軟な設計ができる可能性が高まってきました。

おわりに

まずはPoC的にやってみたので、Wasm側もRustのコードに関してもメモリ周りがかなり怪しいコードになっていますが、とりあえずJSONでやり取りすることはできそうなことがわかりました。これを用いてちょっとやってみたいことがあるので、今後も改善していきたいと思います。

Discussion