[llvm] [AArch64] Copy MMO in ld1 / st1 post index selection. (PR #199023)

Alexander Kornienko via llvm-commits llvm-commits at lists.llvm.org
Fri Jun 26 10:16:23 PDT 2026


alexfh wrote:

AI seems to agree that this is a miscompilation with the following explanation. I don't see any obvious errors in the explanation, but I'm also far from being an expert in this:

---

Here is why the compiler's behavior is incorrect (a miscompile) rather than a valid optimization under C++ aliasing rules:

### 1. Well-Defined Sequential C++ Semantics
When `arr.erase(0, 1)` shifts elements forward in a contiguous buffer (`dst < src`), the algorithm routes to `CopyUnaligned(dst, src, count)`. 
Suppose `dst = &buf[0]` and `src = &buf[16]`. In sequential program execution across 16-element chunks:
* **Iteration 0:** Reads `src[0..15]` (`&buf[16..31]`) $\rightarrow$ Writes `dst[0..15]` (`&buf[0..15]`).
* **Iteration 1:** Reads `src[16..31]` (`&buf[32..47]`) $\rightarrow$ Writes `dst[16..31]` (`&buf[16..31]`).

Notice that in forward copying (`dst < src`), each iteration writes to memory that was already read by the previous iteration. At no point in sequential execution does a load read memory that was overwritten by an earlier or later iteration. The C++ program logic is entirely well-defined and free of undefined behavior.

### 2. The Illegal Reordering by the Machine Scheduler
When `#pragma unroll 4` unrolls the loop body, it emits a sequence of four pairs of NEON vector loads and stores:
```text
Lane 0: v0 = vld1q_f32(src + 0);  vst1q_f32(dst + 0,  v0);
Lane 1: v1 = vld1q_f32(src + 16); vst1q_f32(dst + 16, v1); // dst + 16 is physical address &buf[16]
```
Because `dst = &buf[0]` and `src = &buf[16]`, the physical memory address of `dst + 16` is **identical** to `src + 0` (`&buf[16]`). 

Therefore, there is a strict **Read-After-Write (RAW) / Anti-Dependence** in physical memory: the load from `src + 0` (Lane 0) **must** complete before the store to `dst + 16` (Lane 1) executes.

### 3. Why the Compiler is Wrong (The Root Cause in LLVM)
While the pointer arguments `dst` and `src` are qualified with `__restrict__`, standard compiler memory model rules dictate that `__restrict__` allows assuming two pointers do not alias *unless physical program flow establishes a direct address overlap within the same execution block*. 

Prior to LLVM commit f0c423380111, AArch64 `ld1` and `st1` vector instructions lacked Machine Memory Operand (MMO) alias metadata. Consequently, the instruction scheduler treated vector loads and stores conservatively, preserving their sequential order in the emitted assembly.

Commit f0c423380111 attached Type-Based Alias Analysis (TBAA) and scoped alias metadata to `ld1`/`st1` MMOs. When building the dependence Directed Acyclic Graph (DAG) for the unrolled loop, `machine-scheduler` evaluates the alias metadata on the MMOs, sees `__restrict__` derived from separate function parameters, and erroneously concludes that **no store to `dst` can ever alias with any load from `src` across the entire unrolled block.** 

Because it drops the physical memory dependence between `dst + 16` and `src + 0`, the scheduler interleaves Lane 1's store before Lane 0's load to optimize pipeline latency. The load subsequently reads overwritten, corrupted data, proving that the compiler's alias analysis / dependence tracking is unsound in this unrolled overlapping pattern.

https://github.com/llvm/llvm-project/pull/199023


More information about the llvm-commits mailing list