练习

本节阅读量:

这一页适合读完第二章后回头做。先能解释输出,再尝试改代码。

练习 1:手画 AST

画出下面程序的 AST:

1
(let x 7 x)

参考形状:

1
2
3
4
Let
  name = ...
  value = ...
  body = ...

练习 2:追踪环境

不用写代码,只写出环境变化。

程序:

1
(let x 10 (+ (let x 32 x) x))

参考形式:

1
2
3
4
5
进入外层:...
进入内层:...
内层 x 查到:...
离开内层:...
最后那个 x 查到:...

练习 3:手写 IR

给下面程序写出 IR:

1
(let x (+ 20 22) x)

参考形式:

1
2
3
t.0 = ...
x0 = ...
return ...

注意 value 里的加法会先生成临时变量。

练习 4:判断结果

判断下面程序的结果:

1
(let x 5 (let y 6 (+ x y)))

再判断:

1
(let x 5 (let x 6 (+ x x)))

练习 5:未绑定变量

运行:

1
(+ x 1)

观察:

1
2
3
./mini run <file>
./mini ir <file>
./mini compile <file> -o out.s

解释器和编译器都会在查找变量时发现 x 没有绑定。

练习 6:看汇编里的位置

编译:

1
(let x 10 (+ (let x 32 x) x))

找出:

1
2
3
外层 x 的 IR 名字和栈槽
内层 x 的 IR 名字和栈槽
最终加法结果的 IR 名字和栈槽

确认外层 x 和内层 x 不是同一个位置。

练习 7:让同名变量连续编号

当前 lowerer 使用一个全局的 next_local_ 为所有 let 绑定编号。对于依次绑定 xyx 的程序:

1
(let x 1 (let y 2 (let x 3 (+ x y))))

生成的内部名字是:

1
2
3
x0
y1
x2

修改内部名字的生成方式,让每个源码名字分别计数,得到:

1
2
3
x0
y0
x1

修改后运行:

1
2
./mini ir <file>
./mini run <file>

确认 IR 中同名变量的编号连续,同时程序结果仍然是 5

这个改动只影响 IR 名字是否容易阅读,不应该改变变量查找、shadowing 或栈槽分配的行为。

练习 8:分清 value 和 body 的环境

先不要运行,判断下面程序中每个 x 对应哪一层绑定:

1
(let x 1 (let x (+ x 1) (+ x x)))

分别写出:

1
2
3
计算内层 value 时的环境
计算内层 body 时的环境
程序的最终结果

然后运行下面两个命令,检查你的推导:

1
2
./mini run <file>
./mini ir <file>

重点观察:内层绑定要等 value 计算完成后才加入环境。

练习 9:变量引用会生成 IR 吗

分别查看下面两个程序的 IR:

1
(let x 7 x)
1
(let x 7 (+ x x))

数一数每个程序生成了多少条 copy 和多少条 add,再回答:

1
2
body 里的 x 为什么没有生成新的 IR 操作?
两个 x 为什么可以直接成为 add 的操作数?

结合 lowerer 的 ExprKind::variable 分支解释答案。


2.7 本章总结

上一节

3.0 有无相生,前后相随

下一节