一种简单的方法是将窥视孔优化器实现为有限状态机。
我们假设您有一个原始代码生成器,该 生成 器 生成 指令但不 发出 指令,还有一个 发出 例程,该例程将实际代码发送到对象流。
状态机捕获代码生成器生成的指令,并通过在状态之间进行转换来记住0个或多个生成的指令的序列。因此,状态会隐式记住已生成但未发出的指令的(简短)序列;它还必须记住已捕获指令的关键参数,例如寄存器名称,常数值和/或寻址模式以及抽象目标存储器位置。特殊的开始状态会记住指令的空字符串。在任何时候,您都需要能够发出未被忽略的指令(“刷新”);如果您一直这样做,则窥孔生成器会捕获下一条指令,然后发出该指令,而不会做任何有用的工作。
为了完成有用的工作,我们希望机器捕获尽可能长的序列。由于通常有多种类型的机器指令,因此实际上您不会连续记住太多,否则状态机将变得巨大。但是,记住最常见的机器指令(装入,添加,cmp,分支,存储)的最后两三个是实用的。机器的大小实际上取决于我们要进行的最长窥孔优化的长度,但是,如果该长度为P,则整个机器不必处于P状态深。
根据我的代码生成器生成的“下一条”指令,每个状态都转换为下一状态。想象一个状态代表捕获N条指令。过渡选项包括:
- 刷新该状态表示的最左边的0个或多个(称为k)指令,并转换到表示N-k + 1的下一个状态,该指令表示对机器指令I的附加捕获。
- 刷新该状态表示的最左边的k条指令,转换到表示其余Nk条指令的状态,然后重新处理指令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解析器状态机的生成。我不在这里解释:-}



