Gauche:YAGHG:VM:dynamic-wind
定義元の調べ方
まずdynamic-windそれ自体を評価すると実体はCの関数であることが分かるので、*.stubから検索してみよう。
$ grep -A 1 dynamic-wind src/*.stub src/stdlib.stub:(define-cproc dynamic-wind (pre body post) src/stdlib.stub- (call "Scm_VMDynamicWind"))
Scm_VMDynamicWind
3003 ScmObj Scm_VMDynamicWind(ScmObj before, ScmObj body, ScmObj after) 3004 { 3005 void *data[3]; ... 3016 data[0] = (void*)before; 3017 data[1] = (void*)body; 3018 data[2] = (void*)after; 3019 3020 Scm_VMPushCC(dynwind_before_cc, data, 3); 3021 return Scm_VMApply0(before); 3022 }
Scm_VMApply0
dynamic-windは乱暴に言ってしまえば引数として与えられたbefore/body/afterを順に実行するだけのものだ。 それらSchemeレベルの関数をCの関数から呼び出す方法の一つがScm_VMApply0である。
2725 ScmObj Scm_VMApply0(ScmObj proc) 2726 { 2727 ScmVM *vm = theVM; 2728 vm->pc = apply_calls[0]; 2729 return proc; 2730 }
vm->pcが指すようになるのはTAIL-CALLとRETからなるインストラクションコードである。 またreturnされることでprocがval0レジスタに設定されるため、TAIL-CALLによってprocが呼び出されるという仕組みだ。
考慮しなければならない点
しかし話はそう簡単ではない。dynamic-windにはさらに次のような特徴がある。
- dynamic-wind関数の戻り値はbodyの戻り値
- 非局所脱出時のbefore/afterの呼び出し
- 継続
- エラー処理
前者は単純に値を待避しておけばよい。
後者のために使われるのがhandlersレジスタである。これはトップレベルから現在の実行時点までに導入されたbefore/afterのペアを 保持したリストで次のような構造をとる。
(dynamic-wind before-0 (lambda () (dynamic-wind before-1 (lambda () => ... ) after-1)) after-0) handlers ----> ((before-1 . after-1) (before-0 . after-0))
handlersレジスタの値をどのように使うのか、という点はここでは触れない。これから見るのは
- bodyの戻り値の待避
- handlersレジスタの設定
をどのように行っているのかということである。
Scm_VMPushCC
Scm_VMPushCCはGauche:YAGHG:VM:Insn:RETでも少し触れているがC-continuationをプッシュするための関数である。 呼び出している部分を再掲しよう。
3020 Scm_VMPushCC(dynwind_before_cc, data, 3); 3021 return Scm_VMApply0(before);
dynwind_before_ccが引数data(before/body/after)で呼び出されるタイミングは C-continuationがポップされるとき、すなわちbeforeが呼び出されたあとである。
定義を見てみよう。
3024 static ScmObj dynwind_before_cc(ScmObj result, void **data) 3025 { ... 3029 ScmObj prev = theVM->handlers; .... 3032 d[0] = (void*)after; 3033 d[1] = (void*)prev; 3034 theVM->handlers = Scm_Cons(Scm_Cons(before, after), prev); 3035 Scm_VMPushCC(dynwind_body_cc, d, 2); 3036 return Scm_VMApply0(body); 3037 }
行っているのは
- handlersレジスタの設定
- dynwind_body_cc呼び出しのためのC-continuationのプッシュ
- body呼び出し
の3つである。dynwind_body_ccもやることが違うだけで構成は同じだ。
処理の流れをまとめよう。
- before
- dynwind_before_cc
- handlersレジスタの設定(追加)
- dynwind_before_cc
- body
- dynwind_body_cc
- handlersレジスタの設定(削除)
- bodyの戻り値をdynwind_after_ccへの引数にセット
- dynwind_body_cc
- after
- dynwind_after_cc
- 戻り値をセット
- dynwind_after_cc