Scm_VMCallCC (4)

誰かに進捗状態報告しなきゃいけない && 進捗してないと誰かに迷惑がかかる訳ではないのでアレなんですが、少しでも手を付けとかないと忘れてしまうので (何

とりあえず

contproc が束縛するナニを確認。具体的に言うと

(call/cc (lambda (c) (set! cont c) 4))

の c ですな。とりあえず問題になっている部分が以下。

    contproc = Scm_MakeSubr(throw_continuation, ep, 0, 1,
                            SCM_MAKE_STR("continuation"));

Scm_MakeSubr 手続きは proc.c にて定義されてて以下。

ScmObj Scm_MakeSubr(ScmSubrProc *func,
                    void *data,
                    int required, int optional,
                    ScmObj info)
{
    ScmSubr *s = SCM_NEW(ScmSubr);
    SCM_SET_CLASS(s, SCM_CLASS_PROCEDURE);
    SCM_PROCEDURE_INIT(s, required, optional, SCM_PROC_SUBR, info);
    s->func = func;
    s->data = data;
    return SCM_OBJ(s);
}

ええと、ScmSubr 型な定義は gauche.h にて定義されてて以下。

/* Subr - C defined procedure */
struct ScmSubrRec {
    ScmProcedure common;
    ScmSubrProc *func;
    void *data;
};

これ、最近のエントリで引用しているような気もしますがスルーでお願いします。ちょっと面白いのが SCM_PROCEDURE_INIT マクロ。定義は同様に gauche.h にて以下。

#define SCM_PROCEDURE_INIT(obj, req, opt, typ, inf)     \
    SCM_PROCEDURE(obj)->required = req,                 \
    SCM_PROCEDURE(obj)->optional = opt,                 \
    SCM_PROCEDURE(obj)->type = typ,                     \
    SCM_PROCEDURE(obj)->info = inf,                     \
    SCM_PROCEDURE(obj)->setter = SCM_FALSE,             \
    SCM_PROCEDURE(obj)->inliner = SCM_FALSE

これって定義の引用を略しますが、先頭部分に ScmProcedure 型の属性があるので ScmSubr な s を無理矢理 ScmProcedure でキャストして OK なソレ。func とか data という属性をどう扱っているか、はその後の話な訳と見た。

いかん

スデに忘れているような気がしますが何を忘れたかは不明 (を
ええと、Scm_VMCallCC のいっちゃんケツの Scm_VMApply1 で引数である継続手続きを push しといて PC に

    { SCM_VM_INSN1(SCM_VM_TAIL_CALL, 1),
      SCM_VM_INSN(SCM_VM_RET) },

がナニされた後に引数で指定された lambda なクロージャが戻る形。一旦評価ループに戻って lambda なクロージャを CALL する形になるのか。これは CLOSURE なインストラクションで作られたナニなはず。これ、後で見たら意味不明だろな。
ちょっと補足しておくとこの時点でのソレは

     3 PRE-CALL(1) 9
     5 CLOSURE #<lambda 0>      ; (lambda (c) (set! cont c) 4)
     7 PUSH-GREF-CALL(1) #<identifier user#call/cc>; (call/cc (lambda (c) (set! cont c) 4))

むむ。Scm_VMCallCC 手続きって末尾で Scm_VMApply1 を呼び出してて、PC を書き換えた上で引数の proc を戻しています。CALL から呼び出される箇所が以下だったはずなんですが

                    VAL0 = SCM_SUBR(VAL0)->func(ARGP, argc,
                                                SCM_SUBR(VAL0)->data);
                    /* the subr may substituted pc, so we need to check
                       if we can pop the continuation immediately. */
                    if (TAIL_POS()) RETURN_OP();
                    NEXT;
                }

戻りが val0 に格納されてて NEXT で Scm_VMApply1 で書き換えられた PC が上記の

  • TAIL-CALL
  • RET

だとすると、どこかで call/cc 以降の継続が push されてないと微妙じゃん、と言いつつこのあたりは PRE-CALL で処理されているに違いないと勝手読み。という事は call/cc の中で PC が書き換えられるのはセイフか。
継続手続きな contproc は Scm_VMApply1 手続きにてスタックに push 済みなのでこのまんま TAIL-CALL で lambda な手続きオブジェクトを呼び出して問題ないはず。RET で続きのナニも取り出せるはず。

うーん

なんだか違うイキオイに乗ってトバシてますが、絶対忘れるな (何

備忘

現時点で PC が指してるのは以下な並びのはず。

     0 LREF0                    ; c
     1 GSET #<identifier user#cont>; cont
     3 CONSTI(4)
     4 RET

ええと、

     3 PRE-CALL(1) 9

なインストラクションで続きが継続フレームにナニされてるので

     7 PUSH-GREF-CALL(1) #<identifier user#call/cc>; (call/cc (lambda (c) (set! cont c) 4))

で PC ががんがん書き換えられてても RET 一発で元に戻れる、という事なはず。
あとは、cont に設定された c な手続き (contproc に束縛されてた手続き) が呼び出された時に何が起きるのか、を確認すれば OK なのでしょうか。throw_continuation のあたり。
ここからがハードル高い部分だと思います。