stack の動作を机上で確認

最初の状態が分からん、と言いつつ gdb で動かしてみようとしたりしたのですが微妙。どうしたものやら、と言いつつ vm.c をぱらぱら眺めていたら Constructor なるコメントを発見。これが stack の最初の状態ってコトにして見てみる事に。

初期化は以下?

vm.c の Scm_NewVM() 手続きの以下なあたり。

#ifdef USE_CUSTOM_STACK_MARKER
    v->stack = (ScmObj*)GC_generic_malloc((SCM_VM_STACK_SIZE+1)*sizeof(ScmObj),
                                          vm_stack_kind);
    *v->stack++ = SCM_OBJ(v);
#else  /*!USE_CUSTOM_STACK_MARKER*/
    v->stack = SCM_NEW_ARRAY(ScmObj, SCM_VM_STACK_SIZE);
#endif /*!USE_CUSTOM_STACK_MARKER*/
    v->sp = v->stack;
    v->stackBase = v->stack;
    v->stackEnd = v->stack + SCM_VM_STACK_SIZE;

あるいは ScmVM 型 (struct ScmVMRec 構造体) の上記な部分の定義が以下。

    ScmObj *sp;                 /* stack pointer */
    ScmObj *stack;              /* bottom of allocated stack area */
    ScmObj *stackBase;          /* base of current stack area  */
    ScmObj *stackEnd;           /* end of current stack area */

上記の sp 属性は vm.c にて以下のマクロが定義済み。

#define SP    (vm->sp)

それは良いとして、順番としては

  • 領域確保
  • 先頭アドレスを sp 属性にセット
  • 先頭と末端アドレスを属性にセット

そーゆー意味では他のレジスタについても確認?

ScmVM 型のレジスタな属性

以下な形で初期設定。

    v->env = NULL;
    v->argp = v->stack;
    v->cont = NULL;
    v->pc = PC_TO_RETURN;
    v->base = NULL;
    v->val0 = SCM_UNDEFINED;
    for (i=0; i<SCM_VM_MAX_VALUES; i++) v->vals[i] = SCM_UNDEFINED;
    v->numVals = 1;

vm.h における ScmVM 型の属性としての定義は以下。

    /* Registers */
    ScmCompiledCode *base;      /* Current executing closure's code packet. */
    SCM_PCTYPE pc;              /* Program pointer.  Points into the code
                                   vector. (base->code) */
    ScmEnvFrame *env;           /* Current environment.                      */
    ScmContFrame *cont;         /* Current continuation.                     */
    ScmObj *argp;               /* Current argument pointer.  Points
                                   to the incomplete environment frame
                                   being accumulated.  This is a part of
                                   continuation.                             */
    ScmObj val0;                /* Value register.                           */
    ScmObj vals[SCM_VM_MAX_VALUES]; /* Value register for multiple values */
    int    numVals;             /* # of values */
  • 環境 (env) は NULL
  • 引数ポインタ (argp) は stack な値が代入
  • 継続 (cont) は NULL
  • プログラムカウンタ (pc) は RET がナニ (PC_TO_RETURN マクロ)
  • base (詳細不明) も NULL
  • val0 レジスタは #undefined がセット
  • vals も #undefined で numVals に 1 がセット

てーコトは最初の状態は以下?

argp,sp>|         |
        +---------+

ただし、ここから云々ってのはちょっと違う気がしますが ...

進めてみる

以下なソレだとちょっとは現実的?

