llvm后端实现栈帧布局的核心是重写targetframelowering子类,在emitprologue/emitepilogue中生成正确栈指令,通过machineframeinfo统一管理slot分配,并由getframeindexoffset返回相对于fp/sp的字节偏移以支持地址计算。

LLVM 后端实现栈帧布局,核心在于重写 TargetFrameLowering 子类,并在 emitPrologue / emitEpilogue 中生成正确的栈操作指令。它不是靠硬编码偏移,而是通过 MachineFrameInfo 统一管理 slot 分配,再由后端决定如何将 slot 映射到寄存器或栈内存。
怎么让 LLVM 知道局部变量该放哪
LLVM 不直接处理 C 变量名,而是把每个 alloca 指令转为一个 MachineFrameInfo slot(用 FrameIndex 编号)。后端要做的,是告诉 LLVM:这个 slot 是放在栈上、还是某个 callee-saved 寄存器里、偏移多少、是否需要对齐。
-
getFrameIndexOffset(MF, FI)必须返回该FrameIndex相对于frame pointer或stack pointer的字节偏移 —— 这个值最终会参与地址计算(如addi sp, sp, -16+sw a0, 4(sp)) - 若 slot 大小超过寄存器宽度(比如 128-bit 向量),且目标不支持向量寄存器 spill/fill,则必须分配栈空间;否则可考虑
isSafeToReallocate启用寄存器复用 - RISC-V 向量扩展(V extension)下,
getStackAlignment()需返回VLEN/8(如 VLEN=256 → 对齐 32 字节),否则llvm::alignTo计算出的 slot 偏移可能破坏向量访存边界
Prologue/Epilogue 里哪些指令不能少
emitPrologue 不只是 push 寄存器,它要协调栈增长、帧指针建立、slot 分配三件事。漏掉任一环节,会导致后续 FrameIndex 地址计算错位,甚至段错误。
- 先调用
MFI->setHasCalls()(如有 call),否则getCalleeSavedSpillSlots可能不触发 - 用
BuildMI插入ADDI(RISC-V)或SUBSP(ARM)调整sp;注意:若启用frame pointer,还需ADDI fp, sp, #offset - 对每个需保存的 callee-saved 寄存器,调用
saveCalleeSavedRegisters—— 它内部会查getCalleeSavedSpillSlots返回的std::vector<calleesavedinfo></calleesavedinfo>,并为每个 slot 插入 store 指令 -
emitEpilogue要逆序恢复:先 load 寄存器,再ADDI sp, sp, #size,最后ret;顺序颠倒会导致sp提前恢复,后续 load 访问野地址
SafeStack 和向量栈帧怎么共存
SafeStack 把敏感数据(返回地址、ra)和普通局部变量分到两个栈上,而 RISC-V 向量扩展要求大块连续对齐内存。两者叠加时,MachineFrameInfo 会为两类 slot 分别维护独立 offset 计数器,但后端必须确保:
- 向量 slot 的分配不跨 SafeStack 的「unsafe stack」边界 —— 否则
getFrameIndexOffset返回的偏移无法被安全栈的 base 寻址模式覆盖 -
getStackAlignment()应取二者最大值(如 SafeStack 默认 16 字节,VLEN=512 → 64 字节),否则向量 load/store 触发 misaligned trap - 避免在
emitPrologue中混用两套栈指针(如sp和ssp)做同一 slot 的地址计算;LLVM 当前不自动推导 multi-stack 地址表达式,需手动检查MFI->getStackID(FI) == TargetStackID::Safe
真正容易被忽略的是:栈帧布局逻辑一旦写进 TargetFrameLowering,就会影响所有优化阶段 —— 比如 RegAllocFast 会依据 slot 大小决定是否 spill,StackSlotColoring 依赖 offset 对齐性做合并。改 layout 不单是修几个数字,得同步验证 llc -march=riscv32 -mattr=+v 下的 prologue 输出和实际运行时内存布局是否一致。











