目标代码生成
复习
- 优化流水线总装:优化后的中间表示
- 中间表示:与具体机器无关
- 编译器:前端、中端、后端的划分
TL;DR
- 后端把与机器无关的 IR,变成具体机器的代码
- 它要处理指令选择、寄存器分配、栈帧等
- 目标代码必须忠实实现 IR 的语义
- 后端与具体机器架构强相关
正文
优化做完,程序的中间表示已经“又快又干净”了。但 IR 是抽象的,机器却只认它自己那套指令。把 IR 落到真实机器上,就是后端的工作。
前端讲“通用”,后端讲“这台机器”
前面说过,IR 的一大好处就是与具体机器无关:不管目标是什么 CPU,前面那些分析和优化都能照做。
到了后端,画风一转:它要盯着一台具体的机器来办事了。不同的 CPU,指令集、寄存器数量、寻址方式都不一样。同一个 IR 操作,在不同机器上可能要生成不同的指令。“机器无关”的红利到此为止,从这里开始,全是机器细节。
后端要做的事
把一个 IR 程序变成机器代码,后端大致要解决这几件事:
- 指令选择:把 IR 的每个操作,映射成合适的机器指令
- 寄存器分配:决定每个值放在哪个寄存器里
- 栈帧安排:为函数调用准备栈上的空间
- 指令排序:在保证语义的前提下,把指令排到合适的顺序
- 最后生成汇编或机器码
这几件事互相牵制:指令选择影响寄存器需求,寄存器不够又要溢出到栈……所以后端不是“一条直线”,而是一组需要反复协调的决定。接下来的章节,就逐一拆开看。
但无论怎么安排,都有一条底线和前端一样:生成的目标代码,必须忠实实现 IR 的语义。 前端辛苦建立的“行为一致”,不能在后端丢掉。正确,永远是第一位。
思考题 1
后端与前端最大的区别,在于什么?
思考题 2
后端为什么必须“忠实”实现 IR 的语义?
小结
知识点
- 后端把机器无关的 IR 变成具体机器代码
- 主要工作:指令选择、寄存器分配、栈帧、指令排序
- 后端与具体机器架构强相关
- 目标代码必须忠实实现 IR 语义
参考资料
- Wikipedia(zh):代码生成:编译器后端把中间表示转为目标代码
- Wikipedia(zh):编译器:前端、中端与后端的划分
思考题答案(仅供参考)
思考题 1
前端(以及中端)处理的是与具体机器无关的形式,读源码、建结构、做优化;后端则紧盯一台具体机器,处理指令集、寄存器、寻址方式等机器细节。也就是从“通用”转向“特定机器”。
思考题 2
因为 IR 的语义就是程序应有的行为。后端若不能忠实实现它,优化阶段辛苦维持的“行为不变”就会丢失,程序结果可能出错。正确是底线,速度只是在此之上的加分。
协议
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
封面图
设计师 | 南国微雪