Appearance
第1章:编译过程与程序表示
翻译必须保持什么
源语言规定表达式、控制流、存储和异常等行为,目标机器有自己的指令与运行规则。编译器的工作是使目标执行符合源语言语义,而不要求每个源操作都对应一条目标指令。
可观察行为通常包括返回结果、I/O、可见内存效果、终止或异常。具体比较哪些行为取决于语言和编译正确性的定义。一个变换只在普通输入上得到相同数值,不足以证明它保持异常、并发和边界行为。
不同表示保存不同信息
| 表示 | 主要对象 | 适合处理的问题 |
|---|---|---|
| token 流 | 标识符、关键字、符号、位置 | 单词边界和词法错误 |
| 抽象语法树 AST | 表达式、声明、语句 | 名字绑定、类型与语言结构 |
| 控制流图 CFG | 基本块、跳转 | 路径、循环、数据流 |
| SSA 等 IR | 值、操作、合流关系 | 定义使用关系与优化 |
| 机器表示 | 指令、寄存器、栈位置 | 选择、调度、寄存器分配 |
IR 不是一个统一的“万能代码格式”。一个编译器可以同时保留多级 IR,以便在不同抽象层做不同变换。
一个表达式的表示
源程序 y = a + b * 2 中,语法树把乘法作为加法的右子树。降低成三地址形式后可写为:
text
t1 = b * 2
t2 = a + t1
y = t2后端可能选用移位代替乘法,也可能直接使用立即数乘法。是否合法取决于类型与溢出规则,是否划算取决于目标机器成本,而不是仅凭“移位看起来简单”。
分析与变换
分析推断程序性质,如变量是否存活、某值是否为常量。变换据这些性质改写程序。分析可以保守地回答“不知道”,但若把不确定性误报成保证,依赖它的优化就可能产生错误代码。
例如指针可能别名时,两个访存之间不能仅因变量名字不同就认为独立。保守分析牺牲部分优化机会,换取对所有允许执行都正确。
练习
print(f()+g())的两个调用能否交换?列出需要知道的语言规则。- AST 与 CFG 各自更适合表达哪些结构?
- 一个优化让正常结果相同,却消除了原本应发生的异常,何时可被接受?