💍

Onyx 入門2: ブラウザで実行

2023/12/16に公開

https://zenn.dev/hatappo/articles/f1ecb49715ea6c

の続きです。

前の記事では、PC端末上で動かしていたので今度はブラウザからやってみます。
Onyx がデフォルトで生成する wasm ファイルは通常の wasm バイナリとはマジックナンバー(バイナリの先頭8バイト)が異なっていました。

  • 通常の wasm バイナリ -> \0 a s m (以降の4バイトはバージョン番号。省略)
  • onyx build が生成したバイナリ -> O N Y X

Onyx はデフォルトでは onyx 独自のバイナリを生成します。いつもの wasm バイナリを生成するには -r js オプションが必要です。

onyx build add.onyx -o add.wasm -r js

続いて wasm バイナリを読み込む HTML を作成

<!DOCTYPE html>
<html>
<body>
  <h1>Onyx on browser</h1>
  <script>
    let importObject = {
      host: {
        print_str: function() {},
        time: function() {},
      },
    };

    (async () => {
      let obj = await WebAssembly.instantiateStreaming(fetch('add.wasm'), importObject);
      console.log(`10 + 20 = ${obj.instance.exports.add(10, 20)}`);
    })();
  </script>
</body>
</html>
  1. importObject は wasm 側にインポートするオブジェクトや関数です。エラーを通すためだけのスケルトン実装です。 ちゃんと書くなら このあたり を参考に。
  2. その後の IIFE (即時実行関数)はお決まりの wasm バイナリをロードするコードと wasm 側からエクスポートされた関数の呼び出しです。
npm install live-server -D
npx live-server

などして[1]、ローカルでサーバを立ち上げて http://localhost:3000 を開いて Dev コンソールを開いて動作確認:

ちなみに、ビルドターゲットは以下のように onyx(default), js 以外に wasi, custom が指定可能です。[2]

$ onyx help build | grep '\-r '
        --runtime, -r <runtime> Specifies the runtime. Can be: onyx, wasi, js, custom.

生成された wasm バイナリを wat (Web Assembly Text) フォーマットに逆コンパイルしてみます。次のコードで使っている wasm2wat コマンドは https://github.com/battlelinegames/wat-wasmnpm i npm install wat-wasm -g で入れていますが、 wabtwabt.js でもOKです。

$ wasm2wat add.wasm                    

========================================================
  WASM2WAT
========================================================
  

  Need help?  
  Contact Rick Battagline
  Twitter: @battagline
  https://wasmbook.com
  v1.0.43
  
Writing to add.wat
WAT File Saved!
# インポートしてるオブジェクトと関数
$ grep '(import "host" ' add.wat
 (import "host" "print_str" (func $fimport$0 (param i32 i32) (result i32)))
 (import "host" "print_str" (func $fimport$1 (param i32 i32) (result i32)))
 (import "host" "time" (func $fimport$2 (result i64)))

# エクスポートしてる関数
$ grep '"add" ' add.wat         
 (export "add" (func $18))

$ wc -l add.wat
   44272 add.wat

さて、実際には Onyx としては script タグで wasm バイナリを指定して読み込ませるのが通常の使い方として想定しているようです。 サンプルと思われるロード処理の JS onyx-loader.js をコピーして持ってきます。

<!DOCTYPE html>
<html>
<body>
  <h1>Onyx on browser 2</h1>
  <script type="application/onyx" src="add.wasm"></script>
  <script src="onyx-loader.js"></script>
</body>
</html>

onyx-loader.js の中身:

  1. application/onyx のスクリプトタグを全部スキャンして srcfetch して instantiate しているだけなので、そこは最初の HTML の JS とそんなに変わらないです。
  2. ブローバル変数 window.ONYX_* に色々設定してあるので、最初の HTML で importObject を渡していた処理が不要になっています。 window.ONYX_MODULES に入れといたのをインポートしてくれています。
  3. <script type="application/onyx" multi-threaded という指定ができるみたいです。マルチスレッドのサポートが強力なのか?
  4. window.ONYX_INSTANCE に instantiate した wasm インスタンスが入っています。上のスクショはコンソール上でそこからエクスポート関数を取り出して実行してみてます。

ソースコード全体: https://github.com/hatappo/tinker-onyx

脚注
  1. もちろん、ローカルのファイルをサーブしてくれれば live-server じゃなくてもなんでもいいです。 ↩︎

  2. help コマンドの使い方と -r js大井さかなさんから教えてもらいました。から教えてもらいました。ありがとうございますm(_ _)m ↩︎

Discussion