While building an open-source AIE2 emulator/validator we found what looks like an AIE2 codegen correctness bug in Peano, and wanted to flag it in case it's useful. Happy to help with a reduced test case or a fix if you'd like.
Summary
For simple scalar loops with a sub-word (int8_t/int16_t) output store, Peano's unrolling places the final st.s8/st.s16 in the loop-end (LE) bundle of a zero-overhead (hardware) loop. On real Phoenix (NPU1/AIE2) silicon that store is dropped on every back-edge -- only the final fall-through iteration commits -- so every Nth output element keeps its initial value. xchesscc compiles the same source correctly (it does not park the store at LE).
Reproducer
#include <stdint.h>
extern "C" void k(int8_t* __restrict out) {
for (int i = 0; i < 128; i++)
out[i] = i * i; // int8
}
Built with clang --target=aie2-none-unknown-elf -O2 -c k.cc -o k.o and disassembled with llvm-objdump -d --triple=aie2 k.o, the core loop is a zero-overhead loop whose LE bundle is the st.s8:
00000020 <.LBB0_1>: ; loop start (LS)
20: ... mul r1, r0, r0 ; mov dj0, r0
30: st.s8 r1, [p0, dj0]
...
7a: mul r2, r1, r1 ; data for the 4th unrolled element
7e: nop
00000080 <.L_LEnd0>: ; loop end (LE)
80: nopb ; st.s8 r2, [p0, dj0] ; nops ; nopxm ; nopv <-- parked at LE
Expected out[i] = (i*i) & 0xff. On hardware, every i % 4 == 3 -- the unrolled element whose store lands in the LE bundle -- reads 0, except the final iteration (which falls through with no back-edge):
| i |
3 |
7 |
11 |
15 |
... |
127 |
| expected |
9 |
49 |
121 |
-31 |
... |
1 |
| NPU |
0 |
0 |
0 |
0 |
... |
1 |
Why we think it's the LE-bundle store
- Only the element whose
st.s8 is the LE-bundle store is dropped; stores earlier in the loop body commit fine.
- It is specific to partial-word stores (
st.s8/st.s16), which are read-modify-write and commit late (AIE2Schedule.td: II_STHB MemoryCycles<[5,11]>, i.e. E11). A full-word st in the same slot is fine.
xchesscc does not place the partial-word store in the LE bundle and produces correct output.
This suggests the AIE2 scheduler should avoid placing a partial-word store in the LE bundle of a hardware loop (or account for its E11 commit against the back-edge fetch redirect), similar to how it already keeps the ls/le/lc loop-setup writes a fixed distance from LE.
Environment
- Target: AIE2 / Phoenix (NPU1, Ryzen AI)
- Peano:
llvm-aie@0b72475 (clang 21.0.0git)
- Reproduced on real NPU hardware;
xchesscc output is correct.
Glad to provide the full objdump, more reproducers, or a reduced test case -- just let us know what's most useful.
While building an open-source AIE2 emulator/validator we found what looks like an AIE2 codegen correctness bug in Peano, and wanted to flag it in case it's useful. Happy to help with a reduced test case or a fix if you'd like.
Summary
For simple scalar loops with a sub-word (
int8_t/int16_t) output store, Peano's unrolling places the finalst.s8/st.s16in the loop-end (LE) bundle of a zero-overhead (hardware) loop. On real Phoenix (NPU1/AIE2) silicon that store is dropped on every back-edge -- only the final fall-through iteration commits -- so every Nth output element keeps its initial value.xchesscccompiles the same source correctly (it does not park the store atLE).Reproducer
Built with
clang --target=aie2-none-unknown-elf -O2 -c k.cc -o k.oand disassembled withllvm-objdump -d --triple=aie2 k.o, the core loop is a zero-overhead loop whoseLEbundle is thest.s8:Expected
out[i] = (i*i) & 0xff. On hardware, everyi % 4 == 3-- the unrolled element whose store lands in theLEbundle -- reads0, except the final iteration (which falls through with no back-edge):Why we think it's the LE-bundle store
st.s8is the LE-bundle store is dropped; stores earlier in the loop body commit fine.st.s8/st.s16), which are read-modify-write and commit late (AIE2Schedule.td:II_STHBMemoryCycles<[5,11]>, i.e. E11). A full-wordstin the same slot is fine.xchessccdoes not place the partial-word store in theLEbundle and produces correct output.This suggests the AIE2 scheduler should avoid placing a partial-word store in the
LEbundle of a hardware loop (or account for its E11 commit against the back-edge fetch redirect), similar to how it already keeps thels/le/lcloop-setup writes a fixed distance fromLE.Environment
llvm-aie@0b72475(clang 21.0.0git)xchessccoutput is correct.Glad to provide the full objdump, more reproducers, or a reduced test case -- just let us know what's most useful.