IR lowering:把 AST 变成分支和汇合
本节阅读量:上一节已经确定目标 IR 的形状。这一节进入实现,看看 lowerer 怎样生成谓词、label、跳转和共享结果名。
代码在:
|
|
lower_expr 的约定没有改变
先回顾 lowerer 的核心约定:
|
|
它做两件事:
|
|
例如:
|
|
因此,lowering if 并不是立刻求出 then 或 else 的值。lowerer 在编译期间会遍历两个分支,把两边的操作都生成出来;将来程序运行时,branch 才会决定执行哪一边。
要特别区分这两个时刻:
|
|
IR 在代码里怎样保存
操作种类定义在 ir.h:
|
|
equal 负责产生普通整数结果,branch、jump 和 label 负责控制下一步去哪里。
每条操作都保存在同一个 Op 结构里:
|
|
不同 OpKind 使用不同字段:
|
|
例如,一条 branch:
|
|
在结构里保存的是:
|
|
没有使用到的字段会放占位值。真正决定一条 Op 应该怎样解释的是 kind;IR 打印和下一节的汇编生成器都会用 switch 分派。
把重复动作抽成两个辅助函数
第二章直接在各个分支里生成临时名字、追加 copy。第三章需要这些动作的地方变多了,所以把重复代码抽成两个辅助函数:
|
|
new_temp() 统一生成 t.0、t.1 这样的临时名字,供加法、eq? 和 if 的共享结果使用。emit_copy() 统一追加 dst = value,既用于原有的 let,也用于把 then 和 else 的结果保存到同一个名字。
它们没有引入新的 IR 概念,只是把多处相同的实现集中起来。
eq? 怎样 lowering
eq? 是前面说过的谓词表达式:它提出一个判断,并把结果表示成整数 0 或 1。
谓词本身不会改变控制流。它和加法一样,读取操作数、生成一个临时名字,再把结果写进去。后面的 if 可以读取这个 0 或 1,再决定走哪个分支。
eq? 读取两个操作数:
|
|
例如 (eq? (+ 20 22) 42) 得到:
|
|
判断某个表达式是否为零也用同一条路径,例如 (eq? x 0)。equal 的结果一定是 0 或 1,但在 IR 中仍然只是普通整数操作数,可以继续交给 if、加法或其他表达式使用。
if 怎样 lowering
IfExpr 对应的完整代码是:
|
|
可以把它分成五步读。
第一步,lower condition。condition 自己可能包含加法、变量甚至另一个 if,所以仍然递归调用 lower_expr():
|
|
第二步,为这次 if 创建三个互不冲突的 label,以及一个共享结果名:
|
|
第三步,追加 branch 和 then 路径:
|
|
第四步,追加 else 路径和汇合位置:
|
|
第五步,返回共享结果名:
|
|
调用者不需要知道这个结果来自哪个分支。它只知道:执行到 end_label 后,result 中就是整个 if 的值。
next_label_ 为每个 if 分配独立编号,所以嵌套条件会得到 then0、then1 等不同名字,不会跳进别的 if。
到这里,AST 已经 lowering 成带控制流的 IR。下一节把这些 IR 操作翻译成 x86-64 汇编。