llvm / llvm/llvm-project

[Xtensa] PrologEpilogInserter: "Cannot scavenge register without an emergency spill slot" with frame-pointer=all

Open
#195,412 1 comment 0 reactions 0 assignees View on GitHub
backend:Xtensa crash-on-valid
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.