1

我一直在研究PascalScript 脚本引擎的第 14 期,其中使用 Goto 命令跳出 Case 块会产生编译器错误,即使这是完全有效(如果丑陋)的 Object Pascal 代码。

结果编译器中的 ProcessCase 例程调用 HasInvalidJumps,它会扫描任何在 Case 块之外的 Goto,如果找到,则会给出编译器错误。如果我评论检查,它编译得很好,但最终在运行时崩溃。字节码的反汇编说明了原因。我已经用原始脚本代码对其进行了注释:

[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

“转到 L1;” 103 处的语句跳过了 113 和 114 处的清理弹出,这使堆栈处于无效状态。

Delphi 对此没有任何问题,因为它不使用计算堆栈。然而,PascalScript 就没有那么幸运了。我需要一些方法来完成这项工作,因为这种模式在一些来自更简单系统的遗留脚本中非常常见,而我已经翻译成 PascalScript 并且需要能够支持的控制结构的方式很少。

任何人都知道如何修补 codegen 以便正确清理堆栈?

4

3 回答 3

3

IIRC 经典帕斯卡的 goto 规则是:

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

后者从未得到 Borland 派生的 Pascals 的支持,但第一个仍然成立。

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

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

请记住,if 评估及其分支的 begin..end 块可能已生成需要清理的临时文件。

---------- 稍后添加

我认为代码生成器需要一种方法来遍历 goto 及其端点之间的范围,并在此过程中为块生成相关的退出代码。这样,修复程序适用于一般情况,而不仅仅是此示例。由于您只能跳出范围,而不能进入范围,因此可能并不那么难。

IOW 生成的东西相当于(对于假设的双案例块)

Lgoto1gluecode: // 退出代码第一个块 pop x pop y // 退出代码第一个块 pop A pop B goto real_goto_destination

可以进行额外的分析。例如,如果只有一个范围,并且它已经有一个清理退出标签,您可以直接跳转。如果您确定上述 pop 只是丢弃的值(而不是保存寄存器),您可以通过 add $16,%esp(4*4 字节值)等立即执行它们。

于 2009-06-22T16:39:51.990 回答
1

直接的解决方案是:

在为 goto 语句生成 GOTO 时,在 GOTO 前面加上 RET 之前的相同清理代码。

于 2009-06-21T21:55:35.413 回答
1

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

于 2009-06-22T17:04:27.917 回答