[llvm] [SROA] Allow canonicalizing homogeneous array allocas to vectors (PR #216456)

Yonah Goldberg via llvm-commits llvm-commits at lists.llvm.org
Fri Aug 14 23:19:21 PDT 2026


https://github.com/YonahGoldberg created https://github.com/llvm/llvm-project/pull/216456

Follow-up on https://github.com/llvm/llvm-project/pull/165159. Fixes https://github.com/llvm/llvm-project/issues/164308.

>From the issue:

> Looking at the SROA source and debug output the difference seems to be
it allows merging the 2 x i32 alloca into an i64 but not the 2 x float

Now we can change the `[2 x float]` alloca to `<2 x float>` and promote it.

Julia will need to pass in the `aggregate-to-vector` option to their late SROA pass. We can't turn this on by default because it's harmful before `memcpyopt`.

>From 0670d7a734b5d134a2951069d0c44f27f822076b Mon Sep 17 00:00:00 2001
From: Yonah Goldberg <ygoldberg at nvidia.com>
Date: Sat, 15 Aug 2026 06:11:39 +0000
Subject: [PATCH] fix

---
 llvm/include/llvm/Transforms/Scalar/SROA.h    |  2 +-
 llvm/lib/Passes/PassBuilderPipelines.cpp      | 30 ++++-----
 llvm/lib/Transforms/Scalar/SROA.cpp           | 62 +++++++++++--------
 llvm/test/Transforms/SROA/struct-to-vector.ll | 29 +++++++++
 4 files changed, 82 insertions(+), 41 deletions(-)

diff --git a/llvm/include/llvm/Transforms/Scalar/SROA.h b/llvm/include/llvm/Transforms/Scalar/SROA.h
index 3c0e99f6dee19..7b43a22b29ed4 100644
--- a/llvm/include/llvm/Transforms/Scalar/SROA.h
+++ b/llvm/include/llvm/Transforms/Scalar/SROA.h
@@ -38,7 +38,7 @@ class SROAPass : public OptionalPassInfoMixin<SROAPass> {
   /// If \p PreserveCFG is set, then the pass is not allowed to modify CFG
   /// in any way, even if it would update CFG analyses.
   /// If \p AggregateToVector is set, then the pass will try to convert
-  /// allocas of homogeneous structs into vector allocas.
+  /// allocas of homogeneous structs and arrays into vector allocas.
   LLVM_ABI SROAPass(SROAOptions Options);
 
   /// Run the pass over the function.
diff --git a/llvm/lib/Passes/PassBuilderPipelines.cpp b/llvm/lib/Passes/PassBuilderPipelines.cpp
index b8c5b1eab2f97..f600c9fcaed8e 100644
--- a/llvm/lib/Passes/PassBuilderPipelines.cpp
+++ b/llvm/lib/Passes/PassBuilderPipelines.cpp
@@ -1375,13 +1375,14 @@ void PassBuilder::addVectorPasses(OptimizationLevel Level,
     // or SimplifyCFG passes scheduled after us, that would cleanup
     // the CFG mess this may created if allowed to modify CFG, so forbid that.
 
-    // We also turn on struct to vector canonicalization here, which allows
-    // converting allocas of homogeneous structs into vector allocas when the
-    // allocas' users are all memory intrinsics. This allows promotion in some
-    // cases because structs cannot promote to SSA values, but vectors can. We
-    // only turn this on after memcpyopt runs because this might hinder
-    // memcpyopt's optimizations if done before. Look at the documentation for
-    // `tryCanonicalizeStructToVector` in SROA.cpp to see why.
+    // We also turn on aggregate to vector canonicalization here, which allows
+    // converting allocas of homogeneous structs and arrays into vector allocas
+    // when the allocas' users are all memory intrinsics. This allows promotion
+    // in some cases because aggregates cannot promote to SSA values, but
+    // vectors can. We only turn this on after memcpyopt runs because this might
+    // hinder memcpyopt's optimizations if done before. Look at the
+    // documentation for `tryCanonicalizeAggregateToVector` in SROA.cpp to see
+    // why.
     FPM.addPass(SROAPass(SROAOptions(SROAOptions::PreserveCFG,
                                      /*AggregateToVector=*/true)));
   }
@@ -1476,13 +1477,14 @@ void PassBuilder::addVectorPasses(OptimizationLevel Level,
     // or SimplifyCFG passes scheduled after us, that would cleanup
     // the CFG mess this may created if allowed to modify CFG, so forbid that.
 
-    // We also turn on struct to vector canonicalization here, which allows
-    // converting allocas of homogeneous structs into vector allocas when the
-    // allocas' users are all memory intrinsics. This allows promotion in some
-    // cases because structs cannot promote to SSA values, but vectors can. We
-    // only turn this on after memcpyopt runs because this might hinder
-    // memcpyopt's optimizations if done before. Look at the documentation for
-    // `tryCanonicalizeStructToVector` in SROA.cpp to see why.
+    // We also turn on aggregate to vector canonicalization here, which allows
+    // converting allocas of homogeneous structs and arrays into vector allocas
+    // when the allocas' users are all memory intrinsics. This allows promotion
+    // in some cases because aggregates cannot promote to SSA values, but
+    // vectors can. We only turn this on after memcpyopt runs because this might
+    // hinder memcpyopt's optimizations if done before. Look at the
+    // documentation for `tryCanonicalizeAggregateToVector` in SROA.cpp to see
+    // why.
     FPM.addPass(SROAPass(SROAOptions(SROAOptions::PreserveCFG,
                                      /*AggregateToVector=*/true)));
   }
diff --git a/llvm/lib/Transforms/Scalar/SROA.cpp b/llvm/lib/Transforms/Scalar/SROA.cpp
index bf31273b6e405..2a5aea778d259 100644
--- a/llvm/lib/Transforms/Scalar/SROA.cpp
+++ b/llvm/lib/Transforms/Scalar/SROA.cpp
@@ -5301,12 +5301,12 @@ bool SROA::presplitLoadsAndStores(AllocaInst &AI, AllocaSlices &AS) {
   return true;
 }
 
-/// Try to canonicalize a homogeneous struct partition to a vector type.
+/// Try to canonicalize a homogeneous aggregate partition to a vector type.
 ///
-/// We can do this if all the elements of the struct are the same and the
-/// corresponding vector has the same byte-level layout. This can sometimes
-/// eliminate allocas because structs cannot get promoted to LLVM values, but
-/// vectors can.
+/// We can do this if all the elements of the struct or array are the same and
+/// the corresponding vector has the same byte-level layout. This can sometimes
+/// eliminate allocas because aggregates cannot get promoted to LLVM values,
+/// but vectors can.
 ///
 /// We only apply this transformation when all users of the partition are memory
 /// intrinsics. Otherwise, if there is a load or store of some other type to the
@@ -5324,13 +5324,24 @@ bool SROA::presplitLoadsAndStores(AllocaInst &AI, AllocaSlices &AS) {
 ///
 /// As such, we only apply this transformation after memcpyopt has run. We gate
 /// this transformation by the "AggregateToVector" pass option.
-static FixedVectorType *tryCanonicalizeStructToVector(StructType *STy,
-                                                      Partition &P,
-                                                      const DataLayout &DL) {
-  unsigned NumElts = STy->getNumElements();
+static FixedVectorType *tryCanonicalizeAggregateToVector(Type *AggregateTy,
+                                                         Partition &P,
+                                                         const DataLayout &DL) {
+  Type *EltTy;
+  uint64_t NumElts;
+  if (auto *STy = dyn_cast<StructType>(AggregateTy)) {
+    NumElts = STy->getNumElements();
+    if (!llvm::all_equal(STy->elements()))
+      return nullptr;
+    EltTy = STy->getElementType(0);
+  } else if (auto *ATy = dyn_cast<ArrayType>(AggregateTy)) {
+    NumElts = ATy->getNumElements();
+    EltTy = ATy->getElementType();
+  } else {
+    return nullptr;
+  }
 
-  Type *EltTy = STy->getElementType(0);
-  if (!llvm::all_equal(STy->elements()))
+  if (NumElts > std::numeric_limits<unsigned>::max())
     return nullptr;
 
   bool IsIntegralPointerTy =
@@ -5339,19 +5350,19 @@ static FixedVectorType *tryCanonicalizeStructToVector(StructType *STy,
       !IsIntegralPointerTy)
     return nullptr;
 
-  // Ensure the struct is tightly packed so that the bit-layout is the same as
-  // the corresponding vector. For example, this prevents a miscompile for
-  // { i5, i5 }, which has padding after each i5 field, whereas <i5, i5> has
-  // tightly packed elements and trailing padding.
+  // Ensure the aggregate is tightly packed so that the bit-layout is the same
+  // as the corresponding vector. For example, this prevents a miscompile for
+  // { i5, i5 } or [2 x i5], which have padding after each i5 element, whereas
+  // <2 x i5> has tightly packed elements and trailing padding.
   if (DL.getTypeSizeInBits(EltTy) != DL.getTypeAllocSizeInBits(EltTy))
     return nullptr;
 
-  auto *VTy = FixedVectorType::get(EltTy, NumElts);
-  TypeSize StructSize = DL.getStructLayout(STy)->getSizeInBytes();
+  auto *VTy = FixedVectorType::get(EltTy, static_cast<unsigned>(NumElts));
+  TypeSize AggregateSize = DL.getTypeStoreSize(AggregateTy);
   TypeSize VectorSize = DL.getTypeStoreSize(VTy);
   // After ruling out per-element padding, make sure a vector load/store
-  // covers the same number of bytes as the struct layout.
-  if (StructSize != VectorSize)
+  // covers the same number of bytes as the aggregate layout.
+  if (AggregateSize != VectorSize)
     return nullptr;
 
   auto IsIgnorableOrMemIntrinsicSlice = [](const Slice &S) {
@@ -5483,14 +5494,13 @@ selectPartitionType(Partition &P, const DataLayout &DL, AllocaInst &AI,
       return {LargestIntTy, true, nullptr};
     }
 
-    // Try homogeneous struct to vector canonicalization when requested. Running
-    // this too early can hide memcpy chains from MemCpyOpt.
+    // Try homogeneous aggregate to vector canonicalization when requested.
+    // Running this too early can hide memcpy chains from MemCpyOpt.
     if (AggregateToVector) {
-      if (auto *STy = dyn_cast<StructType>(TypePartitionTy)) {
-        if (auto *VTy = tryCanonicalizeStructToVector(STy, P, DL)) {
-          LogSelection("struct-fallback-vecty", VTy, nullptr, false);
-          return {VTy, false, nullptr};
-        }
+      if (auto *VTy =
+              tryCanonicalizeAggregateToVector(TypePartitionTy, P, DL)) {
+        LogSelection("aggregate-fallback-vecty", VTy, nullptr, false);
+        return {VTy, false, nullptr};
       }
     }
 
diff --git a/llvm/test/Transforms/SROA/struct-to-vector.ll b/llvm/test/Transforms/SROA/struct-to-vector.ll
index b7fb3cc97d66a..ffd3dcbf3c85f 100644
--- a/llvm/test/Transforms/SROA/struct-to-vector.ll
+++ b/llvm/test/Transforms/SROA/struct-to-vector.ll
@@ -418,3 +418,32 @@ entry:
   call void @llvm.memcpy.p0.p0.i32(ptr align 4 %e, ptr align 4 %c, i32 12, i1 true)
   ret void
 }
+
+define void @array_f32x2_memcpy_through_alloca(ptr %dst, ptr %src) {
+; CHECK-LABEL: define void @array_f32x2_memcpy_through_alloca(
+; CHECK-SAME: ptr [[DST:%.*]], ptr [[SRC:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[TMP0:%.*]] = load <2 x float>, ptr [[SRC]], align 4
+; CHECK-NEXT:    store <2 x float> [[TMP0]], ptr [[DST]], align 4
+; CHECK-NEXT:    ret void
+;
+entry:
+  %a = alloca [2 x float], align 4
+  call void @llvm.memcpy.p0.p0.i64(ptr align 4 %a, ptr align 4 %src, i64 8, i1 false)
+  call void @llvm.memcpy.p0.p0.i64(ptr align 4 %dst, ptr align 4 %a, i64 8, i1 false)
+  ret void
+}
+
+define void @array_i5x2_memcpy_into_alloca(ptr %c) {
+; CHECK-LABEL: define void @array_i5x2_memcpy_into_alloca(
+; CHECK-SAME: ptr [[C:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[E:%.*]] = alloca [2 x i5], align 1
+; CHECK-NEXT:    call void @llvm.memcpy.p0.p0.i32(ptr align 1 [[E]], ptr align 1 [[C]], i32 2, i1 true)
+; CHECK-NEXT:    ret void
+;
+entry:
+  %e = alloca [2 x i5], align 1
+  call void @llvm.memcpy.p0.p0.i32(ptr align 1 %e, ptr align 1 %c, i32 2, i1 true)
+  ret void
+}



More information about the llvm-commits mailing list