[RISCV] Disable shrink wrapping for SiFive CLIC and QCI handlers
Project: llvm/llvm-project | Language: C++ | Concepts: refactoring, testing-strategy | Difficulty: mid
Link: https://github.com/llvm/llvm-project/pull/218347
The problem
On RISC-V, certain interrupt handler types (SiFive CLIC and QCI) require mandatory prologue instructions to always execute at function entry. With LLVM's shrink wrapping optimization enabled, these prologues could be incorrectly moved, resulting in them not running on all paths, which produces invalid code.
Why it's non-trivial
A naive fix might simply disable shrink wrapping for all interrupt handlers. However, not all handler types require this: only QCI and SiFive CLIC have mandatory software prologues. Other types (like "machine", "supervisor", and "rnmi") are handled by hardware and don't need this restriction. The fix must precisely target only the affected handler kinds, preserving optimization elsewhere.
The approach
1. Disable shrink wrapping for problematic handler kinds in the target hook
The core change is in the RISCVFrameLowering class. The target hook enableShrinkWrapping is updated to return false for SiFive CLIC preemptible handlers:
bool RISCVFrameLowering::enableShrinkWrapping(const MachineFunction &MF) const {
+ if (MF.getInfo<RISCVMachineFunctionInfo>()->isSiFivePreemptibleInterrupt(MF))
+ return false;
This ensures that, for these handlers, shrink wrapping is disabled, so the mandatory prologue will always execute before any handler code.
2. Tests to verify correct prologue placement
A new test file is added to check that the required prologue instructions (e.g., csrsi mstatus, 8) remain at the entry of SiFive CLIC preemptible handlers:
define void @handler() "interrupt"="SiFive-CLIC-preemptible" {
; CHECK-LABEL: handler:
; CHECK: csrsi mstatus, 8
; CHECK: beqz tp,
entry:
%tp = call ptr @llvm.thread.pointer()
%isnull = icmp eq ptr %tp, null
br i1 %isnull, label %panic, label %return
...
}
This test ensures the prologue instruction is always present before any conditional logic.
3. Documentation update
The release notes are updated to mention the fix:
+* Fixed SiFive CLIC preemptible interrupt handlers potentially executing
+ without their required prologue when shrink wrapping was enabled.
From the review thread
zeyi2:
A short rationale on why I chose to disable the pass:ShrinkWrap runs before the target-specific CLIC prologue is emitted, so it cannot see that saving
mcause/mepcand enabling interrupts are mandatory on every handler path.
TargetFrameLowering::enableShrinkWrapping()is the existing target hook for cases where a target-specific prologue cannot be safely shrink-wrapped. Other backends use it for similar entry-only prologues, such as ARM CMSE entry functions and X86 segmented stacks.
zeyi2:
beqz tpis the first branch from the handler body. This verifies that the mandatory preemption setup (csrsi mstatus, 8) remains in the entry block and executes before the handler body.The required ordering is described here.
Takeaways
- If a function's prologue contains mandatory, target-specific setup that must run on all entry paths, disable shrink wrapping for those functions using the appropriate target hook.
- Use targeted test cases to verify that required prologue instructions remain at function entry, especially after optimization passes.
- Consult and follow existing backend conventions for handling target-specific prologue/epilogue requirements when integrating with generic compiler optimizations.