我们将在本节详细介绍Janus,KL1,Erlang和wamcc如何处理控制流。此演示文稿的灵感来自[5],它采用了堆叠模型的目标。但是,我们不遵循类似于实际执行的抽象。这种选择的后果,明确描述了C代码与WAM指令的相关性。由于篇幅所限,我们只在这里讨论控制问题。首先是出于这样的事实,wamcc使用的WAM是传统而没有优化的。从而现在为其他指令写的代码变得众所周知了[1]。第二,有效控制的关键在于翻译成C,因为WAM代码是平的并且通过分支来执行转换。这是更适合高层次的控制结构,例如功能,并且对于低级别控制不提供更多。因此,主要问题将为WAM分支的翻译找到一个合适的解决方案。我们的演示将基于下面的例子中,只有一个子句和一个事实:
p: allocate /* p:- q, r. */ call(q) deallocate execute(r) q: proceed /* q. */然而,这个简单的例子显示了Prolog的控制在确定性的情况下使用的指令。翻译的方式调用和执行将尤其突出,如何管理直接分枝(即当目标地址是一个已知的标签),而进行指令翻译解决问题间接分枝(即当目标地址是某些变量的内容,在这种情况下注册CP)。
fct_switch() { label_switch: switch(PC){ case p: /* p:- q,r . */ label_p: push(CP); /* allocate */ CP=p1; /* call(q) */ goto label_q; /* " */ case p1: pop(CP); /* deallocate */ goto label_r; /* execute(r) */ case q: /* q. */ label_q: PC=CP; /* proceed */ goto label_switch; /* : */ . . . } }这种方法在RISC机器花费大,因为switch语句的成本约为10个机器指令(包括边界检查)。然而,这种方法的主要的缺点:是一个程序上升到一个单一的功能。因此,除玩具的例子,它会产生一个巨大的功能,C编译器是无法在合理的时间内处理。如果这样设置,应对模块化是不容易的。它要求每个谓词调用一个咨询动态表,以便通过本模块,控制开关功能。此外,为支持一个完整的Prolog,也在上下文变化情况下时关注正确处理回溯。因此,支持模块化是惩罚,并且一个额外的模块调用将比一个模块内调用花费大得多。
3.1 Janus
Janus实现是基于一个简单的思想,通过一个C分支翻译成WAM分支,即一个goto指令。类似的方法,如[11]中描述的在Prolog编译器中使用。但是出现了问题,因为简介分支是标准C(ANSI C)不具备的(因此必须模拟),也因为goto指令只能处理在同一代码功能。该解决方案导致一个C程序组成的一个独特的功能一个开关指令来模拟间接GOTO语句。这种方法之后,我们前面的例子将被转换为:
fct_switch() { label_switch: switch(PC){ case p: /* p:- q,r */ label_p: push(CP); /* allocate */ CP=p1; /* call(q) */ goto label_q; /* : */ case p1: pop(CP); /* deallocate */ goto label_r; /* execute(r) */ case q: /* q. */ label_q: PC=CP; /* proceed */ goto label_switch; /* : */ . . . } }这种方法在RISC机器上花费昂贵,因为switch语句的成本约为10个机器指令(包括边界检查)。然而,主要的缺点这种方法是一个程序,上升到一个单一的功能。因此,除玩具的例子,它会产生一个巨大的功能,C编译器是无法在合理的时间处理。在此设置下,应对模块化是不容易的,它要求每个谓词调用一个咨询动态表,以便通过本模块,控制开关功能。此外,为支持一个完整的Prolog,也照顾环境的变化的情况下,正确处理回溯。因此,支持模块化是惩罚性的,并且一个额外的模块调用将比一个模块内调用昂贵得多。
3.2 KL1
作为一个C函数的汇编是不现实的,代表C程序WAM代码切片成几个功能。每一个Prolog谓词看起来那么自然地翻译成一个C函数。WAM分枝会给上升到函数调用。这样一个函数在返回之前调用另一个嵌套函数(分支),依此类推;如此,它永远不会返回之前结束程序。因此,在C控制栈中积累的数据无用,可导致内存溢出。解决的办法是执行一个分支之前从任何一个函数得到返回,并有一个过程监督器使分支得到足够的延续。这导致了下面我们继续的代码示例:
fct_supervisor() { while(PC) (*PC)(); } void fct_p() /* p:- q,r, */ { push(CP); /* allocate */ CP=fct_p1; /* call(q) */ PC=fct_q; /* : */ } void fct_p1 { pop(CP); /* deallocate */ PC=fct_r; /* execute(r) */ } void fct_q() /* q. */ { PC=CP; /* proceed */ }描绘上面的代码可以抑制PC寄存器优化,可以返回其信息的功能。因此,当传递控制和需要分支时,每个功能实现行计算和终端地址返回。这种方法的分析表明一个WAM分支在一个函数调用后,由一个返回实施到监管者。这花费显然明显高于简单jump指令而将产生一个本地代码编译器。然而,额外的模块调用现在可能无需支付额外费用。首次实施的wamcc使用这种技术,并且比仿真Sicstus慢两倍左右。KL1为了减少函数调用和返回,进行了权衡:同一模块内德所以谓词被翻译成一个单一的功能。因此,当只有一个模块,KL1行为像Janus。监管功能只需要对额外模块调用上下文切换,因此成本超过内部模块调用。
最后,让我们陈述这种方法(无论是否改善KL1建议)是最适合为100%的ANSI C的解决方案。
3.3 Erlang
void fct_p() /* p:- q,r. */ { jmp_tbl[p]=&&label_p; /* (initialization) */ jmp-tbl[p1]=&&labe_p1; return; label_p: push(CP); /* allocate */ CP=&&label_p1; /* call(q) */ goto *jum-tbl[q]; /* : */ label_p1: pop(CP); /* deallocate */ goto *jmp-tbl[r]; /* execute(r) */ } void fct_q() /* q. */ { jmp_tbl[q]=&&label_1; /* (initialization) */ return; label_q: goto *CP; /* proceed */ }所有的分支都通过一个全局地址表间接goto。为了消除间接代替直接跳转的开销,Erlang像KL1或Janus,一个给定模块的所有谓词编译成一个单一功能。因此,只有额外的模块调用需要全局地址表的咨询,并会就此内部模块调用更加昂贵。