跳转至

Translate to Intermediate Code:中间代码生成

约 1021 个字 1 张图片 预计阅读时间 3 分钟

1. 引入与定义

为什么需要 IR?为什么不直接把 AST 翻译为机器码?

  • 模块化设计
    • N 种源语言,M 种目标机器
    • 如果直接翻译,需要 N * M 个翻译器
    • 如果有中间代码,只需要 N + M 个翻译器
  • 可移植性

IR 是一种抽象化的机器语言:

  • 能够在不携带太多机器语言实现细节的情况下,表达目标机器的操作
  • 和源语言的细节无关

不同的编译器可能会使用不同的 IR,例如 Tiger 编译器使用的是表达式树,还有用三地址码的等等。一个编译器可能会用多级 IR。

IR 把编译器分成前后端
  • 前端
    • 词法分析
    • 语法分析
    • 语义分析
    • 翻译为 IR
  • 后端
    • 优化 IR
    • 翻译为目标机器代码

2. 三地址码 Three-Address Code

这是一种中间代码的形式,最常见的 basic instruction 是:x = y op z

  • 一条指令最多有四个域:一个操作,三个地址
  • 地址可以是
    • name:一个源程序名字
    • constant:一个常量
    • compiler-generated temporary:编译器生成的临时变量,例如 t1
  • 指令的右边最多有一个 operation,因此源程序的一句话可能要分成多条指令。
  • 有的时候为了适应不同的语句,可能需要做三地址码的变体,例如 t2 = -t1
  • 因此,三地址码没有严格的标准格式
  • 常见的把三地址码组织起来的形式有数组和链表,由于最多有四个 field,可以实现为 quadruples。如果没有四个 field,空的地方可以用 null 填充。

3. 中间表示树

中间表示树是由一个一个 IR 节点拼接起来的,节点总体分为两类:表达式类型与语句类型。

  • 表达式类型
    • CONST(i):常量 i
    • NAME(n):标签 n
    • TEMP(t):临时变量 t,这里是虚拟寄存器
    • BINOP(op, e1, e2):二元操作符,左边和右边都是表达式。操作包括:PLUS, MINUS, MUL, DIV, AND, OR, XOR, LSHIFT, RSHIFT, ARSHIFT
    • MEM(e):内存地址 e 开始一个 word 大小的数据。MEM 在 MOVE 的左边时候代表 store,否则代表 fetch
    • CALL(f, l):用参数列表 l 来调用函数 f。从左到右传递参数
    • ESEQ(s, e):表达式 e 的值是 s 语句执行后的结果。s 是一个语句,e 是一个表达式。ESEQ 可以用来实现副作用,例如函数调用。
  • 语句类型
    • MOVE(TEMP t, e):把表达式 e 的值存储到临时变量 t 中
    • MOVE(MEM(e1), e2):把表达式 e2 的值存储到内存地址 e1 中
    • EXP(e):表达式 e 的值被计算出来,但不存储到任何地方(discard)
    • JUMP(e, labs):跳转到位置 e 的标签 label
    • CJUMP(op, e1, e2, t, f):计算表达式 e1 和 e2 的值,比较它们的值,如果满足条件 op,则跳转到标签 t,否则跳转到标签 f。
    • SEQ(s1, s2):执行语句 s1 然后执行 s2
    • LABEL(l):定义一个标签 l。NAME(l) 可以用来引用这个标签。

4. 中间表示树的翻译

4.1 表达式

AST 中的表达式翻译成 IR,有三种形式:

  • 带有返回值的 T_exp
  • 不带有返回值的 T_stm
  • 带有布尔值的 conditional jump

例如:

image.png

注意这里我们还不知道 true-destination 和 false-destination 是什么,所以填上 NULL。后续知道了之后再填上。这个叫做 backpatching,其中有 true patch list 和 false patch list。

4.2 简单变量

获取栈帧中存储的一个变量,可以使用 MEM(BINOP(PLUS, TEMP fp, CONST k)),其中 k 是变量在栈帧中的偏移量。

这里也可以做一个结合的简写,记为 MEM(+(TEMP fp, CONST k))

4.3 函数调用

要记得把 static link 传递给函数调用。CALL(NAME lf, [sl, e1, e2, …, en])

4.4 变量声明

需要在栈帧中预留好空间(FP + offset)

4.5 函数声明/定义

在函数体前面和后面加上 prologue 和 epilogue。