Scm_VMCallCC (15)

ええと、昨晩思いついた

gosh> (disasm (lambda () (list 1 2 3 (call/cc (lambda (c) (set! cont c) 4)) 5 6)))
main_code (name=#f, code=0x8117ea8, size=14, const=2, stack=13):
args: #f
     0 CONSTI-PUSH(1) 
     1 CONSTI-PUSH(2) 
     2 CONSTI-PUSH(3) 
     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))
     9 PUSH 
    10 CONSTI-PUSH(5) 
    11 CONSTI(6) 
    12 LIST(6)                  ; (list 1 2 3 (call/cc (lambda (c) (set! c ...
    13 RET 
internal_closure_0 (name=#f, code=0x80d7b88, size=5, const=1 stack=0):
args: #f
     0 LREF0                    ; c
     1 GSET #<identifier user#cont>; cont
     3 CONSTI(4) 
     4 RET 
#<undef>
gosh> 

なインストラクションをスタックに着目しつつ確認してみる事に。

とりあえず

最初の状態がどうなってるか、がナニ。基本的には

gosh> (list 1 2 3 (call/cc (lambda (c) (set! cont c) 4)) 5 6)

と投入された時の、を想定でナニ。cont はグローバルな環境に束縛されてるのが前提とすると継続フレームと環境フレームのみがスタックに居る状態なのでしょうか。argp は面倒なので sp と同じ、という事にして確認してみる。

argp,sp>|        |
        +--------+
        .        .
        .        .
        +--------+
        |        |
    env>|  ENV   |
        +--------+
        .        .
        .        .
        +--------+
        |        |
   cont>|  CONT  |
        +--------+

これを始点にインストラクションをトレイスしてみる。ええと、順に push

     0 CONSTI-PUSH(1)
     1 CONSTI-PUSH(2)
     2 CONSTI-PUSH(3)

するとこんなカンジ?

     sp>|        |
        |   3    |
        |   2    |
   argp>|   1    |
        +--------+
        .        .
        .        .
        +--------+
        |        |
    env>|  ENV   |
        +--------+
        .        .
        .        .
        +--------+
        |        |
   cont>|  CONT  |
        +--------+

で、PRE-CALL で継続フレームがナニ。

        |        |
argp,sp>|        |
        +--------+
        |        | ;; base
        |        | ;; pc   next-pc は CLOSURE?
        |   3    | ;; size 本当かどうか微妙
        |   *3   | ;; argp
        |   *2   | ;; env
   cont>|   *1   | ;; prev
        +--------+
        |   3    |
        |   2    |
     *3>|   1    |
        +--------+
        |        |
*2(env)>|  ENV   |
        +--------+
        |        |
     *1>|  CONT  |
        +--------+

長くて面倒なので直列に積まれてる風にしました。あと、next-pc は CLOSURE ではなくて 9 な PUSH ですね。そういえば、この pc の詳細なソレが気になってたんですが、gauche/vm.h の ScmVM 型の定義を見てるに

    ScmCompiledCode *base;      /* Current executing closure's code packet. */
    SCM_PCTYPE pc;              /* Program pointer.  Points into the code
                                   vector. (base->code) */

な定義あり。pc 属性って base->code が指す領域のの要素を順に指すポインタ配列?
# 日本語的に微妙
ぢつはこの取り組みで code vector が終端まで保管されないと微妙、という事には気がついてて、どうやって保管できてるんだろうか、と思っていたのですが上記継続フレームの pc 属性が指してる限り (というかこの継続フレームがスタックの中にある限り)、この code vector な領域も GC による破棄はないのか、と。
# 上記は類推なのでお手やわらかにお願いします

閑話休題

次に行きます。基本的には call/cc に渡される lambda なリストをクロージャに変換して val0 レジスタにセットする模様。スタックには影響が無い模様。
val0 にクロージャな手続きオブジェクトが格納された状態で次のインストラクションに

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

上記インストラクションは

  • val0 を push ;; lambda な手続きオブジェクトへのポインタ値が push?
  • call/cc の束縛を解決して val0 に格納
  • call_entry に jmp

ってその先が重要なんですが ...
上記の時点でこんなカンジ?

     sp>|        |
   argp>|てつづき| ;; lambda な手続きオブジェクト
        +--------+
        |        | ;; base
        |        | ;; pc   next-pc は CLOSURE?
        |   3    | ;; size 本当かどうか微妙
        |   *3   | ;; argp
        |   *2   | ;; env
   cont>|   *1   | ;; prev
        +--------+
        |   3    |
        |   2    |
     *3>|   1    |
        +--------+
        |        |
*2(env)>|  ENV   |
        +--------+
        |        |
     *1>|  CONT  |
        +--------+

ここから call_entry: なラベル以降が起動されるんですが、これ以上続けるのは色々な意味で危険な状態なので、ここで一旦止めます。