🪚

ZigでWASM

2024/12/01に公開

本記事は

システムアイ アドベントカレンダー1日目の記事です。

すみません、1日目からギリギリになっています。

ZigでWASMをビルドする

Zig言語は、「堅牢、最適、および再利用可能なソフトウェアをメンテナンスするための汎用プログラミング言語およびツールチェイン」です。(公式より引用)

最近では「Bun」を開発するために使われたりしています。

ZigはLLVMを内包しており、C/C++のコンパイルもできます。

LLVMの機能により、WASMを出力もできます。

そこで、簡単なサンプルをもちいてWASMをビルドし、JavaScriptから利用する、というのを試してみよう、としました。

tl;dr

  • ZigでWASMをビルドしてみました

さくっと検索した結果

$ mkdir zig-wasm-sample
$ cd zig-wasm-sample
$ zig init
info: created build.zig
info: created build.zig.zon
info: created src/main.zig
info: created src/root.zig
info: see `zig build --help` for a menu of options

main.zigはmain関数があるファイルになりますが、今回は置いておきます。

root.zigの内容は、以下の通り。

const std = @import("std");
const testing = std.testing;

export fn add(a: i32, b: i32) i32 {
    return a + b;
}

test "basic add functionality" {
    try testing.expect(add(3, 7) == 10);
}

簡単な足し算をする関数と、それのテストコードが生成されます。

こいつをWASMにビルドしたい。

検索した結果「DenoからZigで作ったWasmを使う」という記事が見つかりました。

この記事の段階でZigのバージョンは0.12.0。一方2024/12月現在の最新は0.13.0。当該記事にもありますが、Zigはまだメジャーバージョンに至っておらず、仕様がバージョンごとにコロコロ変わります。Bunはよくこんな中で開発できているよな。。。

ひとまず、ビルドを試します。

$ zig build-exe src/root.zig -target wasm32-freestanding -fno-entry -rdynamic --import-memory -O ReleaseSmall
$ ls
build.zig  build.zig.zon  root.wasm  root.wasm.o  src

wasmファイルができました。

これをJavaScriptから読み込んでみます。ひとまずnode.jsで実行してみましょう。

先程の記事を参考に、index.jsを作ります。

const fs = require('node:fs')
const memory = new WebAssembly.Memory({initial:20, maximum:100})
const wasm_module = new WebAssembly.Module(fs.readFileSync('root.wasm'))
const instance = new WebAssembly.Instance(wasm_module, { env: { memory } })
const add = instance.exports.add
const result = add(3, 7)
console.log(result)

実行してみましょう。

$ node index.js
10
# bunでもやってみる
$ bun run index.js
10

簡単にできましたね。

とはいえこれだけでは先行記事を追いかけただけに過ぎませんので、なにか新規性を出したい。

ビルドスクリプトをちゃんと書いてみよう

build.zigをちゃんと書くと、zig build だけでビルドできるようになります。

試しに書いてみました。

const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{ .default_target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding } });
    const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .Debug });

    const exe = b.addExecutable(.{
        .name = "zig-wasm-sample",
        .root_source_file = b.path("src/root.zig"),
        .target = target,
        .optimize = optimize,
    });

    b.installArtifact(exe);
}

targetは指定することができましたが、-fno-entry -rdynamic --import-memoryあたりのオプションを指定することはできない模様。

ZigはZig言語そのもので作られているので、VSCodeであればF12キーを押せば定義にジャンプできます。

ExecutableOptionsや、addExecutableの定義を見ると、どんなオプションを定義できるかがわかります。

src/main.zigで、zigコマンドに渡すサブコマンドの分岐がありました。

buildとbuild-exeではだいぶ雰囲気ちがうな。。。

くらいで

すみません、時間切れっぽいです。。。

Zigはバージョンごとに動作がよく変わる一方、ソースを追いかけやすいので、Zig自体がどう動くのかを把握しやすくなっています。

みなさんももっとZigを使って流行らせませんか?

Discussion