ぱたへね

はてなダイアリーはrustの色分けができないのでこっちに来た

WebAssembly.instantiateStreamingの第二引数

WebAssemblyがなかなか動かず、一つ分かったことがあるのでメモ書き。

emccが出力するファイル

www.manning.com

WebAssembly In ActionよりEmscriptenが出力するファイルは、以下の3つパターンのどれかになります。

  • WebAssembly module, JavaScript plumbing file, HTML template
  • WebAssembly module, JavaScript plumbing file
  • WebAssembly module

一番最後のWebAssembly Moduleだけを出力した場合は、実行時にdynamic linkingが行われます。dynamic linkingを実現するためには、side moduleを作る事必要があります。

side moduleを作るには、emccの-s SIDE_MODULE = 2 のオプションを指定し、Cの標準ライブラリとJavaScript JavaScript plumbing fileを使わない事を指示します。関数名をExportするために、emccに -s EXPORTED_FUNCTIONS=['_ai'] のようなオプションも必要です。

ここで、JavaScriptが良く分からないのでミニマムな環境を作ろうと思い、一番下の方法でやってみたらずいぶん長い間はまってしまいました。

WebAssembly.instantiateStreaming

本を見たり、他のサイトを見ているとこんな感じで、wasmファイルを呼び出していると思います。

const importObject = {
  env: {
    __memory_base: 0,
  }
};


WebAssembly.instantiateStreaming(fetch("ai.wasm"), importObject).then(result =>  {
    const value = result.instance.exports._ai();
    console.log("ai.wasm returns" + value)
  }).catch (err => {
    console.log(err)
  }
  )

これを実行すると、instantiateStreamingの第二引数(importObject)でエラーになります。

例えばこんなエラー

TypeError: WebAssembly.instantiate(): Import #3 module="GOT.mem" error: module is not an object or function

検索してみるとimportObject は省略可能と書いてあったり、env.tableが要るんだよとかいろんな情報が出てきます。

例えばこういうのです。

const importObj = {
  module: {},
  env: {
    memory: new WebAssembly.Memory({ initial: 256 }),
    table: new WebAssembly.Table({ initial: 0, element: 'anyfunc' })
  }
};

いろいろためしても全く動かないし、そもそもGOT.memって何だろうと悩み続けてようやく分かりました。

instantiateStreamingの二つめの引数は、一つ目の引数でfetchしたwasmファイルが動作するのに必要な環境を渡す必要があります。 何が正解かというのは、wasmファイルの作り方であったり、その内容によって変ってきます。

ちなみに、GOT.memでエラーが出ていたwasmファイルをwasm2watで見てみると、確かにmoduleの中で何か使おうとしています。

natu@Honoka:~/q/myproj/wasm-qumico/app_client/public$ wasm2wat ai.wasm
    (module
  (type (;0;) (func))
  (type (;1;) (func (result i32)))
  (import "env" "__stack_pointer" (global (;0;) (mut i32)))
  (import "env" "__memory_base" (global (;1;) i32))
  (import "env" "__table_base" (global (;2;) i32))
  (import "GOT.mem" "x" (global (;3;) (mut i32)))
  (import "env" "memory" (memory (;0;) 0))
  (import "env" "__indirect_function_table" (table (;0;) 0 funcref))
  (func (;0;) (type 0)
    call 1)

で、作ったwasmファイルに適切なimportObjectを作るのが大変なので、emccはwasmファイルと同時にJavaScript plumbing fileを生成してくれます。

実際に生成されたJavaScriptを見てみると、単にimportObjectを作るだけでなく、初期化など複雑な処理もやっていました。

というわけで振り出しに戻ります。