[Xtensa] PrologEpilogInserter: "Cannot scavenge register without an emergency spill slot" with frame-pointer=all
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
## Summary
The Xtensa backend's `PrologEpilogInserter` aborts in `RegScavenger::scavengeRegisterBackwards` for any function with `"frame-pointer"="all"` that has **≥13 simultaneously-live `ptr` values at a call instruction located in a non-entry basic block**, at any optimisation level `≥ -O1`.
```
LLVM ERROR: Error while trying to spill A8 from class AR:
Cannot scavenge register without an emergency spill slot!
```
## Reproducer
`repro.ll` (12 lines):
```llvm
target datalayout = "e-m:e-p:32:32-i8:8:32-i16:16:32-i64:64-n32"
target triple = "xtensa-unknown-none-elf"
define void @repro(ptr %0, ptr %1, ptr %2, ptr %3, ptr %4, ptr %5, ptr %6, ptr %7, ptr %8, ptr %9, ptr %10, ptr %11, ptr %12) #0 {
entry:
br label %tail
tail:
tail call void null(ptr %0, ptr %1, ptr %2, ptr %3, ptr %4, ptr %5, ptr %6, ptr %7, ptr %8, ptr %9, ptr %10, ptr %11, ptr %12)
ret void
}
attributes #0 = { "frame-pointer"="all" }
```
```bash
$ llc -O1 -mtriple=xtensa-none-elf --filetype=obj repro.ll -o /dev/null
```
## Versions tested
Both upstream LLVM and Espressif's fork are affected:
| Toolchain | llc reports |
|---|---|
| Upstream LLVM 21.1.8 (Debian `llvm-21` package) | `Error while trying to spill **A8** from class AR` |
| Espressif `esp-clang/esp-20.1.1_20250829` | `Error while trying to spill **A10** from class AR` |
Same root cause (different register chosen by the scavenger). Filing here because the upstream backend reproduces it; please cross-link to https://github.com/espressif/esp-llvm-embedded-toolchain.
## Stack trace (upstream LLVM 21.1.8)
```
1. Function Pass Manager
2. Prologue/Epilogue Insertion & Frame Finalization on @repro
llvm::report_fatal_error
llvm::RegScavenger::scavengeRegisterBackwards
llvm::scavengeFrameVirtualRegs
[PEI::runOnMachineFunction]
llvm::MachineFunctionPass::runOnFunction
```
## What's required to trigger
I narrowed the trigger conditions by toggling each ingredient on/off (10 runs each):
| Variant | Crashes |
|---|---:|
| baseline (above) | 10/10 |
| `-O3` / `-O2` / `-O1` | 10/10 |
| `-O0` | 0/10 |
| no `"frame-pointer"="all"` attribute | 0/10 |
| call moved into entry block (no `br label`) | 0/10 |
| 12 ptr params (drop one) | 0/10 |
| 13 params declared but call uses ≤12 of them | 0/10 |
| `+windowed` (default) vs no-windowed (call0) | both 10/10 |
| direct call to a declared function | 10/10 |
| `call` instead of `tail call` | 10/10 |
So all four of these are necessary together: `"frame-pointer"="all"`, `-O ≥ 1`, the call in a non-entry block, and ≥13 ptr values live across it. Removing any one stops the crash. (The exact threshold is 13 — 12 doesn't trigger.)
## Workarounds (in user code)
- Drop `"frame-pointer"="all"` from the offending function's attributes
- Reduce simultaneously-live ptr values at the call site to ≤12
- Hoist the call into the entry block
- Compile the offending function at `-O0`
Contributor guide
Research direction
Run the provided llc command on repro.ll first, then trace PrologEpilogInserter through RegScavenger::scavengeRegisterBackwards and scavengeFrameVirtualRegs. Determine why frame-pointer="all" with many live pointer values in a non-entry block lacks an emergency spill slot. Done means the reproducer no longer aborts and has regression coverage.
Written by the indexing model from the issue text.
Assessment
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100