Scm_VMCallCC (20)
そろそろ ReadingGauche 方面を出力して、test/dynwind.scm の中身をナニしたい。
前提
以下のインストラクションを前提に整理 (になるかどうか)。
gosh> (disasm (lambda () (list 1 2 (call/cc (lambda (c) (set1 cont c) 3)) 4))) main_code (name=#f, code=0x80f9db0, size=12, const=2, stack=12): args: #f 0 CONSTI-PUSH(1) 1 CONSTI-PUSH(2) 2 PRE-CALL(1) 8 4 CLOSURE #<lambda 0> ; (lambda (c) (set1 cont c) 3) 6 PUSH-GREF-CALL(1) #<identifier user#call/cc>; (call/cc (lambda (c) (set1 cont c) 3)) 8 PUSH 9 CONSTI(4) 10 LIST(4) ; (list 1 2 (call/cc (lambda (c) (set1 con ... 11 RET internal_closure_0 (name=#f, code=0x80f8ca0, size=8, const=2 stack=11): args: #f 0 PRE-CALL(2) 6 2 GREF-PUSH #<identifier user#cont>; cont 4 LREF0-PUSH-GREF-CALL(2) #<identifier user#set1>; (set1 cont c) 6 CONSTI(3) 7 RET #<undef> gosh>
上記 6 の call_entry: ラベルの直前のスタックの状態が以下。
sp>| | argp>|てつづき| ;; lambda な手続きオブジェクト +--------+ | | ;; base | | ;; pc next-pc は 8 の push | 2 | ;; size | *3 | ;; argp | *2 | ;; env cont>| *1 | ;; prev +--------+ | 2 | *3>| 1 | +--------+ | | *2(env)>| ENV | +--------+ | | *1>| CONT | +--------+
PUSH_GREF_CALL な分岐にて val0 に格納された closure を push して call/cc の global な束縛を解決して val0 に格納した所。cont が指す継続フレームは直前の PRE_CALL で push されてて、next-pc は 8 になっている模様。
この後、
- 引数の数をカウントして argc に格納 (このケースだと 1)
- SP に ARGP の値を格納
- PC は RET を指す状態
にしておいて val0 に格納されている手続きオブジェクトを元に以下な命令を実行。
VAL0 = SCM_SUBR(VAL0)->func(ARGP, argc, SCM_SUBR(VAL0)->data);
上記は実質 Scm_VMCallCC 手続きの呼び出しになる。val0 に格納されている func な属性には stdlib.c で定義されている stdlib_call_with_current_continuation なポインタが格納されている (はず) で上図でてっぺんに push された手続きオブジェクトが Scm_VMCallCC に渡される模様。
で、まず上記なスタックが heap に退避される。継続フレームが退避される前に環境フレームも heap に退避。その上で R5RS な記述で言えば escape procedure なオブジェクトを生成して引数で渡された手続き (スタックに push された closure) に渡す。
こうして見るに Scm_VMCallCC という手続きは R5RS で言う脱出プロシージャ (escape procedure) を生成する手続きがほとんどで、生成のために継続フレームと環境フレームを heap に退避している事がわかる。3imp きちんと読まないといかんなぁ。
次
継続を補足してそれを呼び出した場合どうなるか。
gosh> (disasm (lambda () (cont -1))) main_code (name=#f, code=0x80d6890, size=4, const=1, stack=4): args: #f 0 CONSTI-PUSH(-1) 1 GREF-TAIL-CALL(1) #<identifier user#cont>; (cont -1) 3 RET #<undef> gosh>
引数を push して cont に束縛された手続きオブジェクトを val0 に格納して末尾呼び出し向けにスタックを調整 (詳細は略)。面倒臭いのでスタックのマンガを略しています。いかんなぁ。とは言え、このあと継続フレームが書きかわるはず。
とりあえず cont に束縛されている手続きオブジェクトの func 属性に格納されているのは throw_continuation です。上記 Scm_VMCallCC 手続きにてオブジェクトが生成。
中略しますが、ここから throw_cont_body 手続きが呼び出されて
- PC は RET を指す状態
- CONT は escape procedure なオブジェクトの cont 属性がセット
- vm->handlers もナニ
された後に通常であれば引数を一つ取り出して戻る。
その後
PC が RET を指しているのでスタックから継続フレームを取り出してナニ。詳細は以下と見ています。
int size__ = CONT->size; \ ARGP = SP = vm->stackBase; \ ENV = CONT->env; \ PC = CONT->pc; \ BASE = CONT->base; \ if (CONT->argp && size__) { \ ScmObj *s__ = CONT->argp, *d__ = SP; \ SP += size__; \ while (size__-- > 0) { \ *d__++ = *s__++; \ } \ } \ CONT = CONT->prev; \
ちょっと微妙。ちょっとここで一旦止めます。
続けてみる
これってスタックの底に保存した積みかけの引数をロードしているように読める。あとはきっちりスタック (正確にはそうでないですが) が元の状態に戻って続きがナニ。
この状態で ReadingGauche にデータ投入を試みてみます。