IR lowering:把 AST 变成分支和汇合

本节阅读量:

上一节已经确定目标 IR 的形状。这一节进入实现,看看 lowerer 怎样生成谓词、label、跳转和共享结果名。

代码在:

1
2
code/03_conditionals/src/compile/ir.h
code/03_conditionals/src/compile/ir.cpp

lower_expr 的约定没有改变

先回顾 lowerer 的核心约定:

1
Operand lower_expr(const Expr& expr, IrProgram& program)

它做两件事:

1
2
1. 把计算 expr 所需的操作追加到 program.ops。
2. 返回一个 Operand,告诉调用者 expr 的结果在哪里。

例如:

1
2
3
Int(42)       不追加操作,直接返回整数操作数 42
Add(...)      追加一条 add,返回保存结果的临时名字
If(...)       追加分支和两条路径,返回汇合后的共享结果名

因此,lowering if 并不是立刻求出 then 或 else 的值。lowerer 在编译期间会遍历两个分支,把两边的操作都生成出来;将来程序运行时,branch 才会决定执行哪一边。

要特别区分这两个时刻:

1
2
编译时:lower 两个分支,生成两份代码。
运行时:根据 condition,只执行一个分支。

IR 在代码里怎样保存

操作种类定义在 ir.h

1
2
3
4
5
6
7
8
enum class OpKind {
    copy,
    add,
    equal,
    branch,
    jump,
    label,
};

equal 负责产生普通整数结果,branchjumplabel 负责控制下一步去哪里。

每条操作都保存在同一个 Op 结构里:

1
2
3
4
5
6
7
8
struct Op {
    OpKind kind;
    std::string dst;
    Operand lhs;
    Operand rhs;
    std::string target;
    std::string else_target;
};

不同 OpKind 使用不同字段:

1
2
3
4
5
6
copy       dst、lhs
add        dst、lhs、rhs
equal      dst、lhs、rhs
branch     lhs、target、else_target
jump       target
label      target

例如,一条 branch:

1
if t.0 goto then0 else else0

在结构里保存的是:

1
2
3
4
kind          = branch
lhs           = temp("t.0")
target        = "then0"
else_target   = "else0"

没有使用到的字段会放占位值。真正决定一条 Op 应该怎样解释的是 kind;IR 打印和下一节的汇编生成器都会用 switch 分派。

把重复动作抽成两个辅助函数

第二章直接在各个分支里生成临时名字、追加 copy。第三章需要这些动作的地方变多了,所以把重复代码抽成两个辅助函数:

1
2
3
4
5
6
7
8
std::string new_temp() {
    return "t." + std::to_string(next_temp_++);
}

void emit_copy(IrProgram& program, const std::string& dst, Operand value) {
    program.ops.push_back(
        {OpKind::copy, dst, std::move(value), Operand::integer(0), "", ""});
}

new_temp() 统一生成 t.0t.1 这样的临时名字,供加法、eq?if 的共享结果使用。emit_copy() 统一追加 dst = value,既用于原有的 let,也用于把 then 和 else 的结果保存到同一个名字。

它们没有引入新的 IR 概念,只是把多处相同的实现集中起来。

eq? 怎样 lowering

eq? 是前面说过的谓词表达式:它提出一个判断,并把结果表示成整数 01

谓词本身不会改变控制流。它和加法一样,读取操作数、生成一个临时名字,再把结果写进去。后面的 if 可以读取这个 01,再决定走哪个分支。

eq? 读取两个操作数:

1
2
3
4
5
6
7
8
9
case ExprKind::equal: {
    const auto& eq_expr = static_cast<const EqExpr&>(expr);
    Operand lhs = lower_expr(*eq_expr.lhs, program);
    Operand rhs = lower_expr(*eq_expr.rhs, program);
    std::string dst = new_temp();
    program.ops.push_back(
        {OpKind::equal, dst, std::move(lhs), std::move(rhs), "", ""});
    return Operand::temp(dst);
}

例如 (eq? (+ 20 22) 42) 得到:

1
2
3
t.0 = 20 + 22
t.1 = eq? t.0 42
return t.1

判断某个表达式是否为零也用同一条路径,例如 (eq? x 0)equal 的结果一定是 01,但在 IR 中仍然只是普通整数操作数,可以继续交给 if、加法或其他表达式使用。

if 怎样 lowering

IfExpr 对应的完整代码是:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
case ExprKind::conditional: {
    const auto& if_expr = static_cast<const IfExpr&>(expr);
    Operand condition = lower_expr(*if_expr.condition, program);
    int label_id = next_label_++;
    std::string then_label = "then" + std::to_string(label_id);
    std::string else_label = "else" + std::to_string(label_id);
    std::string end_label = "end" + std::to_string(label_id);
    std::string result = new_temp();

    program.ops.push_back({OpKind::branch,
                           "",
                           std::move(condition),
                           Operand::integer(0),
                           then_label,
                           else_label});
    program.ops.push_back(
        {OpKind::label, "", Operand::integer(0), Operand::integer(0), then_label, ""});
    Operand then_value = lower_expr(*if_expr.then_branch, program);
    emit_copy(program, result, std::move(then_value));
    program.ops.push_back(
        {OpKind::jump, "", Operand::integer(0), Operand::integer(0), end_label, ""});
    program.ops.push_back(
        {OpKind::label, "", Operand::integer(0), Operand::integer(0), else_label, ""});
    Operand else_value = lower_expr(*if_expr.else_branch, program);
    emit_copy(program, result, std::move(else_value));
    program.ops.push_back(
        {OpKind::label, "", Operand::integer(0), Operand::integer(0), end_label, ""});
    return Operand::temp(result);
}

可以把它分成五步读。

第一步,lower condition。condition 自己可能包含加法、变量甚至另一个 if,所以仍然递归调用 lower_expr()

1
Operand condition = lower_expr(*if_expr.condition, program);

第二步,为这次 if 创建三个互不冲突的 label,以及一个共享结果名:

1
2
3
4
5
int label_id = next_label_++;
std::string then_label = "then" + std::to_string(label_id);
std::string else_label = "else" + std::to_string(label_id);
std::string end_label = "end" + std::to_string(label_id);
std::string result = new_temp();

第三步,追加 branch 和 then 路径:

1
2
3
4
5
branch condition then_label else_label
then_label:
lower then_branch
result = then_value
jump end_label

第四步,追加 else 路径和汇合位置:

1
2
3
4
else_label:
lower else_branch
result = else_value
end_label:

第五步,返回共享结果名:

1
return Operand::temp(result);

调用者不需要知道这个结果来自哪个分支。它只知道:执行到 end_label 后,result 中就是整个 if 的值。

next_label_ 为每个 if 分配独立编号,所以嵌套条件会得到 then0then1 等不同名字,不会跳进别的 if

到这里,AST 已经 lowering 成带控制流的 IR。下一节把这些 IR 操作翻译成 x86-64 汇编。


3.5 IR:把一条直线变成几条可选择的路

上一节

3.7 汇编:让 CPU 真的选择道路

下一节