Issue 208946
Summary [Xtensa] Incoming stack arguments read at the wrong address when the frame is dynamically realigned (windowed ABI)
Labels new issue
Assignees
Reporter rnortman
    ## Summary

When a windowed-ABI function has an over-aligned stack object (alignment > 32,
forcing dynamic SP realignment) *and* takes arguments passed on the stack, the
generated code reads those arguments at the wrong address. The realignment
moves SP by a runtime-variable pad, but the incoming-argument frame indices are
still resolved as `SP + framesize + k`, which is only correct against the SP
value at function entry. Every stack-argument access is off by the pad
(1–64 bytes for a 64-byte-aligned object).

Neither condition alone miscompiles: with realignment only, all arguments stay
in registers; with stack arguments only, SP is never moved.

Confirmed on current main (31ddc7e56481). Also present in LLVM 20 and 21 (the
espressif/MabezDev forks used by the ESP32 toolchains reproduce identically).

I'll be pushing a PR with the fix right after filing this.

## Reproducer

```llvm
target triple = "xtensa-unknown-none-elf"
declare void @sink(ptr)

define i32 @victim(i32 %a, i32 %b, i32 %c, i32 %d, i32 %e, i32 %f,
                   ptr %g, ptr %h, ptr %i) {
  %buf = alloca [64 x i8], align 64   ; forces dynamic SP realignment
  call void @sink(ptr %buf)
  store i8 0, ptr %g                  ; %g/%h/%i are passed on the stack
  store i8 0, ptr %h
  store i8 0, ptr %i
  call void @sink(ptr %buf)
  ret i32 %a
}
```

```
llc -mtriple=xtensa -mattr=+windowed -O2 < repro.ll
```

Output (annotated):

```asm
victim:
        entry   a1, 160          # SP = entry_SP - 160
        movi    a8, 63
        movi    a9, 64
        and     a8, a1, a8
        sub     a8, a9, a8
        add     a1, a1, a8       # realign: SP += pad, pad in [1,64]
        ...
        l32i    a8, a1, 160      # %g: reads *(SP + 160) = entry_SP + pad   WRONG
        s8i     a9, a8, 0        #     should be entry_SP + 0
        l32i    a8, a1, 164      # %h: entry_SP + pad + 4                   WRONG
        s8i     a9, a8, 0
        l32i    a8, a1, 168      # %i: entry_SP + pad + 8                   WRONG
        s8i     a9, a8, 0
```

`(entry_SP - 160) + pad + 160 = entry_SP + pad`, but the caller placed `%g` at
`entry_SP + 0`. The loaded "pointer" is garbage from the caller's frame;
dereferencing it corrupts memory or traps.

Under the call8 convention the first six arguments are in a2–a7, so any
function with ≥7 arguments (or fewer, larger ones) plus an over-aligned local
hits this.

## Analysis

`XtensaFrameLowering::emitPrologue` realigns SP in place
(`AND`/`SUB`/`ADD a1, a1, pad`) when `MaxAlignment > 32`, but nothing preserves
the entry SP, and `XtensaRegisterInfo::eliminateFrameIndex` resolves fixed
(incoming-argument) frame objects against the moved SP with offsets computed
against the entry SP. Targets that support stack realignment normally keep a
frame or base pointer for exactly this case; the Xtensa backend has neither on
this path.

## Second defect on the same path: pad computed from an argument register when the function has dynamic allocas

I know you'd usually file these separately, but the bugs are on the same path
and fixed in the same PR.

The realignment sequence computes the pad from `getFrameRegister(MF)`:

```cpp
BuildMI(MBB, MBBI, DL, TII.get(Xtensa::AND))
    .addReg(RegMisAlign, RegState::Define)
    .addReg(FP)                                // A7 when hasFP
    .addReg(RegMisAlign);
```

When the function also has dynamic allocas, `getFrameRegister` is A7 — but the
`AND` executes before A7 is set up as the frame pointer, while it still holds
the sixth incoming argument. The frame is "realigned" by an amount derived
from an argument value, so the over-aligned object's alignment guarantee is
silently broken:

```asm
realign_alloca:
        entry   a1, 160
        movi    a8, 63
        ...
        and     a8, a7, a8       # a7 = incoming arg, not a frame register
        sub     a8, a9, a8
        add     a1, a1, a8       # SP moved by garbage
        or      a7, a1, a1       # only NOW does a7 become FP
```

(Reproducer: add `%dyn = alloca i8, i32 %n` to `victim` above.)

## Real-world impact

Found via a Rust firmware crash on ESP32-S3 (esp toolchain, which carries this
backend): a function with a 64-byte-aligned DMA buffer and 9 parameters
dereferenced a displaced argument pointer and died with LoadStoreError. Any
windowed-ABI code combining `#[repr(align(64))]`/`alignas(64)` stack objects
with stack-passed arguments is affected.

_______________________________________________
llvm-bugs mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs

Reply via email to