llvm后端中默认caller-save的是未被getcalleesavedregs()列出且未被getreservedregs()保留的通用寄存器;callee-save寄存器由getcalleesavedregs()显式返回(如x86-64的rbx、rbp、r12–r15),必须严格符合abi且按调用约定动态适配。

LLVM后端中哪些寄存器默认是 caller-save 或 callee-save
LLVM 不会自动推断哪些寄存器该由调用者保存、哪些该由被调用者保存 —— 这完全由目标后端在 XXXRegisterInfo.td 和 XXXRegisterInfo.cpp 中显式定义。关键在于两个机制:寄存器别名(Aliases) 和 寄存器类(RegisterClass) 的组合,以及 getReservedRegs() 和 getCalleeSavedRegs() 这两个虚函数的实现。
-
getCalleeSavedRegs()必须返回一个const MCPhysReg *数组,列出所有被调用者承诺保存的物理寄存器(例如 x86-64 下的RBX,RBP,R12–R15) -
getReservedRegs()返回必须始终保留、不能用于分配的寄存器(如栈指针RSP、帧指针RBP在某些模式下、或特殊用途寄存器如RIP) - 所有未出现在
getCalleeSavedRegs()中、且未被getReservedRegs()排除的通用寄存器,默认视为 caller-save
注意:getCalleeSavedRegs() 返回的寄存器列表,必须与实际 ABI 规范严格一致;否则生成的代码在函数调用后可能破坏调用者状态。
如何在 TableGen 中标记 callee-saved 寄存器
TableGen 本身不直接声明“callee-saved”,它只负责定义寄存器及其关系(别名、子寄存器、寄存器类)。真正的 callee-saved 行为是在 C++ 实现中通过 getCalleeSavedRegs() 控制的。但 TableGen 是基础支撑:
- 在
XXXRegisterInfo.td中,每个寄存器需正确定义其Aliases和SubRegs,否则寄存器分配器无法正确判断冲突 - 寄存器类(如
GR64)应只包含语义等价的整数寄存器;若某寄存器(如RBP)在 ABI 中是 callee-saved,但它属于GR64类,那它仍可被分配 —— 是否保存,全看getCalleeSavedRegs()是否把它列进去 - 错误做法:试图在
RegisterClass定义里加注释或字段标“callee-saved”——LLVM 不读这些
常见疏漏:x86-64 下漏掉 R12~R15 中的某一个,或把 RSP 错误地放进 getCalleeSavedRegs()(它应只在 getReservedRegs() 中)
调用约定(calling convention)如何影响 callee-saved 决策
调用约定不改变寄存器本身的保存责任归属,但它决定 哪些寄存器参与参数传递,进而影响哪些寄存器需要被保存 —— 尤其对 fastcc / cc10 / coldcc 这类非 C ABI 的约定:
-
ccc(C calling convention):严格遵循目标平台 ABI,getCalleeSavedRegs()必须返回 ABI 规定的 callee-saved 寄存器集 -
fastcc:允许目标使用更多寄存器传参,但 仍要求 callee 保存 ABI 规定的 callee-saved 寄存器;额外用于传参的寄存器(如R10,R11)默认 caller-save,除非你主动把它们加进getCalleeSavedRegs() -
cc10(GHC):明确禁用 callee-saved 寄存器(“通过禁用被调用者保存寄存器来达到这个目的”),此时getCalleeSavedRegs()应返回空数组;若仍返回RBX等,会导致尾调用优化失败或寄存器污染
关键点:getCalleeSavedRegs() 的返回值必须与当前激活的调用约定语义一致;LLVM 不会为你做适配 —— 你得在函数体内根据 CC 参数分支返回不同数组(见下一点)
如何让 getCalleeSavedRegs() 支持多调用约定
getCalleeSavedRegs() 函数签名是 const MCPhysReg <em>getCalleeSavedRegs(const MachineFunction </em>MF) const override,你可以从 MF->getFunction().getCallingConv() 获取当前函数的调用约定,并据此返回不同集合:
- 对
CallingConv::C,返回标准 ABI 的 callee-saved 列表(如 x86-64 下{RBX, RBP, R12, R13, R14, R15}) - 对
CallingConv::Fast,可返回更小的集合(比如只保RBP和RBX),因为其他寄存器本就不该被 caller 依赖 - 对
CallingConv::GHC(即cc10),返回nullptr或空数组
容易踩的坑:
- 忘记检查
MF是否为空(某些 early pass 可能传入 null) - 返回栈上分配的局部数组(如
static MCPhysReg CS[] = {...}是 OK 的,但MCPhysReg CS[] = {...}不行) - 在
cc10下仍返回非空列表,导致 LLVM 生成冗余的push/pop,破坏 GHC 寄存器固定假设
真正起作用的是你写进 getCalleeSavedRegs() 的逻辑,不是 TableGen 里的任何注释或字段。寄存器保存行为最终由这个函数和指令选择阶段的 CCState 协同决定 —— 后者负责按调用约定把参数分发到寄存器或栈,而前者确保被调用函数不会意外覆盖 caller 的活值。











