🐷

Zigで作ったWasmからJavaScriptの関数を呼び出す

2024/04/28に公開

Zigで作ったWebAssembly(Wasm)からJavaScriptの関数を呼び出す方法を説明します。
Denoを使っていますが、ブラウザもだいたい同じです。

今回の内容はとても簡単ですが、JavaScriptからZigの関数を呼び出す方法が分かっていることが前提となりますので、まずはこちらの記事を参考にされてください。

https://zenn.dev/itte/articles/dc7471ffc2f76f

Zigファイル

今回使うZigファイルはこれです。

output.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行目を見るとenvjs_outputをインポートしていることが分かります。つまり、envjs_outputに関数を指定あげれば良いことになります。

output.ts
// 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インスタンスを作るときにenvjs_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型のみになります。それ以外のデータを受け渡したい場合はこちらを参考にしてください。

https://zenn.dev/itte/articles/57021ace128fff

Discussion