2009-06-21 41 views
1

我一直在使用PascalScript脚本引擎上的Issue 14,其中使用Goto命令跳出Case区块会产生编译器错误,即使这是完全有效的(如果难看的话)Object Pascal代码。跳出某个块时,是否有安全的方式来清除基于堆栈的代码?

发现编译器中的ProcessCase例程调用HasInvalidJumps,它扫描在Case区块外面导致的任何Gotos,并在找到一个时发出编译器错误。如果我发表评论,它编译得很好,但最终会在运行时崩溃。反汇编字节码显示了原因。我已用原始脚本代码对其进行注释:

[TYPES] 
<SNIPPED> 
[VARS] 
Var [0]: 27 Class TFORM 
Var [1]: 28 Class TAPPLICATION 
Var [2]: 11 S32 //i: integer 
[PROCS] 
Proc [0] Export: !MAIN -1 
{begin} 
[0] ASSIGN GlobalVar[2], [1] 
{ i := 1;} 
[15] PUSHTYPE 11(S32) // 1 
[20] ASSIGN Base[1], GlobalVar[2] 
{ case i of} 
[31] PUSHTYPE 25(U8) // 2 
{ 0:} 
[36] COMPARE into Base[2]: [0] = Base[1] 
[57] COND_NOT_GOTO currpos + 5 Base[2] [72] 
{ end;} 
[67] GOTO currpos + 41 [113] 
{ 1:} 
[72] COMPARE into Base[2]: [1] = Base[1] 
[93] COND_NOT_GOTO currpos + 10 Base[2] [113] 
{  goto L1;} 
[103] GOTO currpos + 8 [116] 
{ end;} 
[108] GOTO currpos + 0 [113] 
{ end; //<-- case} 
[113] POP // 1 
[114] POP // 0 
{ Exit;} 
[115] RET 
{L1: 
Writeln('Label L1');} 
[116] PUSHTYPE 17(WideString) // 1 
[121] ASSIGN Base[1], ['????????'] 
[144] CALL 1 
{end.} 
[149] POP // 0 
[150] RET 
Proc [1]: External Decl: \00\00 WRITELN 

“goto L1;”在103处的语句跳过113和114处的清除弹出,这使得堆栈处于无效状态。

德尔福没有任何问题,因为它不使用计算堆栈。然而,PascalScript并不如此幸运。我需要一些方法来完成这项工作,因为这种模式在一些比较简单的系统的传统脚本中非常常见,而且很少有我已经转换为PascalScript并需要支持的控制结构。

任何人有任何想法如何打补丁Codegen所以它会正确地清理堆栈?

回答

1

的简单的解决办法是:

当生成用于GOTO goto语句,前缀GOTO与RET之前出现相同的清理代码。

+0

虽然展开堆栈可能在这里工作,但我不确定它适用于所有情况。 – skamradt 2009-06-22 16:55:44

+0

明天你会得到一个跳出两个嵌套case语句的goto,但是在标签后面有一些代码,你可以从 – 2009-06-26 21:20:04

3

IIRC经典帕斯卡转到规则是:

  • 跳跃只允许一个块外(督察从高到较低嵌套级别上树的“相同”分支)
  • 从当地程序到他们的父母。

后来是afaik从来没有得到Borland派生帕斯卡支持,但第一个仍然成立。

所以你需要像马丁说的那样生成现有的代码,但可能它是用于多个块级别的,所以你不能为每个goto代码生成代码,但必须生成代码(以退出需要的精确数量块)。

一个典型的测试模式是使用goto退出多个嵌套ifs(可能在一个循环中),因为这是一个经典的微优化,其速度至少高达D7。

请记住,如果评估和其分支的begin..end块可能产生了需要清理的临时数据。

----------后

加入我觉得codegenerator需要一种方式来走GOTO和其端点之间的范围,产生了沿途块相关的退出代码。这样一个修补程序适用于一般情况,而不仅仅是这个例子。 既然你只能跳出范围,而不是跳入范围,那可能不那么难。

IOW生成的东西,它等效于(对于一个假想的双壳体块)

Lgoto1gluecode: //退出代码第一框 弹出X 弹出ý //退出代码第一框 弹出甲 弹出B 转到real_goto_destination

可以进行额外的分析。例如。如果只有一个范围,并且它已经有一个清理出口标签,则可以直接跳转。如果您确定上述弹出窗口只是丢弃值(而不是保存寄存器),您可以一次添加$ 16,%esp(4 * 4字节值)等值。

1

它看起来像我计算跳远有多远是个问题。我将不得不花费一些时间来查看解析器的实现以进一步提供帮助,但我的猜测是,使用goto时必须执行额外的处理,并且堆栈中有值,并且goto会放在这些值之后从堆栈中移除。当然,要确定这一点,您需要将正在解析的当前位置(goto)和正向解析保存到目标位置,以监视堆栈更改,如果是,则要么向后调整goto位置,要么将代码注入Martin建议。