汇编:为 copy 增加翻译规则
本节阅读量:
第一章的汇编生成器已经会做这些事:
1
2
3
4
|
给每条 IR 操作的 dst 分配栈槽
在函数开头一次性留出栈空间
把 add 翻译成 movq、addq、movq
把最终结果放进 %rax
|
第二章继续使用这套流程。汇编生成器只需要认识上一节新增的 copy 操作。
copy 怎样生成汇编
代码在:
1
|
code/02_let/src/compile/assembly.cpp
|
IR:
表示把操作数 40 保存到内部名字 x0。它只需要两条指令:
1
2
|
movq $40, %rax
movq %rax, -8(%rbp)
|
对应的新增分支是:
1
2
3
4
|
case OpKind::copy:
out_ += " movq " + operand_text(op.lhs) + ", %rax\n";
out_ += " movq %rax, " + stack_slot(op.dst) + "\n";
return;
|
这里仍然使用第一章已有的两个辅助函数:
1
2
|
operand_text(op.lhs) 把整数或 IR 名字写成汇编操作数
stack_slot(op.dst) 找到结果对应的栈槽
|
因此,如果 lhs 是另一个 IR 名字,copy 也不需要特殊处理。例如:
可以生成:
1
2
|
movq -8(%rbp), %rax
movq %rax, -16(%rbp)
|
这里借 %rax 中转是有实际原因的:x0 和 x1 都已经位于内存栈槽,普通 movq 不能直接完成一次内存到内存的复制,必须先读进寄存器,再写到另一个内存位置。
对于 x0 = 40,x86-64 其实允许进一步选择更短的写法。当前 emitter 仍统一使用“先放进 %rax,再保存到目标栈槽”的两步模板,让整数、临时结果和其他 IR 名字都走同一条清楚的生成规则;这里暂时不做指令数量优化。
完整例子
源码:
IR:
1
2
3
|
x0 = 40
t.0 = x0 + 2
return t.0
|
其中只有第一条使用新的 copy 分支;第二条 add 和最后的返回都沿用第一章:
1
2
3
4
5
6
|
movq $40, %rax
movq %rax, -8(%rbp)
movq -8(%rbp), %rax
addq $2, %rax
movq %rax, -16(%rbp)
movq -16(%rbp), %rax
|
这段 IR 有两个 dst,所以栈槽分配是:
1
2
|
x0 -> -8(%rbp)
t.0 -> -16(%rbp)
|
两个栈槽一共 16 字节,不需要继续向上填充。加入第一章的栈帧开头和结尾后,Linux 或 WSL 上的完整输出是:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
.globl main
main:
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
movq $40, %rax
movq %rax, -8(%rbp)
movq -8(%rbp), %rax
addq $2, %rax
movq %rax, -16(%rbp)
movq -16(%rbp), %rax
movq %rbp, %rsp
popq %rbp
retq
|
从源码到位置的关系现在完整接上了:
1
|
源码绑定 x -> IR 名字 x0 -> 栈槽 -8(%rbp) -> 运行时保存 40
|
Shadowing 也不需要新的汇编规则。lowerer 已经把两个同名变量变成 x0 和 x1,原有的栈槽分配会自然地给它们不同的位置:
1
2
3
|
x0 -> -8(%rbp) 外层 x
x1 -> -16(%rbp) 内层 x
t.0 -> -24(%rbp) 最终加法结果
|
三个栈槽需要 24 字节,生成器向上对齐后会在函数开头分配 32 字节:
布局是:
1
2
3
4
5
6
7
8
9
10
|
较高地址
8(%rbp) 返回地址
0(%rbp) 调用者原来的 %rbp <- %rbp
-8(%rbp) x0:外层 x
-16(%rbp) x1:内层 x
-24(%rbp) t.0:最终加法结果
-32(%rbp) 对齐留下的空间 <- %rsp
较低地址
|
离开内层 let 时,lowerer 环境不再把源码名字 x 指向 x1,但 -16(%rbp) 不会立刻释放。当前生成器在函数入口一次分配整个固定栈帧,到函数返回时才整体收回;词法作用域结束和运行时栈槽回收是两件不同的事。
到这里,第二章新增的编译路径就接通了:
1
|
let 绑定 -> copy IR -> copy 汇编分支
|
手动验证
1
2
3
4
5
6
|
cd code/02_let
make
./mini compile examples/let.lang -o out.s
cc out.s -o out
./out
echo $?
|
退出码应该是: