binaryen.jsはbinaryenのjs版。延々とビルドせずに使えるのが嬉しい。
binaryenの説明文に「 used from JavaScript. 」って書いてあるけど、その先はパスにwikiがあるので説明だけ。ググり続け、npmのリポジトリィを見つけた。
npm install binaryen
元ネタのリポジトリィは、github.com/
ちなみにそこをクローンすると6GB以上あるのでnpmで済ませる。
パッケージ本体はTypeScriptで書かれていて配布用のindex.jsは圧縮されほぼ解読不能。
npmのページのサンプルコードを入力して実行すると
Uncaught Error Error [ERR_REQUIRE_ASYNC_MODULE]: require() cannot be used on an ESM graph with top-level await. Use import() instead. To see where the top-level await comes from, use --experimental-print-required-tla.
どうやらnpmのページのサンプルは5年前の書き方らしい。
var binaryen = require(“binaryen”);
を元ネタのページのサンプルの様に
import binaryen from “binaryen”;
に直すと動き出したが、直ぐエラってしまうので、※5年の間に色々変わり過ぎ
全部元ネタのページのサンプルに差し替えると最後まで動いた。
ちなみにこの記事を書いている時点でのモジュールのupdateは4時間前。※出来たてホヤホヤ
それにしても
jsのアロー関数とか、
wasmのテキストがS式とか、
大昔に卒論で(デバッグのために)いっぱい書いたから違和感はないけど
なんで大昔(半世紀くらい前)に流行ったフォーマットを使うんだろう?
謎(闇?)は深い。
binaryen.jsはTypeScriptベースのjavascriptソースで提供されている。
Node.jsでサンプルを実行する分にはnpmで作った設定ファイル等を参照してくれるのでパッケージ名だけ指定すればOK。
import binaryen from "binaryen";
ブラウザ(Chrome)で使用する場合はスクリプト・タグのタイプを”module”指定しimport文を使用可能にしてもnpmプロジェクトとか気に留めてないので直接node_modules/binaryenプロジェクトのpackage.jsonに”main”:”index.js”と書かれたファイルを相対パスで指定しないといけない。
chromeで表示するHTMLで<script type="module" src="./worker.js">とtypeでmodule指定しないとimportが使えない
import binaryen from "./node_modules/binaryen/index.js"; ※npm install binaryen したなら皆同じになるハズ
※ローカルな環境(file:///)ではimportがアクセスエラーで使えないっぽく、何かのWebサービス(apache等)上にHTML等を配置する必要がある。
index.html:1 Access to script at 'file:///・・・/test/worker.js' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: chrome, chrome-extension, chrome-untrusted, data, http, https, isolated-app.
worker.js:1
Failed to load resource: net::ERR_FAILED
さらに厄介なのが、HTMLやtype=”text/script”(デフォルト設定)のjavascriptからtype=”module”を指定するモジュールが見えない(DevToolでモジュール欄すら出ない)のでpostMessageを経由してイベントを通知しないとダメらしい。※moduleと指定してるのでworker.jsをワーカーとして実行しなくてよい。
window.addEventListener("load", () => {
document.querySelector("#btn").addEventListener("click", test);
});
const test = () => {
postMessage("test", "http://localhost:xxxxx"とか);
}
それより全スクリプトをtype=”module”にすればいい、モジュールにはwindow.addEventListener(“load”, の通知が来ないが、 モジュールにはdefer属性が自動付与されるから、即DOMにアクセスできる。
import binaryen from "./node_modules/binaryen/index.js";
//# とりあえず、urlからドメインとポートを取り出してポストメッセージのチェックに使用する
//# function getDomainPort() {
//# const url = import.meta.url;
//# const result = /^(http[s]?:\/\/([.0-9a-z]+)(:\d+)?)/.exec(url);
//# return result[0];
//# }
document.querySelector("#btn").addEventListener("click", workerTest);※これで解決
// アロー関数にするとworkerTest実行時にChromeのDevToolの範囲のモジュールに
// 自分の関数名が出ないので再帰する時は困るかもしれない。
function workerTest(event) {
try {
//# if (event.origin !== getDomainPort()) {
//# return;
//# }
// Create a module with a single function
myModule.addFunction("add", // 以下、ボクの理解範囲
binaryen.createType([binaryen.i32, binaryen.i32]), // parames: [引数[0]の型, 引数[1]の型]
binaryen.i32, // result: 戻り値の型
[binaryen.i32], // vars: スタックを型付き配列風に宣言
myModule.block(null, [
myModule.local.set(2, // 結果を スタック[2] に格納
myModule.i32.add( // 加算(スタックの2つの変数を加算)
myModule.local.get(0, binaryen.i32), // 引数[0]をスタックに積む
myModule.local.get(1, binaryen.i32) // 引数[1]をスタックに積む
)
),
myModule.return( // 戻り値は下記の様にスタックされる
myModule.local.get(2, binaryen.i32) // スタック[2]をスタックに積む
)
])
);
myModule.addFunctionExport("add", "add"); // 上の"add"関数を"add"の名前で外部参照を宣言
// Optimize the module using default passes and levels
myModule.optimize(); // 最適化
// Validate the module
if (!myModule.validate()) // 計算でエラったら
throw new Error("validation error"); // "validation error"とスローする
// Generate text format and binary
var textData = myModule.emitText(); // 上記の設定からテキスト(S式)で得る
var wasmData = myModule.emitBinary(); // 上記の設定からWebAssemblyコードで得る
// Example usage with the WebAssembly API
var compiled = new WebAssembly.Module(wasmData); // WebAssemblyコードを含むモジュールを生成
// importのリンク情報等を設定?
var instance = new WebAssembly.Instance(compiled, {}); // 実行可能なモジュールのインスタンスを生成
const result = (instance.exports.add(41, 1)); // インスタンスを実行させてみる
document.querySelector("#result").innerHTML = `textData:${textData}<br/>result:${result}<br/>`;
};
window.addEventListener("message", workerTest);
うっかりすると、localってローカルな変数かと思うけどスタックマシンなのでlocal=stack。
myModule.local.set(2, よりも myModule.local.set(0, の方が良いと思うけど、最適化(Module.optimize())でS式はいづれも同じになる。
(module
(type $0 (func (param i32 i32) (result i32)))
(export "add" (func $add))
(func $add (param $0 i32) (param $1 i32) (result i32)
(i32.add (local.get $0) (local.get $1) ) ) ) ※明示的にパラメータをスタックに積む様になってる
ここまで面倒になってるものの、以前はmodule側からは一切DOMにアクセスできなかったと思うが
document.querySelector(“#result”)が可能になってるのは助かる。
とりあえず、podmanの未公開なapacheにソースを配置してVScodeでリモートデバッグを心掛ければ支障は無さそう。

VScodeの拡張機能でLive ServerやLive Previewを使うのも手軽かも。いづれもVScode上でのデバッグはできないが、
Live Serverは、HTMLエディタ上で右クリックして「Open with Live Server」でブラウザで開けるしDevToolでデバッグが可能。
Live Previewも同様に「Show Preview」でスグ見れる、見れるダケだけど。
LiveServerがどのポートを使うのか判らないのでgetDomainPort()みたいにLiveServerが割り当てたurlからドメインとポートが得られるようにした方が気楽。