Deno のファーストクラス Wasm サポートを使う
はじめに
#11月の Deno 2.1 リリースで First-class Wasm support が入りました。
Deno 2.1: Wasm Imports and other enhancements
従来の Deno では Web 標準の API を使って Wasm のロード・実行が可能でしたが、2.1以降は通常のモジュール同様、import で使用できるようになりました。
Wasm のロードと実行:従来の方法
#以下は、ローカルに配置した add.wasm という WebAssembly をインスタンス化して公開された関数(add)を使用する例です。
const wasmInstance = await WebAssembly.instantiateStreaming(
fetch(new URL("./add.wasm", import.meta.url)));
const { add } = wasmInstance.instance.exports as { add: (a: number, b: number) => number };
console.log(add(1, 2));
(TypeScript の型エラーが出ないようにしているのもありますが、)かなり長ったらしいコードになっています。まず WebAssembly API でインスタンスし、インスタンスからエクスポートされた関数(ここでは add) を取得して利用できるようにしています。
実行は、--allow-read パーミッションを使用します[1]。
$ deno --allow-read main.ts
3
上記の add.wasm は以下のような wat ファイルを変換したものです。
(module
(func (export "add") (param i32 i32) (result i32)
local.get 0
local.get 1
i32.add))
WABT(WebAssembly Binary Toolkit) をインストールして wat2wasm コマンドを使えば、この wat ファイルを wasm 形式に変換可能です。
wat2wasm のデモサイトを使えば、ローカルに WABT を導入しなくても 変換済みの wasm ファイルをダウンロードできます。
Wasm のロードと実行に関する Web 標準 API については以下のドキュメントを参照してください。
WebAssembly コードの読み込みと実行 - WebAssembly | MDN
Deno での Web 標準 API による Wasm の利用については以下でも紹介しています。かなり古い記事ですが。
Wasm のロードと実行:import
#Deno 2.1以降は import で Wasm をロード・実行できるようになりました。
import { add } from "./add.wasm";
console.log(add(1, 2));
とてもシンプルになりました。実行もシンプルです。
deno main.ts
Deno は Wasm のモジュールに対しても型チェックを行います。
Web Worker から使ってみる。
#以前の連載記事では、Web Worker で Wasm のコードを分散実行させる例を紹介していました。当時は Wasm をメインプロセスで事前コンパイルして Worker に渡し、Worker 側でインスタンス化して実行するという方法を取っていました[2]。現在は Worker のコードで直接 import できるので楽になりました。
ワーカー側のコード。add モジュールを使って、メッセージから取り出したパラメータにより加算処理を行い、結果を送信しています。
import { add } from "./add.wasm";
self.onmessage = e => {
const result = add(e.data.a, e.data.b); // add 実行
postMessage(self.name + ": " + result); // ワーカー名と結果を送信
};
メインのコード。ワーカーを複数生成し、ワーカーからのメッセージ受信時の処理を書いているだけです。
const worker1 = createWorker("worker1");
const worker2 = createWorker("worker2");
handleWorkerMessage(worker1); // worker1 のメッセージ受信時の処理を登録
handleWorkerMessage(worker2); // worker2 のメッセージ受信時の処理を登録
worker1.postMessage({ a: 1, b: 2 }); // worker1 にメッセージ送信
worker2.postMessage({ a: 3, b: 4 }); // worker2 にメッセージ送信
function createWorker(name: string) {
const worker = new Worker(new URL("./worker.ts", import.meta.url).href, {
type: "module",
name: name
});
return worker;
}
function handleWorkerMessage(worker: Worker) {
worker.onmessage = e => {
console.log(e.data);
};
}
実行結果。各 Worker から Wasm を使った処理結果を取得できています。
$ deno --allow-read main.ts
worker1: 3
worker2: 7
さいごに
#以上、Deno のファーストクラス Wasm サポートを軽く試しました。Wasm が Deno のモジュールグラフに取り込まれ、Deno による分析・キャッシュが行われるため、高速に利用できるようになっているようです。
ファーストクラスサポートなので当然ですが、グルーコードっぽいところが皆無です。Wasm を使う障壁がグッと下がった感じですね。