Deno のファーストクラス Wasm サポートを使う

| 4 min read
Author: masahiro-kondo masahiro-kondoの画像

はじめに

#

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)を使用する例です。

main.ts
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 の作成方法

上記の add.wasm は以下のような wat ファイルを変換したものです。

add.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 ファイルをダウンロードできます。

@wat2wasm demo

Information

Wasm のロードと実行に関する Web 標準 API については以下のドキュメントを参照してください。

WebAssembly コードの読み込みと実行 - WebAssembly | MDN

Deno での Web 標準 API による Wasm の利用については以下でも紹介しています。かなり古い記事ですが。

Deno を始める - 第5回 (WebAssembly の利用)

Wasm のロードと実行:import

#

Deno 2.1以降は import で Wasm をロード・実行できるようになりました。

main.ts
import { add } from "./add.wasm";

console.log(add(1, 2));

とてもシンプルになりました。実行もシンプルです。

deno main.ts
Information

Deno は Wasm のモジュールに対しても型チェックを行います。

Web Worker から使ってみる。

#

以前の連載記事では、Web Worker で Wasm のコードを分散実行させる例を紹介していました。当時は Wasm をメインプロセスで事前コンパイルして Worker に渡し、Worker 側でインスタンス化して実行するという方法を取っていました[2]。現在は Worker のコードで直接 import できるので楽になりました。

ワーカー側のコード。add モジュールを使って、メッセージから取り出したパラメータにより加算処理を行い、結果を送信しています。

worker.ts
import { add } from "./add.wasm";

self.onmessage = e => {
  const result = add(e.data.a, e.data.b); // add 実行
  postMessage(self.name + ": " + result); // ワーカー名と結果を送信
};

メインのコード。ワーカーを複数生成し、ワーカーからのメッセージ受信時の処理を書いているだけです。

main.ts
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 を使う障壁がグッと下がった感じですね。


  1. Deno 1系の最終リリース 1.46 以降 deno run サブコマンドは省略可能になりました。 ↩︎

  2. ワーカー毎にコンパイルが走るのを止めたかったので。 ↩︎

豆蔵では共に高め合う仲間を募集しています!

recruit

具体的な採用情報はこちらからご覧いただけます。