一つ前の記事で
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