Gauche:YAGHG:VM:dynamic-wind

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

src/vm.c:3003-

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である。

src/vm.c:2725-

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にはさらに次のような特徴がある。

  1. dynamic-wind関数の戻り値はbodyの戻り値
  2. 非局所脱出時の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レジスタの値をどのように使うのか、という点はここでは触れない。これから見るのは

をどのように行っているのかということである。

Scm_VMPushCC

Scm_VMPushCCはGauche:YAGHG:VM:Insn:RETでも少し触れているがC-continuationをプッシュするための関数である。 呼び出している部分を再掲しよう。

src/vm.c:3020-3021

3020     Scm_VMPushCC(dynwind_before_cc, data, 3);
3021     return Scm_VMApply0(before);

dynwind_before_ccが引数data(before/body/after)で呼び出されるタイミングは C-continuationがポップされるとき、すなわちbeforeが呼び出されたあとである。

定義を見てみよう。

src/vm.c:3024-3037

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 }

行っているのは

の3つである。dynwind_body_ccもやることが違うだけで構成は同じだ。

処理の流れをまとめよう。

  1. before
    1. dynwind_before_cc
      • handlersレジスタの設定(追加)
  2. body
    1. dynwind_body_cc
      • handlersレジスタの設定(削除)
      • bodyの戻り値をdynwind_after_ccへの引数にセット
  3. after
    1. dynwind_after_cc
      • 戻り値をセット
More ...