ZigをWasmにコンパイルしてNode.jsから呼び出す
はじめに
SHIFT DAAE部 shinagawa です。 最近、JSのランタイムである「Bun」が話題になっているのを目にしました。 ちょっと調べてみると、スピードやパフォーマンスについて注目されているようでしたが、 特に気になった点は、実装にZigという言語が利用されていることでした。
※ Zigについて
他の言語と比べて情報量がそれほど多くは無かったのですが、実行バイナリを小さくできる点と、 ビルドの標準のオプションでWasmが吐ける点が素晴らしいと思ったので触ってみることにしました。
環境
この検証では以下の環境を利用しています。
Windows10 / WSL2
Zig 0.9.1
Wasmer 2.3.0
Node.js 18.2.0
環境構築
まずはWasmを生成するのに必要なコンパイラ基盤であるLLVMとZig本体をhomebrewを使ってインストールします。
$ brew install llvm zig
インストールが完了するとコマンドが利用できる状態になります。
$ zig version
0.9.1
Zigプロジェクト作成
ワークスペースを作成します。
$ mkdir hello-world
$ cd hello-world
$ zig init-exe
init-exeでプロジェクトを初期化すると、次のようにセットアップされます。
$ tree .
.
├── build.zig
└── src
└── main.zig
src/main.zigを書き換えていきます。まずは簡単にHello Worldできるようにします。
// src/main.zig
const std = @import("std");
pub fn main() anyerror!void {
const stdout = std.io.getStdOut().writer();
try stdout.print("Hello, World!\n", .{});
}
zig runで実行します。Hello World!と出てくれば成功です。
$ zig run src/main.zig
Hello, World!
Wasmerで実行する
生成したWasmの動作確認を行うために、Wasmerを以下のコマンドでインストールします。
※環境によっては、依存関係の調整が必要な可能性があります。
$ curl https://get.wasmer.io -sSfL | sh
実行権限を付与し、パスを通しておきます。
$ chmod +x ~/.wasmer/bin/wasmer
$ export PATH="~/.wasmer/bin:$PATH"
これでWasmerが利用できる状態となりました。
$ wasmer --version
wasmer 2.3.0
次にWasmとしてコンパイルを行います。
$ zig build-exe -O ReleaseSmall -target wasm32-wasi src/main.zig
実行すると以下の様にmain.wasmが吐き出されます。
$ tree
.
├── build.zig
├── main.wasm
└── src
└── main.zig
wasmerで実行します。Hello World!と出てくれば成功です。
$ wasmer main.wasm
Hello, World!
Node.jsから呼び出す
最後に、タイトルにある通りNode.jsから呼び出してみます。
まずは JSで呼び出すための関数をsrc/main.zigに定義します。
// src/main.zig
const std = @import("std");
pub fn main() anyerror!void {
const stdout = std.io.getStdOut().writer();
try stdout.print("Hello, World!\n", .{});
}
export fn test1() usize {
return 1;
}
export fn test2(num1: usize, num2: usize) usize {
return num1 +| num2;
}
呼び出す側を call_wasm.jsとして作成します。
// call_wasm.js
const fs = require("fs");
const content = fs.readFileSync("./main.wasm");
WebAssembly.compile(content)
.then((module) => {
const lib = new WebAssembly.Instance(module, {
env: {
memoryBase: 0,
tableBase: 0,
memory: new WebAssembly.Memory({ initial: 256 }),
table: new WebAssembly.Table({ initial: 0, element: "anyfunc" }),
},
}).exports;
// test1
console.log(lib.test1()); // 1
// test2
console.log(lib.test2(128, 128)); // 256
})
.catch((e) => {
console.error(e);
});
関数 test1、test2 を外から呼び出せるように--exportオプションで関数を指定してコンパイルします。
$ zig build-lib \
-O ReleaseSmall \
-target wasm32-wasi \
-dynamic \
--export=test1 \
--export=test2 \
src/main.zig
2つの関数が呼び出されていることが確認できれば成功です。
$ node call_wasm.js
1
256
所感
Zigについてソースを見ていく中で、Cに近い様な印象を受けました。
エディタの補完機能や構文チェックを行うプラグインは見当たらず、パッケージマネージャーもなさそうなので、 開発言語として利用していくには、これからに期待という感じでした。
補足1:「wasmer: error while loading shared libraries: libtinfo.so.5 ...」が出現する場合
Wasmerの導入・実行にて
wasmer: error while loading shared libraries: libtinfo.so.5: cannot open shared object file: No such file or directory
が出力される場合、libtinfo5をインストールすることで解消することが出来ました。
Ubunutの場合次のコマンドでインストールします。
$ apt install libtinfo5
補足2: Zig の build オプションについて
文章中に出てきたZigのbuildの際のオプションについて触れておきます。
実行可能な出力形式
zig build-exe 、zig build-lib は、それぞれ実行可能ファイル、ライブラリを出力するために使用できます。
クロスコンパイルオプション
-targetでプラットフォームに合わせたコンパイルを行うことができます。
具体的には次のようなパラメータを利用します。
wasm32
x86_64
i386
参考にしたサイト
\もっと身近にもっとリアルに!DAAE公式Twitter/
お問合せはお気軽に
https://service.shiftinc.jp/contact/
SHIFTについて(コーポレートサイト)
https://www.shiftinc.jp/
SHIFTのサービスについて(サービスサイト)
https://service.shiftinc.jp/
SHIFTの導入事例
https://service.shiftinc.jp/case/
お役立ち資料はこちら
https://service.shiftinc.jp/resources/
SHIFTの採用情報はこちら
https://recruit.shiftinc.jp/career/