Wasm Text Formatを利用してWebAssemblyをCLI上でビルド・実行する

2020.12.06

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

概要

WebAssembly(Wasm)はブラウザ上で実行可能なバイナリ形式のプログラムを指しますが、V8上で動作することからnode.jsでの実行も可能です。本記事ではnode.jsを利用してWasm Text FormatからWasmバイナリをビルドし実行します。

Wasm Text Format

Wasm Text Format(Wat)はWasmと相互変換可能な中間コードです。S式で記述されていることからテキストエディタやブラウザの開発ツールなどで編集することができます。

Wasmの開発では、通常C++やRustなど別言語からビルドすることが主流ですが、単純なプログラムであればWatを直接記述し、そこからビルドすることが可能です。多言語やそのエコシステムを学習する必要がないため、とりあえずWasmを試してみたい場合に有効な選択肢の一つです。また、Wasmの低レベルでの動作の検証に利用することもできます。

手順

ここでは、WatからWasmをビルドし、実行するまでの手順をまとめます。

Wat-Wasm間の変換はwabtと呼ばれるツールから実行できますが、別途ツールのビルドやセットアップが必要です。今回はnode.jsのライブラリとしてwabtの機能をポートしているwabt.jsを利用し、npm scriptからビルドや実行を行えるようにします。

環境構築

まずは wabt.js をインストールします。

npm install wabt

ビルドしたいWatファイルをプロジェクト内に作成します。今回は main.wat という名前で作成します。

(module
  (func (result i32)
    (i32.const 42)
  )
  (export "helloWorld" (func 0))
)

このWatでは42を返すhelloWorldという関数を定義しています。

ビルド

次に、WatからWasmへの変換を行うbuild.jsを作成します。

const { readFileSync, writeFileSync } = require("fs");
const wabt = require("wabt");

const inputWat = "main.wat";
const outputWasm = "main.wasm";

wabt().then(wabt => {
  const wasmModule = wabt.parseWat(inputWat, readFileSync(inputWat, "utf8"));
  const { buffer } = wasmModule.toBinary({});
  writeFileSync(outputWasm, Buffer.from(buffer));    
});

build.jsを実行するとmain.wasmが生成されます。

node build.js

package.jsonに上記コマンドを追加すれば、npm scriptとしてwasmのビルドが実行できるようになります。

{
  ...
  "scripts": {
    "build": "node build.js"
  },
  "dependencies": {
    ...
  }
}

実行

つづいてmain.wasmを実行するためのスクリプト(index.js)を以下のように作成します。

const { readFileSync } = require("fs");

const run = async () => {
  const buffer = readFileSync("main.wasm");
  const module = await WebAssembly.compile(buffer);
  const instance = await WebAssembly.instantiate(module);
  console.log(instance.exports.helloWorld());
}

run();

生成したWasmはWebAssembly Javascript APIを利用することでロードや実行を行うことができます。上記の例ではWasmモジュールをロードした後、モジュールからhelloWorld()を呼び出し、コンソールに結果を出力します。

実行すると42が出力されます。

$ node index.js
42

参考