一つ前の記事で
guardの例外の再送出をwith-exception-handlerで受けると無限ループに陥る問題を解決した話。結論を先に書くと、raise、raise-continuable及びwith-exception-handlerをSchemeで実装して、継続の境界を作らないようにした。ぼ~っと考えて実装したら動いちゃった系の解決方法で、特に苦労とかなかったんだけど、実装前に気になっていた点が以下:
- Cで
with-exception-handlerを使っているか - 使ってなかった。なんでこれC側にあるんだろう状態だった。
- C側で例外投げたらどうなるの
- この記事の肝、気になったら続きを読んで。
(core)ライブラリだけでは使えなくなったけど、これに依存するコードはないと思うのでまぁ、問題ないだろう。二つ目はC側のコールスタック。継続の境界は
Sg_Apply系の関数で作られるんだけど、コールスタックの深いところで例外が投げられたらどうする?ってのをぼ~っと考えていた。はい、longjmpを使うだけでした。具体的にはこんな感じで例外が投げられたとする。
Call stack
+----------+ +---------+ +---------+ +---------+
--| Sg_Apply |--| C func1 |--| C func2 |--| C func3 |
+----------+ +---------+ +---------+ +---------+
^^^^^^^
C error
こんな感じでCのraiseは呼ばれた時点でVMのスタックにSchemeのraiseを呼び出す継続フレームを入れる。
Before
PC = CALL
Stack
+--------------------------+
| Return to previous frame |
+--------------------------+
:
After
PC = RET
Stack
+--------------------------+
| Return to calling raise | --> PC to return = CALL raise
+--------------------------+
| Return To previous frame |
+--------------------------+
:
with-exception-handlerとraise-continuableの組み合わせだと、呼び出し元に戻る必要があるので一つ前のフレームを飛ばすわけにはいかない。この状態にしておいて、longjmpを呼び出し、VMのループを再起動する。もともとVM自体はVMループへのjmpbufを持っているのでそれを使うだけ。割とお手軽に解決できてしまった。このバグのおかげで別のバグをつぶすこともできたし、YpsilonとMoshのバグを発見することもできたのでいいバグ(?)だった。
No comments:
Post a Comment