【问题标题】:Is there a safe way to clean up stack-based code when jumping out of a block?跳出块时是否有一种安全的方法来清理基于堆栈的代码?
【发布时间】:2009-06-21 18:13:59
【问题描述】:

我一直在研究 PascalScript 脚本引擎上的 Issue 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 以便正确清理堆栈?

【问题讨论】:

    标签: delphi goto pascalscript


    【解决方案1】:

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

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

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

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

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

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

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

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

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

    Lgoto1gluecode: // 退出代码第一个块 流行x 流行音乐 // 退出代码第一个块 流行音乐 流行音乐B 转到 real_goto_destination

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

    【讨论】:

    • 是的,在同一过程中从一个块到更高的分支是我在这里想要完成的。
    【解决方案2】:

    直接的解决方案是:

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

    【讨论】:

    • 虽然展开堆栈可能在这里有效,但我不确定它是否适用于所有情况。
    • 明天你会得到一个退出两个嵌套 case 语句的 goto,但标签后面确实有一些代码,你可以重新开始
    【解决方案3】:

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

    【讨论】:

      猜你喜欢
      • 2015-12-01
      • 1970-01-01
      • 2014-12-19
      • 1970-01-01
      • 2017-11-03
      • 1970-01-01
      • 1970-01-01
      • 2012-06-16
      • 2020-06-12
      相关资源
      最近更新 更多