Node.js / Denoで始める手書きWebAssembly
この記事は Deno Advent Calendar 2019 10 日目の記事(大遅刻)です。
最近 WebAssembly(以下、Wasm)の text format (wat) を少しだけ勉強しています。
Wasm を動かす環境として、一番ベーシックなのはブラウザ (Chrome / Firefox など) ですが、気軽に書いて試すにはやはり Terminal 上で完結させたいと思いました。
Terminal 上で Wasm を動かすにあたっての選択肢は、下記の 2 つがあります。
- Node.js (フラグ付き)
- Deno
本記事では、手書きWasmをコンパイルして上記の2つの環境で動かす方法を紹介します。
用意するもの
- Node.js v13
- Deno
-
wabt
- WebAssembly のツールキット
- wat を Wasm に変換する wat2wasm を使います
参考文献
初めに
Node.js / DenoでWebAssemblyを動かすには、
- wasmファイル
- wasmをロードするJSファイル
が必要となります。
また、wasmファイルを作成するには、
- WebAssembly Text Format (以下、wat) で記述してWasmにコンパイルする
- 何かしらの言語からWasmにコンパイルする
などが必要となります。
今回の記事で手書きWebAssemblyと呼んでいるのは、上記のwatのことを指しています。
(Wasmファイルの内容はバイナリなので、特殊な鍛錬を積んだ人以外は手書き出来ないと思います)
watからWasmへのコンパイル
上記の 用意するもの で示したツールはインストール済みとします。
ここでは、watファイルの文法には触れず、コンパイルと実行のみを扱うこととします。
下記の内容のファイルを add.wat
として保存してください。
(module
(func (export "add") (param $lhs i32) (param $rhs i32) (result i32)
local.get $lhs
local.get $rhs
i32.add)
)
内容は、2つの引数 ($lhs, $rhs)
を受け取って、それらを足した結果を返すadd関数の定義となっています。
こちらを wabt に含まれるwat2wasmで変換します。
wat2wasmの使い方は簡単です。
$ wat2wasm add.wat
とすると、add.wasmが出力されます。
これでwasmファイルが得られたので、続いてこれを実行してみます。
wabtのインストールについての補足
詳細は割愛しますが、wabtのインストールはaptやbrewでサクッと!と言う感じではありません。
リポジトリをcloneした上で、CMakeでビルドする必要があります。
README.mdに記載の手順に従えば基本うまく行くはずなのでトライしてみてください。
こちらの記事ではUbuntuの環境をベースにしているのですが、ビルド結果のバイナリは wabt/out/clang/Debug
に格納されていました。
(Clangをまだ入れていない環境だったので、インストールが必要だった気がします)
Wasmをどう実行するか
Node.js / DenoでのWasmの実行方法には2通りあります。
- WebAssembly.instantiate()を使う
- ES ModulesのWebAssembly integrationを使う
1. WebAssembly.instantiate()を使う
こちらの方がスタンダードなやり方で、方法としては事前にロードしたWasmのコードをArrayBufferに格納してWebAssembly.instantiate()関数に渡すというものです。
JavaScript側で事前に確保したメモリや、JavaScript側で定義した関数への参照を持たせた importObject
をセットで渡して初期化することも出来ます。
コードで見た方がより伝わりやすいと思いますので、MDNの例を下記に引用します。
var importObject = {
imports: {
imported_func: function(arg) {
console.log(arg);
}
}
};
fetch('simple.wasm').then(response =>
response.arrayBuffer()
).then(bytes =>
WebAssembly.instantiate(bytes, importObject)
).then(result =>
result.instance.exports.exported_func()
);
MDN - WebAssembly.instantiate()
なお、ChromeやFirefox等のブラウザ上では、より効率的にWasmをロード可能なWebAssembly.instantiateStreaming()関数が存在するのでそちらを利用するのが推奨されています。
2. ES ModulesのWebAssembly integrationを使う
こちらはまだ仕様が確定していないものですが、より手軽なので今回はこちらを使います。
内容としては、Wasm側でexportされた関数やメモリへの参照などを直接ES Modules (以下、ESM) でimport出来ると言うものです。
詳しくは @bellbind さんの Qiita - 2019年のWebAssembly事情 をご覧いただくのが参考になると思います。
今回作成したadd関数を使うには、次のようにします。
import { add } from './add.wasm';
console.log(add(1, 2));
// => 3
非常に簡単に使えることが伝わったかと思います。
この後実際にこのコードを実行してみるので、上記の内容を、add.jsとして保存しておきましょう。
Node.jsでWasmを動かす
Node.jsでは、WebAssemblyはまだフラグ付きでないと実行できません。
また、 .js
ファイルでESMを使うには、package.jsonに設定を追加する必要があります(Node.js 12以前は.mjsでないと動きません)。
それでは、まず下記の内容を含んだpackage.jsonを用意しましょう。
{
"type": "module"
}
今回は、この内容しか含まないpackage.jsonを用意してしまっても大丈夫です。
続いて、Node.jsをフラグ付きで起動します。
すると、下記のとおりに結果の 3
が表示されるはずです。
$ node --experimental-wasm-modules add.js
(node:771) ExperimentalWarning: The ESM module loader is experimental.
(node:771) ExperimentalWarning: Importing Web Assembly modules is an experimental feature. This feature could change at any time
3
もし起動しないようであれば、Node.jsのバージョンを確認してください。
DenoでWasmを動かす
DenoでのWasmの実行はもっと簡単です。
Denoは初めからESMにも、Wasmにもフラグ無しで対応しているので下記の内容を実行するだけで完了です。
$ deno add.js
3
とてもお手軽ですよね?
おわりに
この記事では、Node.js / DenoとESMでWasmを動かす方法を紹介しました。
最後に書いた通り、DenoでのWasmのロード・実行はとても簡単なので、Text Formatの実行環境として練習に適していると思います。
この冬休み、ぜひDenoとWebAssemblyで遊んでみてください!