4. wamcc方法
上述三个建议方法的共同点表现为,同样在一个单独模块内,引发一个大的功能,C编译器编译起来很痛苦。如果这些可能,额外的模块调用比内部模块调用开销更大。因此,一段程序分解在模块的方式,不仅影响编译时间,而且影响执行时间,呈明显反比。
我们wamcc系统的第二个版本的目标是翻译aWAM分支到一个本机代码jump。由于强制分解成几个功能,这些跳转应该到达一个函数内部的代码块。为了产生直接分支,我们必须确定静态标签(在编译时),而不是动态的(执行时)。“编译器+连接器”组合很适合做函数地址。在wamcc采取的办法是插入一个标签,在进入每个功能感谢ASM(...)指令。要操纵这些标签的地址,说L,只需要愚弄一下计算机,让它相信L是一个外部函数,声明为原型函数L并使用符号L(在C函数的名称是其地址)。然后,编译器生成一个带孔指令,将依据所有插入标签(内部和外部)的知识,由连接器填补。额外模块调用的成本完全和模块内部调用相同。要完成我们最喜欢的例子,将产生如下代码:
void label_p(); /* prototypes */ void label_p1(); void label_q(); void label_r(); #define Direct_Goto(lab) #define Indirect_Goto(p_lab) void fct_p() /* p:- q,r. */ { asm("label_p:"); push(CP); /* allocate */ CP=label-p1; /* call(q) */ Direct_Goto(label_q); /* : */ } void fct_p1() { asm("label_p1:"); pop(CP); /* deallocate */ Direct_Goto(label_r); /* execute(r) */ } void fct_q() /* q. */ { asm("label_q:"); Indirect_Toto(CP); /* proceed */ }只有两个宏直接或间接需要实现分支,他们依赖于及其的体系结构。例如对于一个RISC机器,我们有:
* 在这些伪函数内,可能有真函数调用。特别是大部分WAM指令相关的宏,扩展到调用wamcc库。这有可能改变代码大小(编译速度),对执行速度造成的(小)损害。
现在让我们详细描述上述开始计算所需要的代码。假设第一个谓词(通常是顶层)地址p_lab:
#include<setjmp.h> jmp_buf jumper; void Label_Success(); void Label_Fail(); Bool Call_Prolog(WamCont p_lab) { Create_Choice_Point(); ALTB(B)=Label_Fail; CP=Label_Success; ret_val=setjmp(jumper); if (ret_val==0) Call_Next(p_lab); Delete_Choice_Point(); return ret_val==2; } void Call_Next(WamCont p_lab) { int t[1024]; Indirect_Goto(p_lab); } void Call_Prolog_Success(void) { asm("Label_Success:"); longjmp(jumper,2); } void Call_Prolog_Fail(void) { asm("Label_Fail:"); longjmp(jumper,3); }Call_Prolog函数已执行谓词,地址是p_lab。它开始创建一个选择点,以便失败(Label失败)后记录分支地址。CP(Call_Prolog),表示谓词成功后执行哪些代码,由Label_Success初始化。最后,执行一个setjmp是为了随后能返回到指令。调用Call_Next函数在C堆栈,为可能的局部变量(对照:数组T声明)保留足够空间。控制权然后转交给谓词,它将像先前一样细致执行。成功(或失败)时,控制权转移至Label_Success(或Label_Failure)。将通过一个longjmp的第二个参数设置为value 2(或3),简单返回到Call_Prolog函数。
【2】在某些RISC处理器,指令后马上执行一个jump或函数调用(延迟槽)。因为在管道中已经准备好了。编译器试图在分支后通过移动一个相关指令使用这特性。如果这是不可能的,将生成一个nop指令。