函数序言和尾声由PrologEpilogInserter pass在寄存器分配后、指令调度完成时调用TargetFrameLowering::emitPrologue/emitEpilogue生成,插入到MachineInstr列表中,不依赖IR或DAG,仅操作机器指令。

函数序言(prologue)和尾声(epilogue)由 TargetFrameLowering 子类生成,不是手写汇编,也不是在指令选择阶段插入——它发生在寄存器分配之后、指令调度完成、MachineInstr 已成型但尚未发射的阶段。
什么时候调用 emitPrologue 和 emitEpilogue
LLVM 后端在 MachineFunctionPass 流水线中,由 PrologEpilogInserter pass 负责统一插入。该 pass 会遍历每个 MachineFunction,在入口基本块开头调用 TargetFrameLowering::emitPrologue,在每个有返回指令(如 RET、BR_RET)的基本块末尾调用 emitEpilogue。
- 这个 pass 运行在寄存器分配(
RegisterAllocator)之后,因此能准确知道哪些虚拟寄存器被 spill 到栈上,也清楚帧指针(FP)、栈指针(SP)的偏移关系 - 它不关心 IR 或 DAG,只操作
MachineInstr列表,直接往MBB->instr_begin()或MBB->instr_end()插入新指令 - 如果你看到序言里有
ADD SP, SP, #-16或STR R0, [SP, #-4]!这类指令,它们是 C++ 代码动态 emit 出来的,不是 TableGen 模式匹配出来的
TargetFrameLowering 必须重写的两个核心函数
每个目标后端都需继承并实现 TargetFrameLowering,最关键的两个虚函数是:
-
emitPrologue(MachineFunction &MF, MachineBasicBlock &MBB):负责分配栈空间、保存 callee-saved 寄存器、设置帧指针(如果需要)。注意:它不负责计算栈大小——那是computeStackSize阶段干的,结果存在MF.getFrameInfo()->getStackSize() -
emitEpilogue(MachineFunction &MF, MachineBasicBlock &MBB):恢复 callee-saved 寄存器、调整栈指针、跳过帧指针恢复逻辑(除非显式启用hasFP) - 还必须实现
hasFP(const MachineFunction &MF)—— 它决定是否把帧指针当作独立寄存器管理;若返回 true,emitPrologue通常要插入MOV FP, SP类似指令,且后续所有栈访问都基于FP偏移而非SP
常见错误:栈偏移算错、寄存器保存顺序颠倒、漏处理变长栈分配
序言/尾声出错,往往不是语法问题,而是语义错位。典型表现包括:
- 函数返回后寄存器值异常 → 检查
getCalleeSavedRegs()返回的寄存器列表是否与emitPrologue中实际保存的顺序、数量、条件(如是否仅在有栈溢出时才保存)严格一致 - 局部变量读写越界或值错乱 → 栈帧大小计算错误,常见于未正确处理
hasVarSizedObjects():若函数含变长数组(alloca),getStackSize()不再是常量,必须在运行时计算并用指令动态调整SP - 调试信息错乱或 GDB 显示栈回溯断裂 → 忘记在
emitPrologue中调用addRegOffset注册CFI指令(如CFI_ADJUST_CFA_OFFSET、CFI_REMEMBER_STATE),导致 DWARF 无法重建调用栈 - 硬故障或非法指令 → 在
emitPrologue中试图使用未定义的机器指令(比如用ARM::STRi12但当前 subtarget 不支持 Thumb2),应优先通过getInstrInfo()->get(...)获取合法 MCID
调试建议:用 -mllvm -print-machineinstrs 看真实插入点
光看 C++ 实现容易误判执行时机。最有效的方式是编译时加参数观察中间状态:
-
llc -march=yourtarget -mllvm -print-machineinstrs input.ll 2>&1 | grep -A5 -B5 "PROLOG"可定位到序言指令实际插在哪条MachineInstr前后 - 配合
-debug-only=regalloc或-debug-only=stack-slot查看帧信息如何被计算和传播 - 若修改了
emitPrologue但没生效,先确认YourTargetFrameLowering是否已注册进YourTargetTargetMachine::createTargetFrameLowering,且TM.getSubtargetImpl()->getFrameLowering()确实返回你的实例
序言和尾声看似只是几条栈操作指令,但它横跨帧布局、寄存器保存策略、CFI 调试信息、变长分配四大耦合模块,任何一处 mismatch 都会导致静默崩溃或难以复现的栈破坏——动手前务必通读本 target 的 FrameLowering 实现和对应 ABI 文档。

















