Skip to content

第1章:编译过程与程序表示

翻译必须保持什么

源语言规定表达式、控制流、存储和异常等行为,目标机器有自己的指令与运行规则。编译器的工作是使目标执行符合源语言语义,而不要求每个源操作都对应一条目标指令。

可观察行为通常包括返回结果、I/O、可见内存效果、终止或异常。具体比较哪些行为取决于语言和编译正确性的定义。一个变换只在普通输入上得到相同数值,不足以证明它保持异常、并发和边界行为。

不同表示保存不同信息

表示主要对象适合处理的问题
token 流标识符、关键字、符号、位置单词边界和词法错误
抽象语法树 AST表达式、声明、语句名字绑定、类型与语言结构
控制流图 CFG基本块、跳转路径、循环、数据流
SSA 等 IR值、操作、合流关系定义使用关系与优化
机器表示指令、寄存器、栈位置选择、调度、寄存器分配

IR 不是一个统一的“万能代码格式”。一个编译器可以同时保留多级 IR,以便在不同抽象层做不同变换。

一个表达式的表示

源程序 y = a + b * 2 中,语法树把乘法作为加法的右子树。降低成三地址形式后可写为:

text
t1 = b * 2
t2 = a + t1
y = t2

后端可能选用移位代替乘法,也可能直接使用立即数乘法。是否合法取决于类型与溢出规则,是否划算取决于目标机器成本,而不是仅凭“移位看起来简单”。

分析与变换

分析推断程序性质,如变量是否存活、某值是否为常量。变换据这些性质改写程序。分析可以保守地回答“不知道”,但若把不确定性误报成保证,依赖它的优化就可能产生错误代码。

例如指针可能别名时,两个访存之间不能仅因变量名字不同就认为独立。保守分析牺牲部分优化机会,换取对所有允许执行都正确。

练习

  1. print(f()+g()) 的两个调用能否交换?列出需要知道的语言规则。
  2. AST 与 CFG 各自更适合表达哪些结构?
  3. 一个优化让正常结果相同,却消除了原本应发生的异常,何时可被接受?

上次更新: