中间代码生成
中间代码/中间表示 (Intermediate Representation, IR)
AST -> IR1 -> IR2 -> … -> IRk -> asm
IR 是一种抽象的机器语言
实际的编译器可能使用多层 IR 以支持不同层次的分析和变换
依据 IR 的结构特征
三地址码( Three-Address Code)
x = y op z
静态单赋值( Static Single Assignment, SSA)
特殊的三地址码
Tiger 语言的中间代码生成
Tiger 语言只使用一个名为 Tree 的 IR
Tree 中的表达式与声明
虎书 p.153 - p.154
IR Tree 将语句分为表达式
表达式的各个可选语句如下表所示
声明的各个可选语句如下表所示
从 Tiger AST 到 IR Tree
/* in translate.h */
typedef struct Tr_exp_ *Tr_exp;
/* in translate.c */
struct Cx {patchList trues; patchList falses; T_stm stm;};
struct Tr_exp_ {
enum {Tr_ex, Tr_nx, Tr_cx} kind;
union {T_exp ex; T_stm nx; struct Cx cx;} u;
};
static Tr_exp Tr_Ex(T_exp ex);
static Tr_exp Tr_Nx(T_stm nx);
static Tr_exp Tr_Cx(patchList trues, patchList falses, T_stm stm);
在 IR Tree 中
Tr_ex
表达式: 计算并返回一个值, 表示为, Tr_exp
。 Tr_nx
无结果: 表达式执行操作但不返回任何值, 表示为, T_stm
。 Tr_cx
条件: 表示为可能跳转到不同标签的语句, 但这些标签尚未填充, 如果填入 true 和 false 对应的跳转目的地址。 那么结果语句会对条件进行求值, 然后跳转到其中一个目标, 。
翻译过程
基于结构归纳法
处理 IR
IR Tree 包含与真实的汇编语言差异较大的语句CJUMP
, ESEQ
, CALL
具体而言CJUMP(e, t, f)
可以跳转到两个标签t
与 f
分别对应表达式 e
的真值
IR Tree 中的 CALL
对返回值没有指定处理BINOP(PLUS, CALL(...), CALL(...))
CALL
的返回值会覆盖寄存器 RV
中第一个 CALL
的返回值
IR Tree 中的 ESEQ
BINOP(PLUS, TEMP a, ESEQ(MOVE(TEMP a, u), v))
ESEQ
可能非预期地影响 a
的值
Canonical Form IR
Canonical Tree 被定义为具有以下属性
- 没有
SEQ
或ESEQ
节点 规范树中的根节点是唯一的语句节点: 如( MOVE
或EXP
) 而其他节点都是表达式节点, 如( BINOP
、 MEM
、 CALL
等) 这样保证了树的结构更加简洁和明确。 。 - 每个
CALL
节点的父节点要么是EXP(...)
节点 要么是, MOVE(TEMP t, ...)
节点: CALL
节点不能嵌套在其他表达式中 必须直接成为根节点的子节点, 同时。 根节点必须是, EXP(...)
或MOVE(TEMP t, ...)
这两者之一, 这样设计是为了确保。 CALL
的副作用 例如修改全局状态( 能够被明确控制和管理) 。
需要将 IR 转化
- 将树重写为没有
SEQ
或ESEQ
节点的规范树列表 - 将这个列表分组为一组基本块
它们不包含内部跳转或标签, - 将基本块排序为一组轨迹
traces( ) 其中每个, CJUMP
后面紧跟着它的 false 标签
消除 ESEQ
将 ESEQ
通过恒等变换SEQ
节点
恒等变换规则
ESEQ(s1, ESEQ(s2, e))
->ESEQ(SEQ(s1,s2), e)
BINOP(op, ESEQ(s, e1), e2)
->ESEQ(s, BINOP(op, e1, e2))
MEM(ESEQ(s,e1))
->ESEQ(s, MEM(e1))
JUMP(ESEQ(s, e1))
->SEQ(s, JUMP(e1))
CJUMP(op, ESEQ(s, e1), e2, l1, l2)
->SEQ(s, CJUMP(op, e1, e2, l1, l2))
语句和表达式的可交换性
如果 s
不影响 e
的值s
和表达式 e
是可交换的t1 != t2
MOVE (MEM(t1), e)
和 MEM(t2)
是可交换的
如果语句 s
和表达式 e
不可交换
考虑 BINOP(op, e1, ESEQ(s, e2))
如果 s
不影响 e1
的值s
和 e1
可交换ESEQ(s, BINOP(op, e1, e2))
如果 s
和 e1
不可交换ESEQ(MOVE(TEMP t1, e1), ESEQ(s, BINOP(op, TEMP t1, e2)))
问题BINOP(op, ESEQ(s, e1), e2)
-> ESEQ(S, BINOP(op, e1e2))
不需要考虑 s
和 e2
的可交换性BINOP(op, e1, ESEQ(S, e2))
则要考虑 s
和 e1
的可交换性?
这是因为 BINOP(op, e1, e2)
对 e1
和 e2
的运算顺序是从左往右计算的s
的影响在求值 e1
和 e2
之前s
的影响则是在求值 e1
和 e2
之间s
实际上只影响 e2
但没有影响 e1
ESEQ
至 BINOP
外侧s
非预期地同时影响了 e1
和 e2
的值
恒等变换规则
- 如果语句
s
和表达式e1
可以交换, BINOP(op, e1, ESEQ(s, e2))
->ESEQ(s, BINOP(op, e1, e2))
- 如果语句
s
和表达式e1
不可交换, BINOP(op, e1, ESEQ(s, e2))
->ESEQ(MOVE(TEMP t1, e1), ESEQ(s, BINOP(op, TEMP t1, e2)))
- 如果语句
s
和表达式e1
可以交换, CJUMP(op, e1, ESEQ(s, e2), l1, l2)
->SEQ(s, CJUMP(op, e1, e2, l1, l2))
- 如果语句
s
和表达式e1
不可交换, CJUMP(op, e1, ESEQ(s, e2), l1, l2)
->SEQ(MOVE(TEMP t1, e1), SEQ(s, CJUMP(op, TEMP t1, e2, l1, l2)))
语句和表达式的可交换性的确定
TODO
Moving CALL
s to Top Level
当处理包含函数调用的表达式时
函数调用的返回值通常会分配给一个专用的返回值寄存器CALL
表达式在同一个上下文中使用时
解决方案是将每个 CALL
的返回值立即分配给一个新的临时寄存器
CALL(fun, args)
->ESEQ(MOVE(TEMP t, CALL(fun, args)), TEMP t)
Moving
CALL
s to Top Level 指的是将CALL
的副作用和结果显式地分离并在语句层面处理而不是嵌套在复杂的表达式内部 , 。
消除 SEQ
执行完以上两个步骤SEQ(SEQ(SEQ(..., sx), sy), sz)
的
反复利用恒等变换 SEQ(SEQ(a, b), c) = SEQ(a, seq(b, c))
SEQ(s_1, SEQ(s_2, ..., SEQ(s_{n-1}, s_n)...))
s_1, s_2, ..., s_{n-1}, s_n
基本块 (Basic Blocks) & 轨迹 (Traces)
处理 CJUMP(cond, lt, lf)
CJUMP
的 false 标签紧跟在 CJUMP
之后
对于经过处理的规范树列表
一个LABEL
JUMP
或 CJUMP
LABEL
JUMP
或 CJUMP
一个
将 IR 序列转换为基本块
- 从头到尾扫描 IR 的语句
。 - 当找到标签时
开始一个新基本块, 并结束前一个基本块( ) 。 - 当找到条件跳转
( CJUMP
或跳转) ( JUMP
时) 结束当前基本块, 并开始下一个基本块( ) 。 - 如果基本块结束时没有
CJUMP
或JUMP
则在其后附加跳转到下一个基本块的指令, 。 - 如果基本块开头没有标签
则为其生成一个标签并添加上去, 。
将所有基本块用轨迹覆盖
src: Tiger p.187
最后CJUMP
后面紧跟的标签
- 如果
CJUMP
后跟着的标签是 true 标签 互换 true 和 false 标签, 并对条件取反, 。 - 如果
CJUMP
后跟着的标签是 false 标签 什么都不做, 。 - 如果
CJUMP
后跟着的标签既不是 true 标签 也不是 false 标签, 替换, CJUMP(cond, a, b, lt, lf)
为:
CJUMP(cond, a, b, lt, lf')
LABEL lf'
JUMP(NAME lf)