用Java编写的编译器:Peephole优化器实现

我正在为Pascal的一个子集编写编译器。编译器为编造的机器生成机器指令。我想为这种机器语言编写一个窥视孔优化器,但我在替换一些更复杂的模式时遇到了麻烦。

窥视孔优化器规格

我已经研究了几种不同的方法来编写窥视孔优化器,并且我已经确定了后端方法:

  • 编码器在每次生成机器指令时调用函数。emit()
  • emit(Instruction currentInstr)检查窥视孔优化表:
    • 如果当前指令与模式的尾部匹配:
      1. 检查以前发出的匹配说明
      2. 如果所有指令都与模式匹配,则应用优化,修改代码存储的尾端
    • 如果未找到优化,请照常发出指令

当前设计方法

该方法非常简单,这是我遇到麻烦的实现。在我的编译器中,机器指令存储在一个类中。我写了一个类存储正则表达式,旨在匹配机器指令的每个组件。如果模式与某些机器指令匹配,则返回其方法。InstructionInstructionMatchequals(Instruction instr)trueinstr

但是,我无法完全应用我的规则。首先,我觉得以我目前的方法,我最终会得到一堆不必要的东西。鉴于窥视孔优化数字的完整列表可以有大约400种模式,这将迅速失控。此外,使用这种方法,我实际上无法获得更困难的替换(请参阅“我的问题”)。

替代方法

我读过的一篇论文将以前的指令折叠成一个长字符串,使用正则表达式进行匹配和替换,并将字符串转换回机器指令。这对我来说似乎是一个糟糕的方法,如果我错了,请纠正我。

示例模式、模式语法

x: JUMP x+1; x+1: JUMP y  -->  x: JUMP y
LOADL x; LOADL y; add     -->  LOADL x+y
LOADA d[r]; STOREI (n)    -->  STORE (n) d[r]

请注意,这些示例模式中的每一个都只是以下机器指令模板的人类可读表示形式:

op_code register n d

(n通常表示字数,d表示地址位移)。语法指示指令存储在代码存储中的地址。x: <instr>x

因此,当操作码为5时,该指令等效于整机指令(并且在此指令中未使用)LOADL 175 0 0 17LOADLnr

我的问题

因此,鉴于该背景,我的问题是:当我需要在替换中包含先前指令的部分作为变量时,如何有效地匹配和替换模式?例如,我可以简单地用增量机器指令替换所有实例 - 我不需要前面指令的任何部分来执行此操作。但是我不知道如何在替换模式中有效地使用我的第二个示例的“x”和“y”值。LOADL 1; add

编辑:我应该提到一个类的每个字段只是一个整数(对于机器指令来说是正常的)。在模式表中使用“x”或“y”都是一个变量,用于表示任何整数值。Instruction


答案 1

一种简单的方法是将窥视孔优化器实现为有限状态机。

我们假设您有一个生成指令但不发出指令的原始代码生成器,以及一个将实际代码发送到对象流的 emit 例程。

状态机捕获代码生成器生成的指令,并通过在状态之间转换来记住 0 个或更多生成指令的序列。因此,状态隐式记住生成但未发出的指令的(短)序列;它还必须记住它捕获的指令的关键参数,例如寄存器名称,常量值和/或寻址模式和抽象目标存储器位置。特殊的启动状态会记住空的指令字符串。在任何时候,您都需要能够发出未发出的指令(“冲洗”);如果你一直这样做,你的窥视孔生成器会捕获下一条指令,然后发出它,没有任何有用的工作。

为了做有用的工作,我们希望机器捕获尽可能长的序列。由于机器指令通常有很多种,因此实际上您无法连续记住太多,否则状态机将变得巨大。但是,记住最常见的机器指令(加载,添加,cmp,分支,存储)的最后两个或三个是可行的。机器的大小实际上将由我们关心的最长窥视孔优化的长度决定,但如果该长度为P,则整个机器不必是P状态深。

每个状态都有基于代码生成器生成的“next”指令转换为下一个状态。想象一下,一个状态表示N条指令的捕获。过渡选项包括:

  • 刷新此状态表示的最左边的 0 个或更多(调用此 k)指令,并转换到下一个状态(表示 N-k+1)指令,该指令表示机器指令 I 的附加捕获。
  • 刷新此状态表示的最左侧的 k 条指令,转换为表示其余 N-k 条指令的状态,然后重新处理指令 I。
  • 完全刷新状态,并发出指令 I。[您实际上可以在启动状态下执行此操作]。

当刷新 k 指令时,实际发出的是这些 k 的窥视孔优化版本。在发出此类指令时,您可以计算所需的任何内容。您还需要记住“转移”其余指令的参数。

