[llvm] 57ad789 - [RISCV][SLP] Use common alignment when checking constant-stride loads (#222520)

via llvm-commits llvm-commits at lists.llvm.org
Fri Sep 11 12:10:35 PDT 2026


Author: Kiva
Date: 2026-09-12T03:10:30+08:00
New Revision: 57ad7898dc17e228c3ce1d34a53626080131f384

URL: https://github.com/llvm/llvm-project/commit/57ad7898dc17e228c3ce1d34a53626080131f384
DIFF: https://github.com/llvm/llvm-project/commit/57ad7898dc17e228c3ce1d34a53626080131f384.diff

LOG: [RISCV][SLP] Use common alignment when checking constant-stride loads (#222520)

Fix SLP's constant-stride load legality check, which can widen naturally
aligned 32-bit loads into misaligned 64-bit RVV accesses and cause
`SIGBUS`.

In `BoUpSLP::canVectorizeLoads`, the check uses the first sorted load's
alignment, which may be stronger than that of later widened groups. Pass
`CommonAlignment` to `analyzeConstantStrideCandidate` instead, matching
the existing cost model and code generation. This is conservative: it
fixes the legality check without deriving a more precise alignment for
each widened group's starting address, so it may reject some otherwise
legal widening.

### Reproducer

The following reproduces the failure on the tested [SpacemiT K1 / Banana
Pi F3](https://docs.banana-pi.org/en/BPI-F3/BananaPi_BPI-F3) running
RV64 Linux, compiled with `-O3 -march=rv64gcv -mabi=lp64d
-mstrict-align`:

```c
struct Row {
  unsigned x, y, padding[5];
};

_Static_assert(sizeof(unsigned) == 4, "requires 32-bit unsigned");
_Static_assert(sizeof(struct Row) == 28, "requires a 28-byte row");

__attribute__((noinline))
void gather_fields(const struct Row *p, unsigned *restrict out) {
  p = __builtin_assume_aligned(p, 16);
  out[0] = p[0].x + 3;
  out[1] = p[0].y + 3;
  out[2] = p[1].x + 3;
  out[3] = p[1].y + 3;
}

int main(void) {
  _Alignas(16) struct Row rows[2] = {
      {5, 9, {0}}, {11, 17, {0}}};
  unsigned out[4];
  gather_fields(rows, out);
  return out[0] != 8 || out[1] != 12 ||
         out[2] != 14 || out[3] != 20;
}
```

The alignment assumption is satisfied, and the four loads at byte
offsets `0`, `4`, `28`, and `32` are naturally aligned and in bounds.
SLP widens the adjacent pairs into:

```llvm
%v = call <2 x i64> @llvm.experimental.vp.strided.load.v2i64.p0.i64(
    ptr align 4 %p, i64 28, <2 x i1> splat (i1 true), i32 2)
```

On RV64 this lowers to:

```asm
li        a2, 28
vsetivli  zero, 2, e64, m1, ta, ma
vlse64.v  v8, (a0), a2
```

whose second active lane accesses `base + 28`, which is not eight-byte
aligned. The legality check incorrectly uses alignment 16, although the
emitted intrinsic already carries `align 4`.

On the tested machine, the example raises `SIGBUS` before the fix and
returns zero afterward. The fixed compiler uses 32-bit vector loads and
retains vectorized arithmetic.

Added: 
    

Modified: 
    llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
    llvm/test/Transforms/SLPVectorizer/RISCV/strided-load-common-alignment.ll

Removed: 
    


################################################################################
diff  --git a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
index 488bfb36b6ccc..2cb633be89c3c 100644
--- a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
+++ b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
@@ -6196,11 +6196,10 @@ BoUpSLP::LoadsState BoUpSLP::canVectorizeLoads(
             },
             SLPReVec))
       return LoadsState::CompressVectorize;
-    Align Alignment =
-        cast<LoadInst>(Order.empty() ? VL.front() : VL[Order.front()])
-            ->getAlign();
-    if (analyzeConstantStrideCandidate(PointerOps, ScalarTy, Alignment, Order,
-                                       Diff, Ptr0, SPtrInfo))
+    // Widened strided loads must be legal for every group, not just the first
+    // pointer, which may have a stronger alignment than the remaining loads.
+    if (analyzeConstantStrideCandidate(PointerOps, ScalarTy, CommonAlignment,
+                                       Order, Diff, Ptr0, SPtrInfo))
       return LoadsState::StridedVectorize;
   }
   if (!IsMaskedGatherLegal())

