Ribbon: 例外に対応したい

例外の実装なんてC++の try
〜 catch
とか Cの setjmp
〜 longjmp
で良いじゃんと思ってたら素のWebAssemblyは例外使えないの忘れてた。。
今は例外が必要なところは単に abort
の呼出しを入れている。
$ git grep abort | wc -l
324
... マジで。。? とにかく、この324個を、
- 本当のアサーション。つまりインタプリタを異常終了するしかない状態。
- 未実装。まぁ異常終了で良いかな。
- Scheme的なエラー
に分類する必要がある。

真のabortケース
真の abort
ケースでは RnPanic
や RnLowMemory
を呼ぶように変えた。
noreturnをマークするか悩んでたけど、とりあえず宣言した方が良いかな。。静的解析の結果が読みづらくなることが多いので。
これらは単に abort
するだけで良いので、これ以上の変更は必要ない。

非局所脱出なしでどうやって例外を実装するか
非局所脱出が無いということは、 return
または 関数末尾以外から関数を脱出する方法が無いということである。つまり、バケツリレー方式で例外を伝播させていくしかない。
- 一旦、例外を投げる可能性のあるC関数を全て
void
型にする。 - それぞれの関数に"エピローグ"を用意し、直前にgoto用のラベルを張る --
finally
節を用意することに相当する。素のCにはデストラクタが無いので、デストラクタの明示的な呼出しをそこでやることになる。 - それぞれの関数の型を適当なenum型に設定し、例外発生時は非ゼロを返却すると約束する。
- 例外の発生箇所で、 "現在のVMコンテキストに例外フラグを立て、非ゼロを返す" ロジックを入れる。
... 当然、普通に例外や setjmp
が使える環境ではこれらの処理は不要なので、マクロなり何なりで消せるようにしておく必要がある。

先unroll方式
Ribbonでは、ネイティブ関数は RnEnter
〜 RnLeave
で囲まれた区間にスタックから参照されるGCオブジェクトを置かなければならないと約束している。これで、 RnEnter
でコンテキストに登録したフレームに RnValueLink
でローカル変数を登録できるようにしている。
コンテキストから過去のフレーム全てにアクセスできるので、先にunrollされるぶんのオブジェクト全てを解放してから実際のunrollを行う方が実装が簡単で確実だな多分。つまり、
- フレームに例外ハンドラを任意で登録できるようにする
- 例外が発生したら、その場で例外ハンドラが出てくるまでフレームを(ローカル変数を解放しつつ)巻き戻し、非ゼロを返却する
- 呼出し元は、非ゼロが返却されたらそのまま非ゼロを返して自分の呼出し元に制御を戻す
- 例外ハンドラを登録したフレームは、非ゼロが返却されてきたら例外の処理をする

void
から RnResult
に変更する
例外を起こす可能性のある関数全ての型を
... 苦行すぎる。。
RnResult
型の変数をソースコード内で直接記述することは無い。(例外が使えるプラットフォームでは RnResult
は void
になるので)
この RNFUNC_BEGIN
等のマクロは、
のように宣言されている。例外が発生するかもしれない関数は常に RNFUNC_CALL
マクロを通して呼ぶと約束し、関数の戻り値から例外の発生を知った場合は即 abort
している。先unrollを実装したら、ここのabortが goto rnresult_return;
に変更されることになる。