13.5 生成汇编
经过指令选择、寄存器分配和 frame 构造,后端已经拥有目标专用 machine instructions。它可以输出文本汇编交给外部 assembler,也可以直接编码 object-file bytes。文本便于检查,并可复用成熟 assembler;直接发射则控制更紧密,集成流水线也更快。
汇编输出绝不是把 IR 转成字符串。Emitter 必须选择语法、打印合法操作数、创建唯一 label、排列基本块、发射 section 与 alignment、保持 symbol visibility,并附加 unwind/debug directives。
从机器指令到文本
Assembly printer 同时依赖目标与 dialect。x86 Intel 写法是 mov rax, [rbp-8],AT&T 语法会调换操作数并修饰寄存器与立即数;AArch64、RISC-V 也有各自寄存器名、relocation modifier 与 directive 约定。
Printer 应消费已经验证的 machine operands,而不再临时做寄存器分配。Pseudo-instruction 要么提前展开,要么有意交给 assembler。Label 必须在函数内唯一,避免两个源码 loop 块冲突。
.text
.globl sum
.type sum, @function
sum:
push rbp
mov rbp, rsp
; selected and allocated body
pop rbp
retDirective 描述 code/data section、symbol binding、alignment、constant、CFI 与 metadata。它们不执行,但下游工具依赖它们。
基本块布局与 fallthrough
CFG successor 不必保持源码顺序。把最可能的后继紧接在条件分支后,可以形成 fallthrough,并删除或反转 jump;冷 error path 可以移入独立 section,loop header 则可能为取指性能对齐。
布局本身也有取舍。Alignment 会插入 padding bytes;复制小块可以消除 branch,却增加体积;hot/cold splitting 改善 instruction-cache locality,却拉长跳转距离。Profile 能辅助决策,但所有路径都必须正确,而不只是 hot path。
编码机器指令
直接 encoder 把 opcode 与操作数写入指令字段。AArch64 与基础 RISC-V 等定长 ISA 常使用 32 位 instruction word,包含 opcode、register 和分散 immediate bit field。x86 指令可变长,可能包含 prefix、opcode、ModR/M、SIB、displacement 与 immediate。
每个 immediate/displacement 都有范围与解释。Branch target 超出短编码时,后端可能把它 relax 成更长序列;加载大常量也可能需要多条指令。这些决定会改变代码体积,进而再次改变 label distance,因此 assembler 可能迭代 layout 与 relaxation。
Symbol 与 relocation 请求
单独编译一个文件时,外部函数或 global 的最终地址通常未知。Emitter 会写 placeholder,并记录 relocation request,包含 symbol、relocation kind、addend 与 patch location。Position-independent code 偏好 PC-relative 或 table-based 形式。第 14 章会继续追踪这些请求如何进入 object file 与 linker。
本地 label 可在 layout 已知后由 assembler 内部解析;外部或跨 section 引用通常保留 relocation。后端必须选择与当前 instruction sequence 和 code model 兼容的 relocation kind。
测试 emitter
Golden assembly test 可读性高,但格式变化会制造噪声,应与结构化 machine-instruction test 搭配,并尽量执行 assemble/disassemble round trip。对照可信 assembler 验证立即数边界、每种 addressing mode、寄存器极值、远近 branch、external symbol、PIC、CFI 与 section switch。
最后,把生成片段放进 harness 执行,并与 IR interpreter 比较结果。后端是抽象正确性落到精确 bit field 的地方;分层验证远比在大型 executable 中追查一个错误字节便宜。