0%

编译原理-代码生成

阅读更多

1 代码生成器的主要任务

指令选择

  • 选择适当的目标机指令来实现中间表示IR)语句
  • 例:
    • 三地址语句:x=y+z
    • 目标代码
      • LDR0,y:把y的值加载到寄存器R0
      • ADDR0,R0,zz加到R0
      • STx,R0:把R0的值保存到x

寄存器分配和指派

  • 把哪个值放在哪个寄存器中

指令排序

  • 按照什么顺序来安排指令的执行

2 一个简单的目标机模型

三地址机器模型

  • 加载、保存、运算、跳转等操作
  • 内存按字节寻址
  • n个通用寄存器R0,R1,...,Rn1
  • 假设所有的运算分量都是整数
  • 指令之间可能有一个标号

2.1 目标机器的主要指令

  1. 加载指令:LDdst,addr
    • LDr,x
    • LDr1,r2
  2. 保存指令STx,r
  3. 运算指令OPdst,src1,src2
  4. 无条件跳转指令BRL
  5. 条件跳转指令Bcondr,L
    • 例:BLTZr,L

2.2 寻址模式

  1. 变量名a
    • 例:LDR1,a,即R1=contents(a)
  2. a(r)
    • a是一个变量,r是一个寄存器
    • 例:LDR1,a(R2),即R1=contents(a+contents(R2))
  3. c(r)
    • c是一个整数
    • 例:LDR1,100(R2),即R1=contents(contents(R2)+100)
  4. r
    • 在寄存器r的内容所表示的位置上存放的内存位置
    • 例:LDR1,R2,即R1=conents(contents(contents(R2)))
  5. c(r)
    • 在寄存器r中内容加上c后所表示的位置上存放的内存位置
    • 例:LDR1,100(R2),即R1=conents(contents(contents(R2)+100))
  6. #c
    • 例:LDR1,#100,即R1=100

3 指令选择

3.1 运算语句的目标代码

三地址语句

  • x=yz

目标代码

  • LDR1,y,即R1=y
  • LDR2,z,即R2=z
  • SUBR1,R1,R2,即R1=R1R2
  • STx,R1,即x=R1

尽可能避免使用上面的全部四个指令,如果满足以下条件,就可以删去某些目标代码

  • 所需的运算分量已经在寄存器中了
  • 运算结果不需要存放回内存

3.2 数组寻址语句的目标代码

访问

  • 三地址语句
    • b=a[i]
    • a是一个实数数组,每个实数占8个字节
  • 目标代码
    • LDR1,i,即R1=i
    • MULR1,R1,8,即R1=R18
    • LDR2,a(R1),即R2=contents(a+contents(R1))
    • STb,R2,即b=R2

赋值

  • 三地址语句
    • a[j]=c
    • a是一个实数数组,每个实数占8个字节
  • 目标代码
    • LDR1,c,即R1=c
    • LDR2,j,即R2=j
    • MULR2,R2,8,即R2=R28
    • STa(R2),R1,即contents(a+contents(R2))=R1

3.3 指针存取语句的目标代码

访问

  • 三地址语句
    • x=p
  • 目标代码
    • LDR1,p,即R1=p
    • LDR2,0(R1),即R2=contents(0+contents(R1))
    • STx,R2,即x=R2

赋值

  • 三地址语句
    • p=y
  • 目标代码
    • LDR1,p,即R1=p
    • LDR2,y,即R2=y
    • ST0(R1),R2,即contents(0+contents(R1))=R2

3.4 条件跳转语句的目标代码

三地址语句

  • ifx<ygotoL

目标代码

  • LDR1,x,即R1=x
  • LDR2,y,即R2=y
  • SUBR1,R1,R2,即R1=R1R2
  • BLTZR1,M,即ifR1<0jumptoM

3.5 过程调用和返回的目标代码

3.5.1 静态存储分配

方法调用

  • 三地址语句
    • callcallee
  • 目标代码
    • STcallee.staticArea,#here+20,即callee的活动记录在静态区中的起始位置
    • BRcallee.codeArea,即callee的目标代码在代码区中的起始位置

方法返回

  • 三地址语句
    • return
  • 目标代码
    • BRcallee.staticArea

3.5.2 栈式存储分配

方法调用

  • 三地址语句
    • callcallee
  • 目标代码
    • ADDSP,SP,#caller.recordsize
    • ST0(SP),#here+16
    • BRcallee.codeArea

