WebAssemblyとElixirとの間でデータをやり取りする
Elixirのコード上でWebAssembly(以下、Wasm)を動かしつつ、お互いでデータをやり取りしたかったので、以下のようなものを作ってみました。
データのやり取りの仕組み
Elixirのコード内でWebAssemblyを動かすには、Wasmexが使えます。これは、Rustで書かれたWebAssemblyランタイムのWasmtimeを、拡張モジュール経由で呼び出して利用しています。Wasmtubeでも、このWasmexを利用してWasmとのやり取りを実現しています。簡単なラッパーのような感じです。
Wasmインスタンスには、WebAssembly.Memoryというメモリ領域があり、これを経由してデータを書き込むことで、ElixirとWasmとの間でデータをやり取りできます。あんまりよくわかってないので、「WebAssembly と JavaScript との間で自在にデータをやりとりする」などをご参照ください。
この仕組みを使って、JSONをやり取りすることで、リッチなデータのやり取りを簡単に実現してみました。
シンプルなWasmの例(Rust)
以下の通りのRustのコードを用意しました。引数として渡された内容を、そのまま返すだけのコードです。上述のメモリ内に、引数をJSONエンコードされた文字列として書き込んであるので、それを読み出して用いる感じです。Elixir側に対しては、JSONエンコードした文字列のポインタを返します。
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