[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