このコードを先頭から順に説明する。
2 #include <klic/klichdr.h>
3 #include "atom.h"
4 #include "funct.h"
6 module module_main();
7 Const struct predicate predicate_main_xmain_0 =
8 { module_main, 0, 0 };
9 Const struct predicate predicate_main_xnrev_2 =
10 { module_main, 1, 2 };
11 Const struct predicate predicate_main_xappend_3 =
12 { module_main, 2, 3 };
13 extern Const struct predicate predicate_klicio_xklicio_1;
15 module module_main(glbl, qp, allocp, toppred)
16 struct global_variables *glbl;
17 struct goalrec *qp;
18 register q *allocp;
19 Const struct predicate *toppred;
20 {
[1,2,3]を実現するリスト構造が
定義されている。このように、コンパイル時に確定している構造は、
コンパイル時に静的に生成される(
ページ、第3.4.2章参照)。
21 static Const q cons_const_0[] = {
22 NILATOM,
23 makeint(3),
24 };
25 static Const q cons_const_1[] = {
26 makecons(cons_const_0),
27 makeint(2),
28 };
29 static Const q cons_const_2[] = {
30 makecons(cons_const_1),
31 makeint(1),
32 };
33 static Const q cons_const_3[] = {
34 NILATOM,
35 makesym(atom_nl),
36 };
37 q a0, a1, a2;
39 q *reasonp;
40 module_top:
41 switch_on_pred() {
42 case_pred(0, main_0_top);
43 case_pred(1, nrev_2_top);
44 last_case_pred(2, append_3_top);
45 }
47 main_0_top: {
48 q x0, x1, x2, x3, x4, x5;
49 qp = qp->next;
50 main_0_clear_reason:
51 reasonp = reasons;
nrev([1,2,3],Y)に対応するゴールレコード
が生成される。allocpとあるのはヒープ割り付けである。このように
KLICではゴールレコードもヒープ中に生成される。ゴールレコードのnextに
相当する先頭にはqpが格納されている。
また、基本的にKLICプログラム中で記述した順とは逆にゴールが生成され、 エンキューされていく。 56行目で純粋未定義変数を生成しているのに注意。
52 main_0_0:
53 allocp[0] = (q)qp;
54 allocp[1] = (q)(&predicate_main_xnrev_2);
55 allocp[2] = makecons(cons_const_2);
56 allocp[3] = x0 = makeref(&allocp[3]);
57 allocp[4] = makesym(functor_putt_1);
58 allocp[5] = x0;
59 x1 = makefunctor(&allocp[4]);
60 allocp[6] = makecons(cons_const_3);
61 allocp[7] = x1;
62 x2 = makecons(&allocp[6]);
63 allocp[8] = makesym(functor_normal_1);
64 allocp[9] = x2;
65 x3 = makefunctor(&allocp[8]);
66 allocp[10] = makesym(functor_stdout_1);
67 allocp[11] = x3;
68 x4 = makefunctor(&allocp[10]);
69 allocp[12] = NILATOM;
70 allocp[13] = x4;
71 x5 = makecons(&allocp[12]);
72 allocp[14] = (q)(struct goalrec*)&allocp[0];
73 allocp[15] = (q)(&predicate_klicio_xklicio_1);
74 allocp[16] = x5;
75 qp = (struct goalrec*)&allocp[14];
76 allocp += 17;
77 proceed();
ここでは、さらに実行するゴールがないため、
ゴールスタックのデキュー操作を行うためproceed()が出力されている
(
ページ、第3.3.2章参照)。
78 main_0_ext_interrupt:
79 reasonp = 0l;
80 main_0_interrupt:
84 nrev_2_top: {
85 q x0, x1, x2;
86 a0 = qp->args[0];
87 a1 = qp->args[1];
88 qp = qp->next;
89 nrev_2_clear_reason:
90 reasonp = reasons;
91 nrev_2_0:
92 nrev_2_1:
93 switch (ptagof(a0)) {
94 case CONS:
95 allocp[0] = NILATOM;
96 x1 = car_of(a0);
97 allocp[1] = x1;
98 x0 = makecons(&allocp[0]);
99 allocp[2] = (q)qp;
100 allocp[3] = (q)(&predicate_main_xappend_3);
101 allocp[4] = x2 = makeref(&allocp[4]);
102 allocp[5] = x0;
103 allocp[6] = a1;
104 a0 = cdr_of(a0);
105 a1 = x2;
106 qp = (struct goalrec*)&allocp[2];
107 allocp += 7;
108 execute(nrev_2_0);
109 goto nrev_2_ext_interrupt;
[A] を組みたて、x0にセットする。
ここでは、nrev/2が「直接呼び出し」されている。
この時には、引数レジスタ群(a0, a1, ...)は、呼び出された先でもそのまま (ゴールからひきあげることなく)利用されるため、executeまでの間に 引数レジスタ群に適当な引数を用意しておく必要がある。
Xを第0引数ににセット。
X1(x2に載っている)を第一引数にセット。
#define execute(label)\
{\
if (allocp < heaplimit) goto label;\
}
ここで、allop とheaplimitを比較している。これは第3.2章 (
ページ)
で述べた、「例外的処理の有無」の検出である。
ここで「例外的処理処理」要求が検出できなかった場合には
指定されたラベル(ここでは、
nrev_2_0 に飛ぶ)。検出された場合には109行以降の処理を行う。
nrev_2_0 とはすでに経過した部分であり、91行目にある。
これは、「ゴールから引数が引き上げ」られたのと「引数の検査」の
切れ目に存在するラベルである。これは、execute実行の場合には前述のように
引数(axレジスタ)はすでに正しい内容を持っているため「ゴールからの引数の
引き上げ」は不要であり、省略するためにこのようなラベルに飛ぶようになっている。
つまり、述語が開始されるには、一般的には以下の2通りがある(
ページ、第3.3.2章参照)。
ページ、第4.2.2章参照)。
interrupt_2 とは、「2引数用の割出し処理
ラベル」であり、引数レジスタ上の2つの引数をゴール引数部に格納してから、
「他の処理」の実行のため、関数interrupt_goalに制御を移す。
111 if (a0 != NILATOM) goto nrev_2_interrupt; 112 x0 = NILATOM; 113 unify_value(a1, x0); 114 proceed();
ページ、第4.2.2章参照)。
その後引数を退避し、関数interrupt_goal に制御を移す。
Y=[] が実行される。unify_value は、第2引数が具体値であることが
判明しているときに呼びだされるmacroで、一般的にはdo_unify()が呼びだされる。
これら2つはマクロであり、include/klic/unify.h に定義されている。
unify_value の場合には第0引数が純粋未定義変数である場合には、
実行時ルーチンを呼ばずに、その場で具体化を行う。
この「単純な場合」以外は、
runtime/unify.cで
定義されているC関数、do_unify, do_unify_valueが呼びだされる。
115 case VARREF: 116 deref_and_jump(a0,nrev_2_1); 117 *reasonp++ = a0; 118 goto nrev_2_interrupt;
116行目のderef_and_jumpはinclude/klic/index.hに定義されるマクロで、 deref_and_jump(レジスタ、ラベル)という引数をとる。 具体的には以下のようなマクロである。
#define deref_and_jump(x, loop) \
{ \
q temp0 = derefone(x); \
if(!isref(temp0)) { \
(x) = temp0; \
goto loop; \
} \
}
これを以下で解説する。
117行目では、(少なくとも2段以上)REFであったa0を中断スタックに詰み、 nrev_2_interrptに飛ぶ。その結果、最終的には 引数を退避し、関数interrupt_goal()が呼びだされる。
127 append_3_top: {
128 q x0, x1, x2;
129 a0 = qp->args[0];
130 a1 = qp->args[1];
131 a2 = qp->args[2];
132 qp = qp->next;
133 append_3_clear_reason:
134 reasonp = reasons;
137 switch (ptagof(a0)) {
138 case CONS:
139 allocp[0] = x1 = makeref(&allocp[0]);
140 x2 = car_of(a0);
141 allocp[1] = x2;
142 x0 = makecons(&allocp[0]);
143 allocp += 2;
144 unify_value(a2, x0);
145 a0 = cdr_of(a0);
146 a2 = x1;
147 execute(append_3_0);
148 goto append_3_ext_interrupt;
149 case ATOMIC: 150 if (a0 != NILATOM) goto append_3_interrupt; 151 unify(a1, a2); 152 proceed();
153 case VARREF: 154 deref_and_jump(a0,append_3_1); 155 *reasonp++ = a0; 156 goto append_3_interrupt;
164 interrupt_3: 165 allocp[4] = a2; 166 interrupt_2: 167 allocp[3] = a1; 168 interrupt_1: 169 allocp[2] = a0;
ページ)を参照のこと。
170 interrupt_0: 171 allocp = interrupt_goal(allocp, toppred, reasonp);
なお、interupt_goals(allocp, toppred, reasonp)の関数specは以下の通り。
また、戻り値としては更新されたヒープ割付点を返す。
どちらにしても、このinterrupt_goalが呼び出された後には、 レジスタなどは「中途半端 な状態」にはなく、すべてgoal recordに 収められている。
172 proceed_label: 173 loop_within_module(module_main);
具体的には 以下のような定義である。
#define loop_within_module(f) \
{ \
module (*func)(); \
if (allocp >= heaplimit) { \
allocp = klic_interrupt(allocp, qp); \
qp = current_queue; \
} \
if ((func = (toppred = qp->pred)->func) == (f)) \
goto module_top; \
heapp = allocp; \
current_queue = qp; \
return (module) func; \
}
ここでは、
先に「例外的な処理」の検査、措置を行う理由は、以下の通りである。