方法返回

  • 三地址语句
    • return
  • 目标代码
    • 被调用过程
      • BR0(SP)
    • 调用过程
      • SUBSP,SP,#caller.recordsize

4 寄存器的选择

4.1 三地址语句的目标代码生成

对每个形如x=yopz的三地址指令I,执行如下动作

  1. 调用函数getreg(I)来为xyz选择寄存器,把这些寄存器称为RxRyRz
  2. 如果Ry中存放的不是y ,则生成指令“LDRy,y”。y是存放y的内存位置之一
  3. 类似的,如果Rz中存放的不是z,生成指令“LDRz,z
  4. 生成目标指令OPRx,Ry,Rz

4.2 寄存器描述符和地址描述符

寄存器描述符(register descriptor)

  • 记录每个寄存器当前存放的是哪些变量的值

地址描述符(address descriptor)

  • 记录运行时每个名字的当前值存放在哪个或哪些位置
  • 该位置可能是寄存器、栈单元、内存地址或者是它们的某个集合
  • 这些信息可以存放在该变量名对应的符号表条目中

4.3 基本块的收尾处理

对于一个在基本块的出口处可能活跃的变量x,如果它的地址描述符表明它的值没有存放在x的内存位置上,则生成指令“STx,R” (R是在基本块结尾处存放x值的寄存器)

4.4 管理寄存器和地址描述符

当代码生成算法生成加载、保存和其他指令时,它必须同时更新寄存器和地址描述符

  • 对于指令“LDR,x
    • 修改R的寄存器描述符,使之只包含x
    • 修改x的地址描述符,把R作为新增位置加入到x的位置集合中
    • 从任何不同于x的地址描述符中删除R
  • 对于指令“OPRx,Ry,Rz
    • 修改Rx的寄存器描述符,使之只包含x
    • 从任何不同于Rx的寄存器描述符中删除x
    • 修改x的地址描述符,使之只包含位置Rx
    • 从任何不同于x的地址描述符中删除Rx
  • 对于指令“STx,R
    • 修改x的地址描述符,使之包含自己的内存位置
  • 对于复制语句x=y,如果需要生成加载指令“LDRy,y”则
    • 修改Ry的寄存器描述符,使之只包含y
    • 修改y的地址描述符,把Ry作为新增位置加入到y的位置集合中
    • 从任何不同于y的变量的地址描述符中删除Ry
    • 修改Ry的寄存器描述符,使之也包含x
    • 修改x的地址描述符,使之只包含Ry

fig1

fig2

fig3

fig4

fig5

fig6

5 寄存器选择函数getReg的设计

fig7

fig8

5.1 寄存器Rx的选择

选择方法与Ry类似,区别之处在于

  • 因为x的一个新值正在被计算,因此只存放了x的值的寄存器Rx来说总是可接受的,即使x就是yz之一(因为我们的机器指令允许一个指令中的两个寄存器相同)
  • 如果y在指令I之后不再使用,且(在必要时加载y之后)Ry仅仅保存了y的值,那么,Ry同时也可以用作Rx 。对zRz也有类似选择
  • I是复制指令x=y时,选择好Ry后,令Rx=Ry

6 参考

7 窥孔优化

窥孔(peephole)是程序上的一个小的滑动窗口

窥孔优化是指在优化的时候,检查目标指令的一个滑动窗口(即窥孔) ,并且只要有可能就在窥孔内用更快或更短的指令来替换窗口中的指令序列

也可以在中间代码生成之后直接应用窥孔优化来提高中间表示形式的质量

7.1 具有窥孔优化特点的程序变换的例子

7.1.1 冗余指令删除

  1. 消除冗余的加载和保存指令
    • fig9
  2. 消除不可达代码:一个紧跟在无条件跳转之后的不带标号的指令可以被删除
    • fig10

7.1.2 控制流优化

在代码中出现跳转到跳转指令的指令时,某些条件下可以使用一个跳转指令来代替

  • fig11

7.1.3 代数优化

代数恒等式

  • 消除窥孔中类似于x=x+0x=x1的运算指令

强度削弱

  • 对于乘数(除数)是2的幂的定点数乘法(除法),用移位运算实现代价比较低
  • 除数为常量的浮点数除法可以通过乘数为该常量倒数的乘法来求近似值

7.1.4 特殊指令的使用

充分利用目标系统的某些高效的特殊指令来提高代码效率

  • 例如:INC指令可以用来替代加1的操作
  • 《MOOC-编译原理-陈鄞》
Powered By Valine
v1.5.2