Open5

Ribbon: 例外に対応したい

okuokuokuoku

例外の実装なんてC++の trycatch とか Cの setjmplongjmpで良いじゃんと思ってたら素のWebAssemblyは例外使えないの忘れてた。。

今は例外が必要なところは単に abort の呼出しを入れている。

$ git grep abort | wc -l
324

... マジで。。? とにかく、この324個を、

  1. 本当のアサーション。つまりインタプリタを異常終了するしかない状態。
  2. 未実装。まぁ異常終了で良いかな。
  3. Scheme的なエラー

に分類する必要がある。

okuokuokuoku

真のabortケース

https://github.com/okuoku/ribbon/commit/e3ec2d527520cf94f9e47d744a063e31590f3ebe

真の abort ケースでは RnPanicRnLowMemory を呼ぶように変えた。

noreturnをマークするか悩んでたけど、とりあえず宣言した方が良いかな。。静的解析の結果が読みづらくなることが多いので。

https://github.com/okuoku/ribbon/commit/f859b7f3dfb233ecb5fb47d183d957ddc3eeea49

これらは単に abort するだけで良いので、これ以上の変更は必要ない。

okuokuokuoku

非局所脱出なしでどうやって例外を実装するか

非局所脱出が無いということは、 return または 関数末尾以外から関数を脱出する方法が無いということである。つまり、バケツリレー方式で例外を伝播させていくしかない。

  1. 一旦、例外を投げる可能性のあるC関数を全て void 型にする。
  2. それぞれの関数に"エピローグ"を用意し、直前にgoto用のラベルを張る -- finally 節を用意することに相当する。素のCにはデストラクタが無いので、デストラクタの明示的な呼出しをそこでやることになる。
  3. それぞれの関数の型を適当なenum型に設定し、例外発生時は非ゼロを返却すると約束する。
  4. 例外の発生箇所で、 "現在のVMコンテキストに例外フラグを立て、非ゼロを返す" ロジックを入れる。

... 当然、普通に例外や setjmp が使える環境ではこれらの処理は不要なので、マクロなり何なりで消せるようにしておく必要がある。

okuokuokuoku

先unroll方式

Ribbonでは、ネイティブ関数は RnEnterRnLeave で囲まれた区間にスタックから参照されるGCオブジェクトを置かなければならないと約束している。これで、 RnEnter でコンテキストに登録したフレームに RnValueLink でローカル変数を登録できるようにしている。

https://github.com/okuoku/ribbon/blob/7e583f897d163994fdcadda55b4f99f480d9fd5e/c-proto/c-proto.c#L1331-L1351

コンテキストから過去のフレーム全てにアクセスできるので、先にunrollされるぶんのオブジェクト全てを解放してから実際のunrollを行う方が実装が簡単で確実だな多分。つまり、

  1. フレームに例外ハンドラを任意で登録できるようにする
  2. 例外が発生したら、その場で例外ハンドラが出てくるまでフレームを(ローカル変数を解放しつつ)巻き戻し、非ゼロを返却する
  3. 呼出し元は、非ゼロが返却されたらそのまま非ゼロを返して自分の呼出し元に制御を戻す
  4. 例外ハンドラを登録したフレームは、非ゼロが返却されてきたら例外の処理をする
okuokuokuoku

例外を起こす可能性のある関数全ての型を void から RnResult に変更する

https://github.com/okuoku/ribbon/commit/1732576e9a5bb4826bde7f3df836a611264ee708

... 苦行すぎる。。

RnResult 型の変数をソースコード内で直接記述することは無い。(例外が使えるプラットフォームでは RnResultvoid になるので)

https://github.com/okuoku/ribbon/blob/1732576e9a5bb4826bde7f3df836a611264ee708/c-proto/prims.inc.h#L343-L348

この RNFUNC_BEGIN 等のマクロは、

https://github.com/okuoku/ribbon/blob/1732576e9a5bb4826bde7f3df836a611264ee708/c-proto/c-proto.h#L168-L179

のように宣言されている。例外が発生するかもしれない関数は常に RNFUNC_CALL マクロを通して呼ぶと約束し、関数の戻り値から例外の発生を知った場合は即 abort している。先unrollを実装したら、ここのabortが goto rnresult_return; に変更されることになる。