WASMは通常の制御フローしかなくて大域ジャンプはできないものかと思い込んでいたが、例外も使えるということを知ったのでどんなもんか調べた。
概要
ドキュメントはWebAssembly/exception-handlingに書かれている:
throw
で例外を起こす、「タグ」で識別するtry
〜catch
はWASMバイトコードのブロックrethrow
は何段投げるかを指定する(ラベルも可)delegate
で上に任せることもできる
例
ドキュメントだけだとよくわからなかったので、WATソースを書いて動かしてみた:
;; try.wat |
上記を wat2wasm
でコンパイルするが、現状ではオプション --enable-exceptions
を指定する必要があった(バージョン1.0.32)。
- タグセクション(13)が追加で出力される
--verbose
指定でどんなバイナリが出力されるかがわかりやすい
そしてrunwasmを使って実行: node runwasm.js try.wasm main
- node.js 18より前だと
--experimental-wasm-eh
オプションを指定する必要がある - 例外なので当然ながら、
try
内から呼び出した関数内でthrow
すると関数を飛び越えてcatch
できる- タグに対応する型でパラメータを送れて、キャッチ側でそれを利用できる
- WASM内でキャッチされなかった例外はNode.js側でもキャッチできる
- 例外はJavaScriptの
WebAssembly.Exception
クラス:if (e instanceof WebAssembly.Exception) ...
- タグも
export
できるので、それを使って判定できる:if (e.is(instance.exports.NOT_ENOUGH_MONEY_EXCEPTION) ...
- 例外はJavaScriptの
ブラウザで動くか?
wabtやNode.js 16だとコマンドラインオプションが必要だったが、Google ChromeやSafariでは問題なく動いた: JavaScript built-in: WebAssembly: Exception: is | Can I use… Support tables for HTML5, CSS3, etc
その他
- 実際の例外処理:WASMでは単純な数値型以外は線形メモリ上に置く必要があってその位置をグローバル変数で管理することになるが、例外で大域ジャンプが行われると値がずれてしまう可能性があるので調整する仕組みを入れる必要がある
- C言語からのコンパイルで
setjmp
が動くようにしてやろうと考えたが、try
〜catch
にするには代入先の変数名を特定して構文木を書き換える必要があるので、ちょっと手間がかかりそう対応した