ローファイ日記

出てくるコード片、ぼくが書いたものは断りがない場合 MIT License としています http://udzura.mit-license.org/

mruby/edge進捗: RBSを使ってwasmのexport対象関数を指定するようにした

mruby/edgeの進捗ブログです。

mruby/edgeとは、という話はここです:

udzura.hatenablog.jp

もっと聞きたい人は5月に沖縄にいくといいらしい。

rubykaigi.org

wasmバイナリの中身を軽く話す話

wasmバイナリにどんなセクションがあるか、とか、使い方とは、みたいな話は以下の本をサクッと読んでください。ちなみに Component Model以前の内容ではありますが、今ある日本語の本の中で一番いい感じにカバーしてると思う。

www.oreilly.co.jp

その上でwasmバイナリは関数をexportできます(普通のshared objectみたい)。 wasm-objdump というコマンド(from wabt)を使えます。

$ wasm-objdump -x -j Export hello.wasm

hello.wasm:     file format wasm 0x1
module name: <mywasm.wasm>

Section Details:

Export[2]:
 - memory[0] -> "memory"
 - func[395] <hello.command_export> -> "hello"

この "hello" を呼び出すには例えば wasmedge を使って以下のようにします。

$ wasmedge --reactor hello.wasm hello
My own wasm
Nil

ブラウザでのJavaScriptでもこのexportという概念は大事で、以下のようなコードで呼び出す使い方が基本かなと思います。 ref MDN

WebAssembly.instantiateStreaming(fetch("hello.wasm"), importObject).then(
  (obj) => obj.instance.exports.hello(),
);

また、例えば proxy-wasm のような用途では、特定の関数を export したwasmバイナリ(ABIに沿ったバイナリ)を作って読み込ませる運用を想定しています。

なのでwasmをコンパイルするにあたって、どの関数をどういう定義でexportするかというのは非常に大事です。

mruby/edgeではこうしました

ということで、mruby/edgeではこういうRubyスクリプト fib.rb があるときに

def fib(n)
  if n < 2
    return 1
  else
    return fib(n-1) + fib(n-2)
  end
end

fib というトップレベルのメソッドをそのまま関数としてexportしたいなと思いました。

どういう関数があるねというのを検知させてもいいんですが、逆に無闇に複雑な実装になるかなと思い、以下のような内容の fib.export.rbs を用意させることにしました。

def fib: (Integer) -> Integer

fib.rbfib.export.rbs が同じディレクトリにある時、 mec コマンド v0.2.0 を叩くと:

$ cargo install --version 0.2.0 mec
$ mec fib.rb
...
[ok] wasm file is generated: fib.wasm
$ wasm-objdump -x -j Export fib.wasm

fib.wasm:       file format wasm 0x1
module name: <mywasm.wasm>

Section Details:

Export[3]:
 - memory[0] -> "memory"
 - func[397] <fib.command_export> -> "fib"

と言いつつ、今までは --fnname で指定していました。進歩した点としてはシグネチャもこれで指定可能になっています。FunctionとTypeセクションを見比べることで、 fib がちゃんと (i32) -> i32 の関数として定義されていることがわかる。

$ wasm-objdump -x -j Function fib.wasm | grep 'fib'      
fib.wasm:       file format wasm 0x1
 - func[16] sig=2 <fib>
 - func[397] sig=2 <fib.command_export>

$ wasm-objdump -x -j Type fib.wasm | grep 'type\[2\]'
 - type[2] (i32) -> i32

呼び出すとこうなります。もちろんブラウザでも使えます、はず。

$ wasmedge --reactor fib.wasm fib 12               
233

ちなみに今回の対応で複数の関数をexportすることも可能になりました。

def fib: (Integer) -> Integer
def hello: () -> void
$ wasm-objdump -x -j Export fib.wasm

fib.wasm:       file format wasm 0x1
module name: <mywasm.wasm>

Section Details:

Export[3]:
 - memory[0] -> "memory"
 - func[397] <fib.command_export> -> "fib"
 - func[398] <hello.command_export> -> "hello"

まだちゃんと動かない(例えばいわゆる文字列の扱いをどうするかとか)点もありますが、そこはwasmに詳しくなって頑張る...。

今回の対応は、 RBS 勢的にもやや興味深い使い方なのかも? と思ったりしています(率直な意見を沖縄などで聞きたい)。


肝心の mruby/edgeコアは相変わらず基本的な命令もまだまだですが、とは言えやるだけのことなので...粛々やる...