diff  --git a/llvm/test/Transforms/SLPVectorizer/RISCV/strided-load-common-alignment.ll b/llvm/test/Transforms/SLPVectorizer/RISCV/strided-load-common-alignment.ll
index 81203dbb5a047..0e34894be821c 100644
--- a/llvm/test/Transforms/SLPVectorizer/RISCV/strided-load-common-alignment.ll
+++ b/llvm/test/Transforms/SLPVectorizer/RISCV/strided-load-common-alignment.ll
@@ -5,13 +5,16 @@
 ;
 ; The first load's alignment does not describe every group. Widening each
 ; adjacent pair to i64 would leave the group at byte offset 28 misaligned.
-; FIXME: Do not form an i64 strided load in the RV64 strict-alignment case.
 
 define void @gather_fields(ptr %base, ptr %out) {
 ; RV64-LABEL: define void @gather_fields(
 ; RV64-SAME: ptr [[BASE:%.*]], ptr [[OUT:%.*]]) #[[ATTR0:[0-9]+]] {
-; RV64-NEXT:    [[TMP1:%.*]] = call <2 x i64> @llvm.experimental.vp.strided.load.v2i64.p0.i64(ptr align 4 [[BASE]], i64 28, <2 x i1> splat (i1 true), i32 2)
-; RV64-NEXT:    [[TMP5:%.*]] = bitcast <2 x i64> [[TMP1]] to <4 x i32>
+; RV64-NEXT:    [[P2:%.*]] = getelementptr inbounds i32, ptr [[BASE]], i64 7
+; RV64-NEXT:    [[TMP1:%.*]] = load <2 x i32>, ptr [[BASE]], align 16
+; RV64-NEXT:    [[TMP2:%.*]] = load <2 x i32>, ptr [[P2]], align 4
+; RV64-NEXT:    [[TMP3:%.*]] = shufflevector <2 x i32> [[TMP1]], <2 x i32> poison, <4 x i32> <i32 0, i32 1, i32 poison, i32 poison>
+; RV64-NEXT:    [[TMP4:%.*]] = shufflevector <2 x i32> [[TMP2]], <2 x i32> poison, <4 x i32> <i32 0, i32 1, i32 poison, i32 poison>
+; RV64-NEXT:    [[TMP5:%.*]] = shufflevector <2 x i32> [[TMP1]], <2 x i32> [[TMP2]], <4 x i32> <i32 0, i32 1, i32 2, i32 3>
 ; RV64-NEXT:    [[TMP6:%.*]] = add <4 x i32> [[TMP5]], splat (i32 3)
 ; RV64-NEXT:    store <4 x i32> [[TMP6]], ptr [[OUT]], align 4
 ; RV64-NEXT:    ret void
@@ -19,13 +22,13 @@ define void @gather_fields(ptr %base, ptr %out) {
 ; RV32-LABEL: define void @gather_fields(
 ; RV32-SAME: ptr [[BASE:%.*]], ptr [[OUT:%.*]]) #[[ATTR0:[0-9]+]] {
 ; RV32-NEXT:    [[P2:%.*]] = getelementptr inbounds i32, ptr [[BASE]], i64 7
-; RV32-NEXT:    [[Q2:%.*]] = getelementptr inbounds i32, ptr [[OUT]], i64 2
 ; RV32-NEXT:    [[TMP1:%.*]] = load <2 x i32>, ptr [[BASE]], align 16
-; RV32-NEXT:    [[TMP3:%.*]] = add <2 x i32> [[TMP1]], splat (i32 3)
 ; RV32-NEXT:    [[TMP2:%.*]] = load <2 x i32>, ptr [[P2]], align 4
-; RV32-NEXT:    [[TMP4:%.*]] = add <2 x i32> [[TMP2]], splat (i32 3)
-; RV32-NEXT:    store <2 x i32> [[TMP3]], ptr [[OUT]], align 4
-; RV32-NEXT:    store <2 x i32> [[TMP4]], ptr [[Q2]], align 4
+; RV32-NEXT:    [[TMP3:%.*]] = shufflevector <2 x i32> [[TMP1]], <2 x i32> poison, <4 x i32> <i32 0, i32 1, i32 poison, i32 poison>
+; RV32-NEXT:    [[TMP4:%.*]] = shufflevector <2 x i32> [[TMP2]], <2 x i32> poison, <4 x i32> <i32 0, i32 1, i32 poison, i32 poison>
+; RV32-NEXT:    [[TMP5:%.*]] = shufflevector <2 x i32> [[TMP1]], <2 x i32> [[TMP2]], <4 x i32> <i32 0, i32 1, i32 2, i32 3>
+; RV32-NEXT:    [[TMP6:%.*]] = add <4 x i32> [[TMP5]], splat (i32 3)
+; RV32-NEXT:    store <4 x i32> [[TMP6]], ptr [[OUT]], align 4
 ; RV32-NEXT:    ret void
 ;
 ; UNALIGNED-LABEL: define void @gather_fields(


        


More information about the llvm-commits mailing list