这一切都很容易通过一个窥视孔优化器状态变量实现,并且在代码生成器生成下一条指令的每个点上都有一个case语句。case 语句更新窥视孔优化器状态并实现转换操作。

假设我们的机器是一个增强的堆栈机器,有

 PUSHVAR x
 PUSHK i
 ADD
 POPVAR x
 MOVE x,k

指令,但原始代码生成器只生成纯堆栈机器指令,例如,它根本不发出MOV指令。我们希望窥视孔优化器执行此操作。

我们关心的窥视孔案例是:

 PUSHK i, PUSHK j, ADD ==> PUSHK i+j
 PUSHK i, POPVAR x ==> MOVE x,i 

我们的状态变量是:

 PEEPHOLESTATE (an enum symbol, initialized to EMPTY)
 FIRSTCONSTANT (an int)
 SECONDCONSTANT (an int)

我们的案例陈述:

GeneratePUSHK:
    switch (PEEPHOLESTATE) {
        EMPTY: PEEPHOLESTATE=PUSHK;
               FIRSTCONSTANT=K;
               break;
        PUSHK: PEEPHOLESTATE=PUSHKPUSHK;
               SECONDCONSTANT=K;
               break;
        PUSHKPUSHK:
        #IF consumeEmitLoadK // flush state, transition and consume generated instruction
               emit(PUSHK,FIRSTCONSTANT);
               FIRSTCONSTANT=SECONDCONSTANT;
               SECONDCONSTANT=K;
               PEEPHOLESTATE=PUSHKPUSHK;
               break;
        #ELSE // flush state, transition, and reprocess generated instruction
               emit(PUSHK,FIRSTCONSTANT);
               FIRSTCONSTANT=SECONDCONSTANT;
               PEEPHOLESTATE=PUSHK;
               goto GeneratePUSHK;  // Java can't do this, but other langauges can.
        #ENDIF
     }

  GenerateADD:
    switch (PEEPHOLESTATE) {
        EMPTY: emit(ADD);
               break;
        PUSHK: emit(PUSHK,FIRSTCONSTANT);
               emit(ADD);
               PEEPHOLESTATE=EMPTY;
               break;
        PUSHKPUSHK:
               PEEPHOLESTATE=PUSHK;
               FIRSTCONSTANT+=SECONDCONSTANT;
               break:
     }  

  GeneratePOPX:
    switch (PEEPHOLESTATE) {
        EMPTY: emit(POP,X);
               break;
        PUSHK: emit(MOV,X,FIRSTCONSTANT);
               PEEPHOLESTATE=EMPTY;
               break;
        PUSHKPUSHK:
               emit(MOV,X,SECONDCONSTANT);
               PEEPHOLESTATE=PUSHK;
               break:
     }

GeneratePUSHVARX:
    switch (PEEPHOLESTATE) {
        EMPTY: emit(PUSHVAR,X);
               break;
        PUSHK: emit(PUSHK,FIRSTCONSTANT);
               PEEPHOLESTATE=EMPTY;
               goto GeneratePUSHVARX;
        PUSHKPUSHK:
               PEEPHOLESTATE=PUSHK;
               emit(PUSHK,FIRSTCONSTANT);
               FIRSTCONSTANT=SECONDCONSTANT;
               goto GeneratePUSHVARX;
     }

#IF显示了两种不同样式的转换,一种使用生成的指令,另一种不使用;任何一种都适用于此示例。当你最终得到几百个这样的case语句时,你会发现这两种类型都很方便,“不要消耗”版本可以帮助你保持代码更小。

我们需要一个例程来刷新窥视孔优化器:

  flush() {
    switch (PEEPHOLESTATE) {
        EMPTY: break;
        PUSHK: emit(PUSHK,FIRSTCONSTANT);
               break;
        PUSHKPUSHK:
               emit(PUSHK,FIRSTCONSTANT),
               emit(PUSHK,SECONDCONSTANT),
               break:
      }
      PEEPHOLESTATE=EMPTY;
      return; }

有趣的是,考虑一下这个窥视孔优化器如何处理以下生成的代码:

      PUSHK  1
      PUSHK  2
      ADD
      PUSHK  5
      POPVAR X
      POPVAR Y

整个FSA方案的作用是在状态转换中隐藏您的模式匹配,并在情况下隐藏对匹配模式的响应。您可以手动编写代码,并且编码和调试速度很快且相对容易。但是,当案例数量增加时,您不想手动构建这样的状态机。您可以编写一个工具来为您生成此状态机;良好的背景是FLEX或LALR解析器状态机的生成。我不打算在这里解释这一点:-}


答案 2

推荐