Deno 用 WASM を import だけで使えるようにできた

先日書いた Deno 向け WASM を上手いとこと動かせないという話を書きました。
Deno Manual - WebAssembly support に書いてある対応を行ってみます。

参考

結局何をするのか?

「バイナリを uint8 の配列にしてぶちまけろ」ということらしいです。

考察

wasm-bindgen を使って deno 向けのコンパイル?ビルド?をすると、次のようなコードが生成されます。

deno 向けビルドのwasm読み込み処理
1
2
3
4
5
6
7
8
9
const file = new URL(import.meta.url).pathname;
const wasmFile =
file.substring(
0,
file.lastIndexOf(Deno.build.os === "windows" ? "\\" : "/") + 1
) + "wasm_test_bg.wasm";
const wasmModule = new WebAssembly.Module(Deno.readFileSync(wasmFile));
const wasmInstance = new WebAssembly.Instance(wasmModule, imports);
const wasm = wasmInstance.exports;

このコードを見ると、ローカルにあるファイルを読み込むことを前提としているのがよくわかります。
これを次のコードに変換できれば、動く見込みです。

おそらく望まれている処理
1
2
3
4
5
6
const wasmCode = new Uint8Array([
0,97,115,109,1,0,0,0,1,166,130,128,128,~~~~~ // wasmをuint8 の配列に変換したもの
])
const wasmModule = new WebAssembly.Module(wasmCode);
const wasmInstance = new WebAssembly.Instance(wasmModule, imports);
const wasm = wasmInstance.exports

変換ツール作成

というわけで、変換ツールを用意しました。

実装

以下の変換ツールを作りました。

deps.ts
1
2
export { parse } from "https://deno.land/std@0.66.0/flags/mod.ts";
export { existsSync } from "https://deno.land/std@0.103.0/fs/mod.ts";
convert.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import { parse, existsSync } from "./deps.ts";

const parsedArgs = parse(Deno.args);

// パラメータ検証
// .wasm ファイルパス
const targetWasmFilePath =
typeof parsedArgs["wasm-file"] === "string" ? parsedArgs["wasm-file"] : "";

if (!targetWasmFilePath) {
console.error(`--wasm-file is not Set!!\nplease confirm.`);
Deno.exit();
}

// .js ファイルパス
const targetJsFilePath =
typeof parsedArgs["js-file"] === "string" ? parsedArgs["js-file"] : "";

if (!targetJsFilePath) {
console.error(`--js-file is not Set!!\nplease confirm.`);
Deno.exit();
}

//対象ファイルの存在検証
if (!existsSync(targetWasmFilePath)) {
console.error(
`Wasm file [${targetWasmFilePath}] is not Found!!\nplease confirm.`
);
Deno.exit();
}

if (!existsSync(targetJsFilePath)) {
console.error(
`Js file [${targetJsFilePath}] is not Found!!\nplease confirm.`
);
Deno.exit();
}

const file = Deno.readFileSync(targetWasmFilePath);
const jsCode = Deno.readTextFileSync(targetJsFilePath);

// Deno 向けローカル保存されたwasmを使うコードを削除
const noImportJsCode = jsCode
.split("\n")
.filter((row) => !row.match(/^const file/))
.filter((row) => !row.match(/^const wasmFile/))
.filter((row) => !row.match(/^const wasmModule/))
.filter((row) => !row.match(/^const wasmInstance /))
.filter((row) => !row.match(/^const wasm /))
.join("\n");

// wasmの文字列化処理、jsコード埋め込み
let str = `
${noImportJsCode}

const wasmCode = new Uint8Array([
${[].slice.call(file)}
]);
const wasmModule = new WebAssembly.Module(wasmCode);
const wasmInstance = new WebAssembly.Instance(wasmModule, imports);
const wasm = wasmInstance.exports

`;

console.log(str);

使い方

前提としては、wasm-bindgen で deno 向けのビルドがされていることが条件になります。

1
2
3
# 実行
$ deno run -A --unstable convert.ts --wasm-file=./pkg/wasm_test_bg.wasm --js-file=./pkg/wasm_test.js
> ./pkg/wasm_test_with_bin.js

これで、Deno Manual - WebAssembly support に書かれたものを生成できました。


この件、かれこれ 2 週間ほどかけてしまいました。
最終的に、かなり勉強になりました。
近日、このツールだけ deno.land/x に公開する予定です。
公開しました。
deno.land/x/js_with_embedded_wasm

ではでは。