wamcc:将Prolog编译成C (No.7-4)

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机器,我们有:
    * Direct_Goto(lab)简单的调用标签功能。
    * Direct_Goto(p_lab)调用一个函数,名称(地址)将储存在p_lab。
事实上,一个RISC机器上的一个函数调用指令,通过控制给定的地址(如一个jump),并初始化处理器的继续指针。由于RISC架构,这个指令是一个简单的快速jump。如无堆积,它可以被用来分支(事实上继续指针的更新并不重要,因为我们知道这是不是一个真正的函数调用,而仅仅是jump)。这样做可以避免需要插入jump指令的汇编代码。此外,RISC分支指令只能访问代码比较接近当前指令,函数调用指令不遵循这个限制。由于模块分解,这确实需要访问代码比较深。让我们终于注意到离开生成函数调用的C编译器,允许它来优化延迟槽指令pipeline【2】的优势。总结:
    * 直接jump是为尽可能快地执行,因为它们被翻译成本机代码jump(或RISC架构的情况下以相同开销进入函数调用)。
    * 额外模块调用不超过内部模块调用。
    * 相比以往的方法,其中一个单独的模块的所有谓词被编译成一个单一的功能,这种方法产生了和子句体里一样多的函数(头和第一目标计数)。因此产生的代码编译速度更快(见后面)。
    * 每个功能已经开始的时候只有一个直接的切入点。因此,只jump过了序言。为了允许本地变量,由定义一个数组中的一个中间函数开始计算,一些(足够大)的空间保留在C控制堆栈。这样C堆栈指针SP指向数组的末尾。局部变量将被分配在此阵列(见下文)。
    * 这只是这种方法的假设。因此,序言除了递减SP什么也不做。这是一般情况,除了少数情况下机器的C编译器不通过SP引用局部变量,但通过另一个FP寄存器(帧指针)将函数入口设为SP。这旨在帮助调试器,它通常根据编译器选项,可能停用此操作。在此情况下,人们不可能总是生成一个汇编指令初始化这个FP寄存器。

    * 在这些伪函数内,可能有真函数调用。特别是大部分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指令。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值