gosh> (disasm (lambda () ((lambda (x y) (x) (y 1 2)) i j)))
main_code (name=#f, code=0x8149f88, size=14, const=2, stack=14):
args: #f
     0 GREF-PUSH #<identifier user#i>; i
     2 GREF-PUSH #<identifier user#j>; j
     4 LOCAL-ENV(2)             ; ((lambda (x y) (x) (y 1 2)) i j)
     5 PRE-CALL(0) 9
     7 LREF1                    ; x
     8 CALL(0)                  ; (x)
     9 CONSTI-PUSH(1)
    10 CONSTI-PUSH(2)
    11 LREF0                    ; y
    12 TAIL-CALL(2)             ; (y 1 2)
    13 RET
#<undef>
gosh>

i とか j とかって define 済みってコトにしときます。値は適当。微妙に気になるのが GREF-PUSH の行番号が飛んでいるコト。GLOBAL_REF の中で INCR_PC してるんですが、スルーします。とりあえず

  • i の値 (手続きオブジェクト) を push
  • j の値 (手続きオブジェクト) を push

した時点で以下と思われます。

  sp>|       |
     |   j   |
argp>|   i   |
     +-------+

で、LOCAL-ENV から FINISH_ENV マクロがナニされて以下?

argp,sp>|       |
        | size  |   <- 2?
        | info  |   <- #f?
    env>| up    |   <- NULL?
        |   j   |
        |   i   |
        +-------+

で、継続フレームがナニ。しかしこれ、どうやって i とか j とか参照するんだろ、と言いつつ続行。次は継続 push ですか。
実際には以下なカンジになっていると思われる。

     5 PRE-CALL(0) 9
     6 CONSTI-PUSH(1)
     7 LREF1                    ; x
     8 CALL(0)                  ; (x)
     9 CONSTI-PUSH(1)

で、以下?

argp,sp>|       |
        +-------+
        | base  |   <- スルー
        | pc    |   <- next_pc は 9 CONSTI_PUSH(1)
        | size  |   <- argp と sp は同じなので 0?
        | argp  |   <- *1
        | env   |   <- env
*1,cont>| prev  |   <- 積む時点の CONT は NULL?
        +-------+
        | size  |   <- 2?
        | info  |   <- #f?
    env>| up    |   <- NULL?
        +-------+
        |   j   |
        |   i   |
        +-------+

したら、x を val0 にセットするんですが LREF1 は以下。

            CASE(SCM_VM_LREF1)  { VAL0 = ENV_DATA(ENV, 1); NEXT1; }

ENV_DATA(ENV, 1) の値を val0 に格納してます。ENV_DATA マクロが以下。

#define ENV_DATA(env, num) (*(((ScmObj*)(env))-(num)-1))

上記によれば env の二つ前なので i が取り出されて val0 にセットされます。env がベースポインタな訳ですかそうですか。なかなか凄い。
で、次が CALL ですが、i とか j とかはユーザ定義な手続きってコトにします。SCM_VM_CALL の以下なあたりがナニ、のはず。

                if (proctype == SCM_PROC_CLOSURE) {
                    ADJUST_ARGUMENT_FRAME(VAL0, argc);
                    if (argc) {
                        FINISH_ENV(SCM_PROCEDURE_INFO(VAL0),
                                   SCM_CLOSURE(VAL0)->env);
                    } else {
                        ENV = SCM_CLOSURE(VAL0)->env;
                        ARGP = SP;
                    }
                    vm->base = SCM_COMPILED_CODE(SCM_CLOSURE(VAL0)->code);
                    PC = vm->base->code;
                    CHECK_STACK(vm->base->maxstack);
                    SCM_PROF_COUNT_CALL(vm, SCM_OBJ(vm->base));
                    NEXT;
                }

引数無いんで env はどこか他所をナニ。とりあえず stack の状態は以下?

argp,sp>|       |
        +-------+
        | base  |   <- スルー
        | pc    |   <- next_pc は 9 CONSTI_PUSH(1)
        | size  |   <- argp と sp は同じなので 0?
        | argp  |   <- *1
        | env   |   <- *2
*1,cont>| prev  |   <- 積む時点の CONT は NULL?
        +-------+
        | size  |   <- 2?
        | info  |   <- #f?
     *2>| up    |   <- NULL?
        +-------+
        |   j   |
        |   i   |
        +-------+

とりあえず継続なナニに積んであるので安心。で、上記には出てきませんが i な手続きの中で RET がナニされて手続きから戻るはず、という事で以下。

            CASE(SCM_VM_RET) {
                RETURN_OP();
                NEXT;
            }

RETURN_OP マクロが以下。

/* return operation. */
#define RETURN_OP()                                     \
    do {                                                \
        if (CONT == NULL || BOUNDARY_FRAME_P(CONT)) {   \
            return; /* no more continuations */         \
        }                                               \
        POP_CONT();                                     \
    } while (0)

cont は NULL では御座いませんので POP_CONT マクロがナニ。ちょい長いんで該当すると思われるソレを以下に。

        } else if (IN_STACK_P((ScmObj*)CONT)) {                         \
            SP   = CONT->argp + CONT->size;                             \
            ENV  = CONT->env;                                           \
            ARGP = CONT->argp;                                          \
            PC   = CONT->pc;                                            \
            BASE = CONT->base;                                          \
            CONT = CONT->prev;                                          \
        } else {                                                        \

これは見事に巻き戻ります。面白いなぁ。
備忘録まで、上記のナニが呼ばれる直前の stack を再掲。

argp,sp>|       |
        +-------+
        | base  |   <- スルー
        | pc    |   <- next_pc は 9 CONSTI_PUSH(1)
        | size  |   <- argp と sp は同じなので 0?
        | argp  |   <- *1
        | env   |   <- *2
*1,cont>| prev  |   <- 積む時点の CONT は NULL?
        +-------+
        | size  |   <- 2?
        | info  |   <- #f?
     *2>| up    |   <- NULL?
        +-------+
        |   j   |
        |   i   |
        +-------+

とりあえず出かけますので、続きは明日。