Scm_VMDynamicWind (2)

POP_CONT の動作がなんとなくイメージできた。
昨晩なエントリの焼き直しになりますが、ご容赦下さい。まず押さえておく必要があるのが昨晩エントリな記述で言えば Scm_VMDynamicWind() を呼び出す部分な_PC に RET を仕込んで val0 な関数 (stdlib_dynamic_wind) 呼び出して戻りを val0 格納_になる。

とりあえず Scm_VMDynamicWind()

Scm_VMPushCC() を呼び出して dynwind_before_cc を_次の RET で呼んでね_なおまじない。

    Scm_VMPushCC(dynwind_before_cc, data, 3);

新たに積む継続フレームの pc 属性に引数で渡される dynwind_before_cc を設定。他はざっくり以下なカンジ。

    s = SP;
    cc = (ScmContFrame*)s;
    s += CONT_FRAME_SIZE;
    cc->prev = CONT;
    cc->argp = NULL;
    cc->size = datasize;
    cc->pc = (ScmWord*)after;
    cc->base = BASE;
    cc->env = ENV;
    for (i=0; i<datasize; i++) {
        *s++ = SCM_OBJ(data[i]);
    }
    CONT = cc;
    ARGP = SP = s;

継続フレームの上に引数が積まれてます。で、以下。

    return Scm_VMApply0(before);

これが Scm_VMDynamicWind() の戻りになります。戻った時点で PC が

TAIL-CALL
RET

なナニを指してて val0 には before なポインタが格納されてます。ので before が呼ばれて云々、って引数とかどうなるんだと思ったら dynamic-wind に渡される手続きは全部 thunk な模様。そのまま続けます。

before が呼ばれて

戻って RET がナニ。基本的に継続フレームがあればそれを pop するので先で push した継続フレームが復活。pop されるのは C continuation (Scm_VMPushCC() 手続きでは継続フレームの argp 属性に NULL がセット) なので以下が適用される。

        if (CONT->argp == NULL) {                                       \
            void *data__[SCM_CCONT_DATA_SIZE];                          \
            ScmCContinuationProc *after__;                              \
            void **d__ = data__;                                        \
            void **s__ = (void**)((ScmObj*)CONT + CONT_FRAME_SIZE);     \
            int i__ = CONT->size;                                       \
            while (i__-- > 0) {                                         \
                *d__++ = *s__++;                                        \
            }                                                           \
            after__ = (ScmCContinuationProc*)CONT->pc;                  \
            if (IN_STACK_P((ScmObj*)CONT)) SP = (ScmObj*)CONT;          \
            ENV = CONT->env;                                            \
            ARGP = SP;                                                  \
            PC = PC_TO_RETURN;                                          \
            CONT = CONT->prev;                                          \
            BASE = CONT->base;                                          \
            VAL0 = CALL_CCONT(after__, VAL0, data__);                   \
        } else if (IN_STACK_P((ScmObj*)CONT)) {                         \

当初、CALL_CCONT というマクロは一体? というあたりにヒッカカッてたのですが、shiro さんのコメントを見た後に上記をニラんでて気がついたのが

  • CALL_CCONT の定義は
#define CALL_CCONT(p, v, d) p(v, d)
    • てコトは CALL_CCONT(after__, VAL0, data__) は after__(VAL0, data__) じゃん
    • after__ の型は ScmCContinuationProc なポインタになってますな
    • 定義は以下
typedef ScmObj ScmCContinuationProc(ScmObj result, void **data);
    • これって例えば dynwind_before_cc なソレと合致

という事は before が呼ばれて RET する時点で dynwind_before_cc が引数とかも復帰された状態で呼びだされて、その中で今度は body が (ry
という流れになる訳か。このあたりは正に shiro さんがフォローしてくれていた部分ですね。ざくっと Scm_VMDynamicWind() 以降の流れを整理してみると

  • Scm_VMDynamicWind()
    • before, body, after を引数で dynamic_wind_cc を呼んでね、と継続フレームに push
    • before な thunk 呼んでね、と vm にお願い
    • before な処理が終わったら継続フレームから dynamic_wind_cc を取り出して呼び出し
  • dynwind_before_cc()
    • vm->handlers に (before after) なリストを push
    • after と push する前の vm->handlers を引数で dynamic_body_cc を呼んでね、と継続フレームに push
    • body な thunk 呼んでね、と vm にお願い
    • body な処理が終わったら継続フレームから dynamic_body_cc を取り出して呼び出し
  • dynwind_body_cc()
    • push する前の vm->handlers に復帰
    • val0 (result で渡される) と vm->numVals と多値な戻りを引数で dynwind_after_cc を呼んでね、と継続フレームに push
    • after な thunk 呼んでね、と vm にお願い
    • after な処理が終わったら継続フレームから dynwind_after_cc を取り出して呼び出し
  • dynwind_after_cc()
    • 戻りを設定して return

このあたりのイメージが

ある程度適切だとすると、Scm_VMCallCC() の掘削精度が上がったりなんかするのかな。余裕があればそちらに戻って掘削予定。

それにしても

shiro さんからのフォローに感謝です。