IR:把用户名字变成内部名字
本节阅读量:
解释器处理变量时,可以在 environment 里直接保存运行时值:
编译器不能把源码里的 x 原样带到汇编里。汇编没有这种用户变量名。
第二章的编译器先做一件中间工作:
例如:
后面的汇编生成器再把 IR 名字放到栈槽里:
普通 let 的 IR
源码:
IR:
1
2
3
|
x0 = 40
t.0 = x0 + 2
return t.0
|
这里的 x0 是 let 绑定的内部名字,t.0 是加法结果的临时名字。点号是有意保留给编译器临时值的:源码 identifier 不允许点号,绑定内部名由“源码名字 + 数字”组成,因此用户变量 t 会变成 t0,不会和 t.0 冲突。
例如:
1
|
(+ (let t 1 t) (+ 2 3))
|
会得到互不冲突的内部名字:
1
2
3
4
|
t0 = 1
t.0 = 2 + 3
t.1 = t0 + t.0
return t.1
|
为什么不直接继续叫 x?因为 shadowing 会出现多个同名用户变量:
1
|
(let x 10 (+ (let x 32 x) x))
|
如果 IR 里都叫 x,内层和外层就混在一起了。
IR 新增 copy 操作
第一章的 IR 只有加法:
第二章需要表示:
这一步不是加法,只是把一个操作数保存到一个内部名字里,所以 IR 增加了 copy:
1
2
3
4
|
enum class OpKind {
copy,
add,
};
|
打印 IR 时,copy 写成:
例如:
Lowerer 里的环境
代码在:
1
|
code/02_let/src/compile/ir.cpp
|
IR lowerer 也有一个环境:
1
|
std::vector<std::pair<std::string, Operand>> env_;
|
它保存的不是运行时值,而是:
例如:
查找逻辑和解释器类似:
1
2
3
4
5
6
7
8
|
Operand lookup(const std::string& name) const {
for (auto it = env_.rbegin(); it != env_.rend(); ++it) {
if (it->first == name) {
return it->second;
}
}
throw std::runtime_error("undefined variable: " + name);
}
|
同样是从后往前找,用来支持 shadowing。
变量引用如何 lowering
变量引用分支很短:
1
2
3
4
|
case ExprKind::variable: {
const auto& var_expr = static_cast<const VarExpr&>(expr);
return lookup(var_expr.name);
}
|
可以读成:
1
2
|
Var(x) 本身不生成新 IR。
它只是查出 x 当前对应哪个操作数。
|
如果环境里有:
那么 Var(x) lowering 后就返回操作数 x0。
let 如何 lowering
let 分支按顺序完成这些步骤:
1
2
3
4
5
6
7
8
9
10
|
case ExprKind::let: {
const auto& let_expr = static_cast<const LetExpr&>(expr);
Operand value = lower_expr(*let_expr.value, program);
std::string local = let_expr.name + std::to_string(next_local_++);
program.ops.push_back({OpKind::copy, local, std::move(value), Operand::integer(0)});
env_.push_back({let_expr.name, Operand::temp(local)});
Operand result = lower_expr(*let_expr.body, program);
env_.pop_back();
return result;
}
|
按顺序读:
1
2
3
4
5
6
7
|
1. 先 lower value,得到 value 的结果在哪里。
2. 生成一个内部名字,例如 x0。
3. 追加 copy 操作:x0 = value。
4. 在环境里加入:用户名 x -> IR 名字 x0。
5. lower body。
6. 离开 let,弹出这个绑定。
7. 返回 body 的结果操作数。
|
这里的 value 在加入新绑定之前 lower,正好对应解释器规则:
1
2
|
let 的 value 在旧环境里求值。
let 的 body 在扩展后的环境里求值。
|
当前 Op 结构为了保持第一章的小模型,所有操作都有 lhs 和 rhs 字段。copy 只使用 lhs,rhs 是占位;真正决定怎么打印和生成汇编的是 OpKind::copy。
shadowing 的 IR
运行:
1
2
3
|
cd code/02_let
make
./mini ir examples/shadow.lang
|
输出:
1
2
3
4
|
x0 = 10
x1 = 32
t.0 = x1 + x0
return t.0
|
对应关系是:
1
2
|
外层 x -> x0
内层 x -> x1
|
IR 把这个关系写得很清楚。下一节会把 x0、x1、t.0 这些 IR 名字分配到栈槽。