Zigで作ったWasmからJavaScriptの関数を呼び出す
Zigで作ったWebAssembly(Wasm)からJavaScriptの関数を呼び出す方法を説明します。
Denoを使っていますが、ブラウザもだいたい同じです。
今回の内容はとても簡単ですが、JavaScriptからZigの関数を呼び出す方法が分かっていることが前提となりますので、まずはこちらの記事を参考にされてください。
Zigファイル
今回使うZigファイルはこれです。
// JavaScript側で実装する関数
extern fn js_output(x: f64) void;
// JavaScriptから呼び出す関数
export fn output(x: f64) void {
js_output(x);
}
js_output
関数は後からJavaScript側で定義する関数です。この時点ではextern
を付けることで実装を外部に任せるという意味になります。
output
関数はJavaScriptから呼び出す関数です。Zig側では何もせずにjs_output
関数を呼び出しているだけです。
ZigをWasmにコンパイル
コンパイル方法は以前と同じで、特別にオプションが必要なわけではありません。(Zig v0.12.0の場合です。Zigのバージョンで変わってきます)
zig build-exe output.zig -target wasm32-freestanding -fno-entry -rdynamic --import-memory -O ReleaseSmall
JavaScriptからWasmに関数を渡す
この時点でWasmファイルを逆アセンブルしてみるとこうなっています。
(module
(type (;0;) (func (param f64)))
(import "env" "memory" (memory (;0;) 16))
(import "env" "js_output" (func (;0;) (type 0)))
(func (;1;) (type 0) (param f64)
local.get 0
call 0)
(global (;0;) (mut i32) (i32.const 1048576))
(export "output" (func 1)))
4行目を見るとenv
のjs_output
をインポートしていることが分かります。つまり、env
のjs_output
に関数を指定あげれば良いことになります。
// Wasmから呼び出す関数を実装する
function js_output(x: number):void {
console.log(`Output: ${x}`)
}
// メモリの確保
const memory = new WebAssembly.Memory({initial:20, maximum:100})
// Wasmモジュールのインポート
const module = new WebAssembly.Module(Deno.readFileSync('output.wasm'))
// Wasmインスタンスを作る
const instance = new WebAssembly.Instance(module, { env: { memory, js_output } })
// 関数のシンボルを取り出して型を付ける
const output = instance.exports.output as (x: number) => void
// 関数を実行する
output(7.5)
このプログラムでは最初にWasmから呼び出すjs_output
関数を実装しています。
あとは、ここでWasmインスタンスを作るときにenv
のjs_output
に関数を渡しています。
const instance = new WebAssembly.Instance(module, { env: { memory, js_output } })
JavaScriptを実行する
作ったTSファイルを実行します。
$ deno run --allow-read output.ts
Output: 7.5
7.5
という値をWasmのoutput
関数に渡して、Wasmから呼び出されたjs_output
関数が実行されて、console.logで出力されました。
活用
今回はconsole.logを実行しましたが、この方法を使えば、例えばWasmからDOM操作や通信も可能となります。
しかし、呼び出せるJavaScript関数に渡す引数と戻り値はNumber型のみになります。それ以外のデータを受け渡したい場合はこちらを参考にしてください。
Discussion