[llvm] [SROA][TTI][DirectX] Add support for struct alloca decomposition (PR #161601)
Deric C. via llvm-commits
llvm-commits at lists.llvm.org
Wed Oct 1 16:14:00 PDT 2025
https://github.com/Icohedron updated https://github.com/llvm/llvm-project/pull/161601
>From 54be8658db7c950072aec1f81866cdb2c17fdb99 Mon Sep 17 00:00:00 2001
From: Deric Cheung <cheung.deric at gmail.com>
Date: Fri, 26 Sep 2025 10:29:55 -0700
Subject: [PATCH 1/5] Add decompose-structs option to SROA
---
llvm/include/llvm/Transforms/Scalar.h | 3 +-
llvm/include/llvm/Transforms/Scalar/SROA.h | 22 +++++++++++--
llvm/lib/Passes/PassBuilder.cpp | 33 ++++++++++++++------
llvm/lib/Transforms/Scalar/SROA.cpp | 36 ++++++++++++++--------
4 files changed, 67 insertions(+), 27 deletions(-)
diff --git a/llvm/include/llvm/Transforms/Scalar.h b/llvm/include/llvm/Transforms/Scalar.h
index 8e68b6a57e51f..20c05687e1b4c 100644
--- a/llvm/include/llvm/Transforms/Scalar.h
+++ b/llvm/include/llvm/Transforms/Scalar.h
@@ -44,7 +44,8 @@ LLVM_ABI FunctionPass *createDeadStoreEliminationPass();
//
// SROA - Replace aggregates or pieces of aggregates with scalar SSA values.
//
-LLVM_ABI FunctionPass *createSROAPass(bool PreserveCFG = true);
+LLVM_ABI FunctionPass *createSROAPass(bool PreserveCFG = true,
+ bool DecomposeStructs = false);
//===----------------------------------------------------------------------===//
//
diff --git a/llvm/include/llvm/Transforms/Scalar/SROA.h b/llvm/include/llvm/Transforms/Scalar/SROA.h
index 8bb65bf7225e0..1de37b749f847 100644
--- a/llvm/include/llvm/Transforms/Scalar/SROA.h
+++ b/llvm/include/llvm/Transforms/Scalar/SROA.h
@@ -21,15 +21,31 @@ namespace llvm {
class Function;
-enum class SROAOptions : bool { ModifyCFG, PreserveCFG };
+struct SROAOptions {
+ enum PreserveCFGOption : bool { ModifyCFG, PreserveCFG };
+ enum DecomposeStructsOption : bool { NoDecomposeStructs, DecomposeStructs };
+ PreserveCFGOption PCFGOption;
+ DecomposeStructsOption DSOption;
+ SROAOptions(PreserveCFGOption PCFGOption)
+ : PCFGOption(PCFGOption), DSOption(NoDecomposeStructs) {}
+ SROAOptions(PreserveCFGOption PCFGOption, DecomposeStructsOption DSOption)
+ : PCFGOption(PCFGOption), DSOption(DSOption) {}
+};
class SROAPass : public PassInfoMixin<SROAPass> {
- const SROAOptions PreserveCFG;
+ const SROAOptions Options;
public:
/// If \p PreserveCFG is set, then the pass is not allowed to modify CFG
/// in any way, even if it would update CFG analyses.
- SROAPass(SROAOptions PreserveCFG);
+ SROAPass(SROAOptions::PreserveCFGOption PreserveCFG);
+
+ /// If \p Options.PreserveCFG is set, then the pass is not allowed to modify
+ /// CFG in any way, even if it would update CFG analyses.
+ /// If \p Options.DecomposeStructs is set, then the pass will decompose
+ /// structs allocas into its constituent components regardless of whether or
+ /// not pointer offsets into them are known at compile time.
+ SROAPass(const SROAOptions &Options);
/// Run the pass over the function.
PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
diff --git a/llvm/lib/Passes/PassBuilder.cpp b/llvm/lib/Passes/PassBuilder.cpp
index c234623caecf9..4f918a33f4dc3 100644
--- a/llvm/lib/Passes/PassBuilder.cpp
+++ b/llvm/lib/Passes/PassBuilder.cpp
@@ -1353,16 +1353,29 @@ Expected<ScalarizerPassOptions> parseScalarizerOptions(StringRef Params) {
}
Expected<SROAOptions> parseSROAOptions(StringRef Params) {
- if (Params.empty() || Params == "modify-cfg")
- return SROAOptions::ModifyCFG;
- if (Params == "preserve-cfg")
- return SROAOptions::PreserveCFG;
- return make_error<StringError>(
- formatv("invalid SROA pass parameter '{}' (either preserve-cfg or "
- "modify-cfg can be specified)",
- Params)
- .str(),
- inconvertibleErrorCode());
+ SROAOptions::PreserveCFGOption PreserveCFG = SROAOptions::ModifyCFG;
+ SROAOptions::DecomposeStructsOption DecomposeStructs =
+ SROAOptions::NoDecomposeStructs;
+
+ while (!Params.empty()) {
+ StringRef ParamName;
+ std::tie(ParamName, Params) = Params.split(';');
+
+ if (ParamName.consume_front("preserve-cfg"))
+ PreserveCFG = SROAOptions::PreserveCFG;
+ else if (ParamName.consume_front("modify-cfg"))
+ PreserveCFG = SROAOptions::ModifyCFG;
+ else if (ParamName.consume_front("no-decompose-structs"))
+ DecomposeStructs = SROAOptions::NoDecomposeStructs;
+ else if (ParamName.consume_front("decompose-structs"))
+ DecomposeStructs = SROAOptions::DecomposeStructs;
+ else
+ return make_error<StringError>(
+ formatv("invalid SROA pass option '{}'", ParamName).str(),
+ inconvertibleErrorCode());
+ }
+
+ return SROAOptions(PreserveCFG, DecomposeStructs);
}
Expected<StackLifetime::LivenessType>
diff --git a/llvm/lib/Transforms/Scalar/SROA.cpp b/llvm/lib/Transforms/Scalar/SROA.cpp
index 45d3d493a9e68..7779d0a4be415 100644
--- a/llvm/lib/Transforms/Scalar/SROA.cpp
+++ b/llvm/lib/Transforms/Scalar/SROA.cpp
@@ -174,6 +174,7 @@ class SROA {
DomTreeUpdater *const DTU;
AssumptionCache *const AC;
const bool PreserveCFG;
+ const bool DecomposeStructs;
/// Worklist of alloca instructions to simplify.
///
@@ -236,9 +237,10 @@ class SROA {
public:
SROA(LLVMContext *C, DomTreeUpdater *DTU, AssumptionCache *AC,
- SROAOptions PreserveCFG_)
+ const SROAOptions &Options)
: C(C), DTU(DTU), AC(AC),
- PreserveCFG(PreserveCFG_ == SROAOptions::PreserveCFG) {}
+ PreserveCFG(Options.PCFGOption == SROAOptions::PreserveCFG),
+ DecomposeStructs(Options.DSOption == SROAOptions::DecomposeStructs) {}
/// Main run method used by both the SROAPass and by the legacy pass.
std::pair<bool /*Changed*/, bool /*CFGChanged*/> runSROA(Function &F);
@@ -6040,7 +6042,7 @@ PreservedAnalyses SROAPass::run(Function &F, FunctionAnalysisManager &AM) {
AssumptionCache &AC = AM.getResult<AssumptionAnalysis>(F);
DomTreeUpdater DTU(DT, DomTreeUpdater::UpdateStrategy::Lazy);
auto [Changed, CFGChanged] =
- SROA(&F.getContext(), &DTU, &AC, PreserveCFG).runSROA(F);
+ SROA(&F.getContext(), &DTU, &AC, Options).runSROA(F);
if (!Changed)
return PreservedAnalyses::all();
PreservedAnalyses PA;
@@ -6054,23 +6056,29 @@ void SROAPass::printPipeline(
raw_ostream &OS, function_ref<StringRef(StringRef)> MapClassName2PassName) {
static_cast<PassInfoMixin<SROAPass> *>(this)->printPipeline(
OS, MapClassName2PassName);
- OS << (PreserveCFG == SROAOptions::PreserveCFG ? "<preserve-cfg>"
- : "<modify-cfg>");
+ OS << (Options.PCFGOption == SROAOptions::PreserveCFG
+ ? "<preserve-cfg>"
+ : "<modify-cfg>");
}
-SROAPass::SROAPass(SROAOptions PreserveCFG) : PreserveCFG(PreserveCFG) {}
+SROAPass::SROAPass(SROAOptions::PreserveCFGOption PreserveCFG)
+ : Options({PreserveCFG, SROAOptions::NoDecomposeStructs}) {}
+SROAPass::SROAPass(const SROAOptions &Options) : Options(Options) {}
namespace {
/// A legacy pass for the legacy pass manager that wraps the \c SROA pass.
class SROALegacyPass : public FunctionPass {
- SROAOptions PreserveCFG;
+ SROAOptions Options;
public:
static char ID;
- SROALegacyPass(SROAOptions PreserveCFG = SROAOptions::PreserveCFG)
- : FunctionPass(ID), PreserveCFG(PreserveCFG) {
+ SROALegacyPass(
+ const SROAOptions &Options =
+ {SROAOptions::PreserveCFGOption::PreserveCFG,
+ SROAOptions::DecomposeStructsOption::NoDecomposeStructs})
+ : FunctionPass(ID), Options(Options) {
initializeSROALegacyPassPass(*PassRegistry::getPassRegistry());
}
@@ -6083,7 +6091,7 @@ class SROALegacyPass : public FunctionPass {
getAnalysis<AssumptionCacheTracker>().getAssumptionCache(F);
DomTreeUpdater DTU(DT, DomTreeUpdater::UpdateStrategy::Lazy);
auto [Changed, _] =
- SROA(&F.getContext(), &DTU, &AC, PreserveCFG).runSROA(F);
+ SROA(&F.getContext(), &DTU, &AC, Options).runSROA(F);
return Changed;
}
@@ -6101,9 +6109,11 @@ class SROALegacyPass : public FunctionPass {
char SROALegacyPass::ID = 0;
-FunctionPass *llvm::createSROAPass(bool PreserveCFG) {
- return new SROALegacyPass(PreserveCFG ? SROAOptions::PreserveCFG
- : SROAOptions::ModifyCFG);
+FunctionPass *llvm::createSROAPass(bool PreserveCFG, bool DecomposeStructs) {
+ return new SROALegacyPass(
+ {PreserveCFG ? SROAOptions::PreserveCFG : SROAOptions::ModifyCFG,
+ DecomposeStructs ? SROAOptions::DecomposeStructs
+ : SROAOptions::NoDecomposeStructs});
}
INITIALIZE_PASS_BEGIN(SROALegacyPass, "sroa",
>From c73620dc42ade50fc91951dcb88412f1f3b29ede Mon Sep 17 00:00:00 2001
From: Deric Cheung <cheung.deric at gmail.com>
Date: Fri, 26 Sep 2025 10:31:09 -0700
Subject: [PATCH 2/5] Add shouldDecomposeStructAllocas to TargetTransformInfo
---
llvm/include/llvm/Analysis/TargetTransformInfo.h | 5 +++++
llvm/include/llvm/Analysis/TargetTransformInfoImpl.h | 2 ++
llvm/lib/Analysis/TargetTransformInfo.cpp | 4 ++++
llvm/lib/Target/DirectX/DirectXTargetTransformInfo.cpp | 4 ++++
llvm/lib/Target/DirectX/DirectXTargetTransformInfo.h | 1 +
5 files changed, 16 insertions(+)
diff --git a/llvm/include/llvm/Analysis/TargetTransformInfo.h b/llvm/include/llvm/Analysis/TargetTransformInfo.h
index 7a4abe9ee5082..599ad3afd008c 100644
--- a/llvm/include/llvm/Analysis/TargetTransformInfo.h
+++ b/llvm/include/llvm/Analysis/TargetTransformInfo.h
@@ -1977,6 +1977,11 @@ class TargetTransformInfo {
/// target.
LLVM_ABI bool allowVectorElementIndexingUsingGEP() const;
+ /// \returns True if the target does not support struct allocas and therefore
+ /// requires struct alloca instructions to be scalarized / decomposed into
+ /// its components.
+ LLVM_ABI bool shouldDecomposeStructAllocas() const;
+
private:
std::unique_ptr<const TargetTransformInfoImplBase> TTIImpl;
};
diff --git a/llvm/include/llvm/Analysis/TargetTransformInfoImpl.h b/llvm/include/llvm/Analysis/TargetTransformInfoImpl.h
index 566e1cf51631a..6b8fc753580ac 100644
--- a/llvm/include/llvm/Analysis/TargetTransformInfoImpl.h
+++ b/llvm/include/llvm/Analysis/TargetTransformInfoImpl.h
@@ -1163,6 +1163,8 @@ class TargetTransformInfoImplBase {
virtual bool allowVectorElementIndexingUsingGEP() const { return true; }
+ virtual bool shouldDecomposeStructAllocas() const { return false; }
+
protected:
// Obtain the minimum required size to hold the value (without the sign)
// In case of a vector it returns the min required size for one element.
diff --git a/llvm/lib/Analysis/TargetTransformInfo.cpp b/llvm/lib/Analysis/TargetTransformInfo.cpp
index bf62623099a97..dee1dd7b2a710 100644
--- a/llvm/lib/Analysis/TargetTransformInfo.cpp
+++ b/llvm/lib/Analysis/TargetTransformInfo.cpp
@@ -1506,6 +1506,10 @@ bool TargetTransformInfo::allowVectorElementIndexingUsingGEP() const {
return TTIImpl->allowVectorElementIndexingUsingGEP();
}
+bool TargetTransformInfo::shouldDecomposeStructAllocas() const {
+ return TTIImpl->shouldDecomposeStructAllocas();
+}
+
TargetTransformInfoImplBase::~TargetTransformInfoImplBase() = default;
TargetIRAnalysis::TargetIRAnalysis() : TTICallback(&getDefaultTTI) {}
diff --git a/llvm/lib/Target/DirectX/DirectXTargetTransformInfo.cpp b/llvm/lib/Target/DirectX/DirectXTargetTransformInfo.cpp
index 68fd3e0bc74c7..2235b8eaf9167 100644
--- a/llvm/lib/Target/DirectX/DirectXTargetTransformInfo.cpp
+++ b/llvm/lib/Target/DirectX/DirectXTargetTransformInfo.cpp
@@ -65,3 +65,7 @@ bool DirectXTTIImpl::isTargetIntrinsicTriviallyScalarizable(
return false;
}
}
+
+bool DirectXTTIImpl::shouldDecomposeStructAllocas() const {
+ return true;
+}
diff --git a/llvm/lib/Target/DirectX/DirectXTargetTransformInfo.h b/llvm/lib/Target/DirectX/DirectXTargetTransformInfo.h
index e2dd4354a8167..5a15d0a4f8510 100644
--- a/llvm/lib/Target/DirectX/DirectXTargetTransformInfo.h
+++ b/llvm/lib/Target/DirectX/DirectXTargetTransformInfo.h
@@ -39,6 +39,7 @@ class DirectXTTIImpl final : public BasicTTIImplBase<DirectXTTIImpl> {
unsigned ScalarOpdIdx) const override;
bool isTargetIntrinsicWithOverloadTypeAtArg(Intrinsic::ID ID,
int OpdIdx) const override;
+ bool shouldDecomposeStructAllocas() const override;
};
} // namespace llvm
>From 10b2d46487dea5512261cd6dd60d60a095cb8aaa Mon Sep 17 00:00:00 2001
From: Deric Cheung <cheung.deric at gmail.com>
Date: Fri, 26 Sep 2025 10:32:31 -0700
Subject: [PATCH 3/5] Add SROA pass to the DXIL backend with -O0
---
llvm/lib/Target/DirectX/DirectXTargetMachine.cpp | 5 +++++
llvm/lib/Target/DirectX/DirectXTargetTransformInfo.cpp | 4 +---
2 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/llvm/lib/Target/DirectX/DirectXTargetMachine.cpp b/llvm/lib/Target/DirectX/DirectXTargetMachine.cpp
index bcf84403b2c0d..29df12b24850e 100644
--- a/llvm/lib/Target/DirectX/DirectXTargetMachine.cpp
+++ b/llvm/lib/Target/DirectX/DirectXTargetMachine.cpp
@@ -48,6 +48,7 @@
#include "llvm/Target/TargetLoweringObjectFile.h"
#include "llvm/Transforms/IPO/GlobalDCE.h"
#include "llvm/Transforms/Scalar.h"
+#include "llvm/Transforms/Scalar/SROA.h"
#include "llvm/Transforms/Scalar/Scalarizer.h"
#include <optional>
@@ -107,6 +108,10 @@ class DirectXPassConfig : public TargetPassConfig {
FunctionPass *createTargetRegisterAllocator(bool) override { return nullptr; }
void addCodeGenPrepare() override {
+ // Clang does not apply SROA with -O0, but it is required for DXIL. So we
+ // add SROA here when -O0 is given.
+ if (getOptLevel() == CodeGenOptLevel::None)
+ addPass(createSROAPass(/*PreserveCFG=*/true, /*DecomposeStructs=*/true));
addPass(createDXILFinalizeLinkageLegacyPass());
addPass(createGlobalDCEPass());
addPass(createDXILResourceAccessLegacyPass());
diff --git a/llvm/lib/Target/DirectX/DirectXTargetTransformInfo.cpp b/llvm/lib/Target/DirectX/DirectXTargetTransformInfo.cpp
index 2235b8eaf9167..8193b5c40acc4 100644
--- a/llvm/lib/Target/DirectX/DirectXTargetTransformInfo.cpp
+++ b/llvm/lib/Target/DirectX/DirectXTargetTransformInfo.cpp
@@ -66,6 +66,4 @@ bool DirectXTTIImpl::isTargetIntrinsicTriviallyScalarizable(
}
}
-bool DirectXTTIImpl::shouldDecomposeStructAllocas() const {
- return true;
-}
+bool DirectXTTIImpl::shouldDecomposeStructAllocas() const { return true; }
>From cadb4de3485530b67fa7cc109d90e4918515de54 Mon Sep 17 00:00:00 2001
From: Deric Cheung <cheung.deric at gmail.com>
Date: Wed, 1 Oct 2025 13:34:39 -0700
Subject: [PATCH 4/5] Implement struct alloca decomposition
---
llvm/lib/Transforms/Scalar/SROA.cpp | 649 +++++++++++++++++-
.../Transforms/SROA/struct-decomposition.ll | 277 ++++++++
2 files changed, 920 insertions(+), 6 deletions(-)
create mode 100644 llvm/test/Transforms/SROA/struct-decomposition.ll
diff --git a/llvm/lib/Transforms/Scalar/SROA.cpp b/llvm/lib/Transforms/Scalar/SROA.cpp
index 7779d0a4be415..f62bbe23b0827 100644
--- a/llvm/lib/Transforms/Scalar/SROA.cpp
+++ b/llvm/lib/Transforms/Scalar/SROA.cpp
@@ -43,6 +43,7 @@
#include "llvm/Analysis/GlobalsModRef.h"
#include "llvm/Analysis/Loads.h"
#include "llvm/Analysis/PtrUseVisitor.h"
+#include "llvm/Analysis/TargetTransformInfo.h"
#include "llvm/Analysis/ValueTracking.h"
#include "llvm/Config/llvm-config.h"
#include "llvm/IR/BasicBlock.h"
@@ -56,6 +57,7 @@
#include "llvm/IR/DerivedTypes.h"
#include "llvm/IR/Dominators.h"
#include "llvm/IR/Function.h"
+#include "llvm/IR/GEPNoWrapFlags.h"
#include "llvm/IR/GlobalAlias.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/InstVisitor.h"
@@ -172,6 +174,7 @@ using RewriteableMemOps = SmallVector<RewriteableMemOp, 2>;
class SROA {
LLVMContext *const C;
DomTreeUpdater *const DTU;
+ TargetTransformInfo *const TTI;
AssumptionCache *const AC;
const bool PreserveCFG;
const bool DecomposeStructs;
@@ -236,9 +239,9 @@ class SROA {
isSafeSelectToSpeculate(SelectInst &SI, bool PreserveCFG);
public:
- SROA(LLVMContext *C, DomTreeUpdater *DTU, AssumptionCache *AC,
- const SROAOptions &Options)
- : C(C), DTU(DTU), AC(AC),
+ SROA(LLVMContext *C, DomTreeUpdater *DTU, TargetTransformInfo *TTI,
+ AssumptionCache *AC, const SROAOptions &Options)
+ : C(C), DTU(DTU), TTI(TTI), AC(AC),
PreserveCFG(Options.PCFGOption == SROAOptions::PreserveCFG),
DecomposeStructs(Options.DSOption == SROAOptions::DecomposeStructs) {}
@@ -248,6 +251,7 @@ class SROA {
private:
friend class AllocaSliceRewriter;
+ bool decomposeStructAlloca(AllocaInst &AI);
bool presplitLoadsAndStores(AllocaInst &AI, AllocaSlices &AS);
AllocaInst *rewritePartition(AllocaInst &AI, AllocaSlices &AS, Partition &P);
bool splitAlloca(AllocaInst &AI, AllocaSlices &AS);
@@ -4513,6 +4517,299 @@ class AggLoadStoreRewriter : public InstVisitor<AggLoadStoreRewriter, bool> {
} // end anonymous namespace
+/// Returns the pointee type of a given pointer value.
+///
+/// This function inspects the provided `Value *Ptr`, which must be a pointer
+/// type, and attempts to determine the type of the object it points to. It
+/// handles several common LLVM IR constructs:
+///
+/// - `AllocaInst`: Returns the allocated type.
+/// - `GlobalValue`: Returns the value type of the global.
+/// - `GetElementPtrInst`: Returns the result element type.
+/// - `Argument`: If marked with `byval` or `byref`, returns the corresponding
+/// parameter type.
+///
+/// \param Ptr a pointer-typed Value.
+/// \returns the pointee `Type *` if it can be determined, or `nullptr`
+/// otherwise.
+static Type *getPointeeType(Value *Ptr) {
+ assert(Ptr->getType()->isPointerTy());
+ Type *Ty = nullptr;
+ if (AllocaInst *Alloca = dyn_cast<AllocaInst>(Ptr))
+ Ty = Alloca->getAllocatedType();
+ else if (GlobalValue *GV = dyn_cast<GlobalValue>(Ptr))
+ Ty = GV->getValueType();
+ else if (GetElementPtrInst *GEP = dyn_cast<GetElementPtrInst>(Ptr))
+ Ty = GEP->getResultElementType();
+ else if (Argument *Arg = dyn_cast<Argument>(Ptr)) {
+ if (Arg->hasByValAttr())
+ Ty = Arg->getParamByValType();
+ else if (Arg->hasByRefAttr())
+ Ty = Arg->getParamByRefType();
+ }
+ return Ty;
+}
+
+namespace {
+
+/// A visitor that determines whether or not a struct-based alloca can be
+/// decomposed into separate allocas for each of its individual members.
+///
+/// The analysis walks through the uses of the alloca, validating each
+/// instruction to ensure it conforms to expected patterns (e.g., constant GEP
+/// indices, correct struct types). If any unsupported or ambiguous access is
+/// encountered, the visitor is aborted.
+///
+/// This visitor provides iteration support over valid accesses to be replaced,
+/// tracks dead users after struct alloca decomposition, and exposes the
+/// first instruction that caused an abort if the visit determines that the
+/// struct alloca can not be decomposed.
+class StructDecompositionAnalysis
+ : public InstVisitor<StructDecompositionAnalysis> {
+public:
+ StructDecompositionAnalysis(AllocaInst &AI) {
+ this->AI = &AI;
+
+ // Ensure the allocated type is a struct or (multi-dimensional) array of
+ // structs.
+ Type *Ty = AI.getAllocatedType();
+ while (isa<ArrayType>(Ty))
+ Ty = Ty->getArrayElementType();
+ StructTy = dyn_cast<StructType>(Ty);
+ if (!StructTy) {
+ AbortedInfo = &AI;
+ return;
+ }
+ const DataLayout &DL = AI.getDataLayout();
+ assert(DL.getTypeAllocSize(StructTy).isFixed() &&
+ "The struct must have a fixed size!");
+ StructSizeInBytes = DL.getTypeAllocSize(StructTy).getFixedValue();
+
+ enqueueUses(AI);
+
+ // Visit all the uses off the worklist until it is empty or we abort.
+ while (!Worklist.empty() && !isAborted()) {
+ Use *U = Worklist.pop_back_val();
+ Instruction *User = cast<Instruction>(U->getUser());
+ visit(User);
+ }
+ }
+
+ /// Support for iterating over the accesses to the struct alloca.
+ /// @{
+ using iterator = SmallVector<Instruction *>::iterator;
+ using range = iterator_range<iterator>;
+
+ iterator begin() { return Accesses.begin(); }
+ iterator end() { return Accesses.end(); }
+
+ using const_iterator = SmallVector<Instruction *>::const_iterator;
+ using const_range = iterator_range<const_iterator>;
+
+ const_iterator begin() const { return Accesses.begin(); }
+ const_iterator end() const { return Accesses.end(); }
+ /// @}
+
+ /// If there are instructions that are not handled by the struct decomposer,
+ /// then abort decomposing the struct.
+ bool isAborted() { return AbortedInfo != nullptr; }
+
+ /// Get the instruction causing the visit to abort.
+ /// \returns a pointer to the instruction causing the abort if one is
+ /// available; otherwise returns null.
+ Instruction *getAbortingInst() const { return AbortedInfo; }
+
+ /// Access the dead users for this alloca after struct decomposition.
+ ArrayRef<Instruction *> getDeadUsers() const { return DeadUsers; }
+
+#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
+ void print(raw_ostream &OS, const_iterator I, StringRef Indent = " ") const;
+ void print(raw_ostream &OS) const;
+ void dump(const_iterator I) const;
+ void dump() const;
+#endif
+
+private:
+ friend InstVisitor<StructDecompositionAnalysis>;
+
+ SmallVector<Instruction *> Accesses;
+
+ /// The AllocaInst being visited, its size, and its corresponding StructType.
+ AllocaInst *AI;
+ uint64_t StructSizeInBytes;
+ StructType *StructTy;
+
+ /// The worklist of to-visit uses.
+ SmallVector<Use *, 8> Worklist;
+
+ /// If the struct is invalid to be decomposed, this analysis will be aborted.
+ Instruction *AbortedInfo = nullptr;
+
+ /// Users of the Alloca which will be considered dead if the Alloca is
+ /// decomposed
+ SmallVector<Instruction *, 8> DeadUsers;
+
+ /// A set of visited uses to break cycles in unreachable code.
+ SmallPtrSet<Use *, 8> VisitedUses;
+
+ /// Set to de-duplicate dead instructions found in the use walk.
+ SmallPtrSet<Instruction *, 4> VisitedDeadInsts;
+
+ void enqueueUses(Value &I) {
+ for (Use &U : I.uses())
+ if (VisitedUses.insert(&U).second)
+ Worklist.push_back(&U);
+ }
+
+ void visitGetElementPtrInst(GetElementPtrInst &GEPI) {
+ // The GEPs visited must have a source element type of the struct or a
+ // (multi-dimensional) array of structs. Otherwise the intended access chain
+ // for the struct can be ambiguous.
+ unsigned StructMemberOperandIdx = 2;
+ Type *Ty = GEPI.getSourceElementType();
+ while (Ty->isArrayTy()) {
+ Ty = Ty->getArrayElementType();
+ StructMemberOperandIdx++;
+ }
+ if (Ty != StructTy) {
+ AbortedInfo = &GEPI;
+ return;
+ }
+
+ // If this GEP does not have the struct member index, then visit its uses.
+ if (GEPI.getNumOperands() < StructMemberOperandIdx + 1) {
+ markAsDead(GEPI);
+ enqueueUses(GEPI);
+ return;
+ }
+
+ // Ensure the struct member index is constant.
+ Value *StructMemberIdx = GEPI.getOperand(StructMemberOperandIdx);
+ if (!isa<ConstantInt>(StructMemberIdx)) {
+ AbortedInfo = &GEPI;
+ return;
+ }
+
+ Accesses.push_back(&GEPI);
+ }
+
+ void visitMemSetInst(MemSetInst &MSI) {
+ // Ensure the number of bytes set is a multiple of the struct size in
+ // bytes.
+ if (!MSI.getLengthInBytes()) {
+ AbortedInfo = &MSI;
+ return;
+ }
+ APInt Length = *MSI.getLengthInBytes();
+ if (Length.getZExtValue() % StructSizeInBytes != 0) {
+ AbortedInfo = &MSI;
+ return;
+ }
+
+ // Ensure we are setting the bytes of the correct type of struct.
+ Value *Dest = MSI.getDest();
+ if (AllocaInst *Alloca = dyn_cast<AllocaInst>(Dest))
+ assert(Alloca == AI &&
+ "It should be impossible to visit the allocas of other structs!");
+ else if (GetElementPtrInst *GEP = dyn_cast<GetElementPtrInst>(Dest)) {
+ [[maybe_unused]] Type *Ty = GEP->getResultElementType();
+ while (Ty->isArrayTy())
+ Ty = Ty->getArrayElementType();
+ assert(Ty == StructTy &&
+ "GEP must have a result element type of the expected struct or a "
+ "(multi-dimensional) array of it!");
+ } else {
+ AbortedInfo = &MSI;
+ return;
+ }
+ Accesses.push_back(&MSI);
+ }
+
+ void visitMemTransferInst(MemTransferInst &MTI) {
+ // Ensure the number of bytes transferred is a multiple of the struct size
+ // in bytes.
+ if (!MTI.getLengthInBytes()) {
+ AbortedInfo = &MTI;
+ return;
+ }
+ APInt Length = *MTI.getLengthInBytes();
+ if (Length.getZExtValue() % StructSizeInBytes != 0) {
+ AbortedInfo = &MTI;
+ return;
+ }
+
+ // Ensure we are transferring the bytes of the correct type of struct.
+ auto IsStructTy = [&](Type *Ty) -> bool {
+ while (Ty->isArrayTy())
+ Ty = Ty->getArrayElementType();
+ return Ty == StructTy;
+ };
+
+ Value *Dest = MTI.getRawDest();
+ Type *DestPtrTy = getPointeeType(Dest);
+ Value *Src = MTI.getRawSource();
+ Type *SrcPtrTy = getPointeeType(Src);
+ if (!DestPtrTy || !SrcPtrTy || DestPtrTy != SrcPtrTy ||
+ !IsStructTy(DestPtrTy)) {
+ AbortedInfo = &MTI;
+ return;
+ }
+
+ Accesses.push_back(&MTI);
+ }
+
+ void visitInstruction(Instruction &I) { AbortedInfo = &I; }
+
+ void visitIntrinsicInst(IntrinsicInst &II) {
+ switch (II.getIntrinsicID()) {
+ case Intrinsic::lifetime_start:
+ case Intrinsic::lifetime_end:
+ Accesses.push_back(&II);
+ break;
+ default:
+ AbortedInfo = &II;
+ }
+ }
+
+ void markAsDead(Instruction &I) {
+ if (VisitedDeadInsts.insert(&I).second)
+ DeadUsers.push_back(&I);
+ }
+};
+
+#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
+
+void StructDecompositionAnalysis::print(raw_ostream &OS, const_iterator I,
+ StringRef Indent) const {
+ OS << Indent << **I << "\n";
+}
+
+void StructDecompositionAnalysis::print(raw_ostream &OS) const {
+ if (AbortedInfo) {
+ OS << "Can't decompose struct alloca: " << *AI << "\n"
+ << " An access to this alloca is not supported:\n"
+ << " " << *AbortedInfo << "\n";
+ return;
+ }
+
+ OS << "Instructions to rewrite for this alloca: " << *AI << "\n";
+ for (const_iterator I = begin(), E = end(); I != E; ++I)
+ print(OS, I);
+}
+
+LLVM_DUMP_METHOD void
+StructDecompositionAnalysis::dump(const_iterator I) const {
+ print(dbgs(), I);
+}
+
+LLVM_DUMP_METHOD void StructDecompositionAnalysis::dump() const {
+ print(dbgs());
+}
+
+#endif // !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
+
+} // end anonymous namespace
+
/// Strip aggregate type wrapping.
///
/// This removes no-op aggregate types wrapping an underlying type. It will
@@ -4664,6 +4961,330 @@ static Type *getTypePartition(const DataLayout &DL, Type *Ty, uint64_t Offset,
return SubTy;
}
+/// Recursively rebuild a multi-dimensional array type, swapping the leaf
+/// element type from `OldLeaf` to `NewLeaf`. If `ArrTy` is not an
+/// `ArrayType` the function simply returns `ArrTy` unchanged.
+///
+/// - `ArrTy`: The original (possibly multi‑dimensional) array type
+/// - `NewLeaf`: The desired new leaf element type (must be a non‑array type)
+///
+/// /returns the new (multi-dimensional) array type using the new leaf element
+/// type.
+static Type *replaceArrayLeafType(Type *ArrTy, Type *NewLeaf) {
+ if (!ArrTy->isArrayTy())
+ return NewLeaf;
+
+ // Peel off the outermost ArrayType, recurse on its element type, then re‑wrap
+ // the same number of elements.
+ auto *OuterArr = cast<ArrayType>(ArrTy);
+ uint64_t NumElems = OuterArr->getNumElements();
+
+ // Recurse to get the transformed inner type.
+ Type *InnerTransformed =
+ replaceArrayLeafType(OuterArr->getElementType(), NewLeaf);
+
+ return ArrayType::get(InnerTransformed, NumElems);
+}
+
+/// Retrieves or constructs a pointer for a specific member of a decomposed
+/// struct.
+///
+/// This helper recursively constructs a `GetElementPtrInst` (GEP) used to
+/// access a given member (`MemberId`) of a struct pointed to by `Ptr`. It uses
+/// a cache (`PtrMap`) to avoid redundant GEP creation for the same
+/// pointer-member pair and to resolve the base case where `Ptr` is the
+/// original struct alloca instruction.
+///
+/// If `Ptr` is not an alloca instruction, the function assumes that `Ptr` is a
+/// GEP instruction and walks back through the pointer operand chain to build a
+/// new GEP with the updated source element type (`MemberTy`). The resulting GEP
+/// is inserted at the same location as the original `Ptr` and cached in
+/// `PtrMap`.
+///
+/// If `Ptr` is an alloca instruction, the function assumes `PtrMap` contains
+/// the corresponding alloca instruction for the specific member of the
+/// decomposed struct alloca.
+///
+/// \param Builder the IRBuilder used to insert new instructions.
+/// \param Ptr a pointer value expected to be a `GetElementPtrInst` or
+/// `AllocaInst`
+/// \param PtrMap a mapping from (Ptr, MemberId) pairs to previously computed
+/// member pointers.
+/// \param MemberTy the type of the struct member being accessed.
+/// \param MemberId the index of the member within the struct.
+/// \returns a `Value *` pointing to memory for the specified struct member.
+static Value *getPtrForStructMemberAccess(
+ IRBuilder<> &Builder, Value *Ptr,
+ SmallMapVector<std::pair<Value *, uint64_t>, Value *, 8> PtrMap,
+ Type *MemberTy, uint64_t MemberId) {
+
+ if (PtrMap.contains({Ptr, MemberId}))
+ return PtrMap[{Ptr, MemberId}];
+
+ BasicBlock::iterator InsertPoint = Builder.GetInsertPoint();
+
+ assert(isa<GetElementPtrInst>(Ptr) &&
+ "Expected pointer operand to be a GEP!");
+
+ GetElementPtrInst *PtrGEP = cast<GetElementPtrInst>(Ptr);
+ Type *NewSrcElemTy =
+ replaceArrayLeafType(PtrGEP->getSourceElementType(), MemberTy);
+ Value *NewPtr = getPtrForStructMemberAccess(
+ Builder, PtrGEP->getPointerOperand(), PtrMap, MemberTy, MemberId);
+ SmallVector<Value *> Indices(PtrGEP->idx_begin(), PtrGEP->idx_end());
+ std::string Name = PtrGEP->getName().str() + "." + std::to_string(MemberId);
+
+ Builder.SetInsertPoint(PtrGEP->getIterator());
+ Value *NewPtrGEP = Builder.CreateGEP(NewSrcElemTy, NewPtr, Indices, Name,
+ PtrGEP->getNoWrapFlags());
+
+ PtrMap.insert({{Ptr, MemberId}, NewPtrGEP});
+
+ Builder.SetInsertPoint(InsertPoint);
+
+ return NewPtrGEP;
+}
+
+/// Attempt to decompose a struct-based alloca into separate member allocas.
+///
+/// If applicable, the function replaces the original struct alloca with
+/// individual allocas for each member of the struct. It then rewrites all
+/// relevant instructions (e.g., `memset`, `memcpy`, `GEP`, lifetime intrinsics)
+/// to operate on the member allocas instead. Unsupported or ambiguous accesses
+/// will cause the decomposition to abort, and no changes to the module will be
+/// made.
+///
+/// \warning This transformation is unsafe in languages that allow dynamic
+/// indexing across struct members (e.g., treating a pointer to one member as a
+/// base for accessing others via computed offsets). Such behavior can violate
+/// the assumptions of this decomposition, which expects each member to be
+/// accessed independently and explicitly.
+///
+/// \param AI the `AllocaInst` representing the struct or (multi-dimensional)
+/// array of structs to decompose.
+/// \returns `true` if the struct alloca was successfully decomposed; `false`
+/// otherwise.
+bool SROA::decomposeStructAlloca(AllocaInst &AI) {
+ // Ensure this is an allocation of a struct or (multi-dimensional) array of
+ // structs.
+ Type *Ty = AI.getAllocatedType();
+ while (Ty->isArrayTy())
+ Ty = Ty->getArrayElementType();
+ if (!Ty->isStructTy())
+ return false;
+ StructType *StructTy = cast<StructType>(Ty);
+
+ const DataLayout &DL = AI.getDataLayout();
+ assert(DL.getTypeAllocSize(StructTy).isFixed() &&
+ "The size of the struct must be fixed!");
+ uint64_t StructSize = DL.getTypeAllocSize(StructTy).getFixedValue();
+
+ // Determine whether or not the (array of) struct(s) can be transformed.
+ LLVM_DEBUG(dbgs() << "Decomposing struct alloca: " << AI << "\n");
+ StructDecompositionAnalysis SDA(AI);
+ LLVM_DEBUG(SDA.print(dbgs()));
+
+ if (SDA.isAborted())
+ return false;
+
+ IRBuilder<> Builder(&AI);
+
+ // A map to keep track of allocas and GEPs created for each struct member.
+ SmallMapVector<std::pair<Value *, uint64_t>, Value *, 8> StructMemberPtrMap;
+
+ // Create allocas for each struct member using the same array dimensions.
+ for (uint64_t I = 0; I < StructTy->getNumElements(); ++I) {
+ std::string Name = AI.getName().str() + "." + std::to_string(I);
+ Type *MemberAllocaType = replaceArrayLeafType(
+ AI.getAllocatedType(), StructTy->getContainedType(I));
+ AllocaInst *MemberAlloca =
+ Builder.CreateAlloca(MemberAllocaType, nullptr, Name);
+ StructMemberPtrMap.insert({{&AI, I}, MemberAlloca});
+ }
+
+ // Update struct accesses to point to the member allocations.
+ for (Instruction *StructAccess : SDA) {
+ Builder.SetInsertPoint(StructAccess);
+
+ if (MemSetInst *MS = dyn_cast<MemSetInst>(StructAccess)) {
+ // A memset over a struct or array of structs will be replaced with M
+ // memsets, where M is the number of struct members.
+
+ Value *Dest = MS->getRawDest();
+ for (unsigned M = 0; M < StructTy->getNumContainedTypes(); ++M) {
+ Type *StructMemberTy = StructTy->getContainedType(M);
+
+ Value *NewDest = getPtrForStructMemberAccess(
+ Builder, Dest, StructMemberPtrMap, StructMemberTy, M);
+
+ assert(DL.getTypeAllocSize(StructMemberTy).isFixed() &&
+ "Struct member types must have a fixed size!");
+ uint64_t StructMemberSize =
+ DL.getTypeAllocSize(StructMemberTy).getFixedValue();
+ assert(MS->getLengthInBytes().has_value() &&
+ "The number of bytes to set must be known!");
+ uint64_t Length = MS->getLengthInBytes()->getZExtValue();
+ Value *NewLength =
+ Builder.getInt64(StructMemberSize * (Length / StructSize));
+
+ Builder.CreateMemSet(NewDest, MS->getValue(), NewLength, std::nullopt,
+ MS->isVolatile());
+ }
+
+ } else if (MemTransferInst *MT = dyn_cast<MemTransferInst>(StructAccess)) {
+ // A memory transfer instruction to copy a struct or (multi-dimensional)
+ // array of structs from one pointer to another is replaced with N * M
+ // memory transfer instructions, where N is the number of structs and M is
+ // the number of struct members.
+
+ Value *Dest = MT->getRawDest();
+ Value *Src = MT->getRawSource();
+
+ // This function returns true if Ptr points into the memory of the given
+ // alloca instruction Alloca using only GEPs.
+ auto IsPtrIntoAlloca = [](Value *Ptr, AllocaInst *Alloca) -> bool {
+ while (auto *GEP = dyn_cast<GetElementPtrInst>(Ptr))
+ Ptr = GEP->getPointerOperand();
+ return Ptr == Alloca;
+ };
+ bool DestIsFromAlloca = IsPtrIntoAlloca(Dest, &AI);
+ bool SrcIsFromAlloca = IsPtrIntoAlloca(Src, &AI);
+
+ for (unsigned M = 0; M < StructTy->getNumContainedTypes(); ++M) {
+ Type *StructMemberTy = StructTy->getContainedType(M);
+
+ Value *NewDestBasePtr = Dest;
+ if (DestIsFromAlloca)
+ NewDestBasePtr = getPtrForStructMemberAccess(
+ Builder, Dest, StructMemberPtrMap, StructMemberTy, M);
+
+ Value *NewSrcBasePtr = Src;
+ if (SrcIsFromAlloca)
+ NewSrcBasePtr = getPtrForStructMemberAccess(
+ Builder, Src, StructMemberPtrMap, StructMemberTy, M);
+
+ assert(DL.getTypeAllocSize(StructMemberTy).isFixed() &&
+ "Struct member types must have a fixed size!");
+ uint64_t StructMemberSize =
+ DL.getTypeAllocSize(StructMemberTy).getFixedValue();
+ assert(MT->getLengthInBytes().has_value() &&
+ "The number of bytes to transfer must be known!");
+ uint64_t Length = MT->getLengthInBytes()->getZExtValue();
+ uint64_t NumElems = Length / StructSize;
+ Value *NewLength = Builder.getInt64(StructMemberSize * NumElems);
+
+ assert(NumElems <= UINT32_MAX &&
+ "Number of elements to transfer must fit within a 32-bit "
+ "unsigned integer!");
+ for (uint32_t N = 0; N < NumElems; ++N) {
+ auto CreateGEPForIndexN = [&](Value *Ptr,
+ bool IsStructAccess) -> Value * {
+ Type *SourceElemTy = StructMemberTy;
+ SmallVector<Value *, 2> Indices = {Builder.getInt32(N)};
+ std::string Name = Ptr->getName().str() + "." + std::to_string(N);
+ if (IsStructAccess) {
+ SourceElemTy = StructTy;
+ Indices.push_back(Builder.getInt32(M));
+ Name += "." + std::to_string(M);
+ }
+ GEPNoWrapFlags NWF = GEPNoWrapFlags::inBounds();
+ if (GetElementPtrInst *GEP = dyn_cast<GetElementPtrInst>(Ptr))
+ NWF = GEP->getNoWrapFlags();
+ Value *V = Builder.CreateGEP(SourceElemTy, Ptr, Indices, Name, NWF);
+ return V;
+ };
+
+ Value *NewDest =
+ CreateGEPForIndexN(NewDestBasePtr, !DestIsFromAlloca);
+ Value *NewSrc = CreateGEPForIndexN(NewSrcBasePtr, !SrcIsFromAlloca);
+ Builder.CreateMemTransferInst(MT->getIntrinsicID(), NewDest,
+ std::nullopt, NewSrc, std::nullopt,
+ NewLength, MT->isVolatile());
+ }
+ }
+
+ } else if (GetElementPtrInst *GEP =
+ dyn_cast<GetElementPtrInst>(StructAccess)) {
+ // Struct- or (multi-dimensional-)array-of-struct-typed GEPs will be
+ // replaced with a similar GEP with its source element type modified to
+ // use the struct member type instead of the struct type. The indices of
+ // the new GEP will have the struct membere index removed from the GEP
+ // indices.
+
+ // Get the iterator to the index specifying the struct member to access.
+ unsigned StructMemberOperandIdx = 2;
+ Type *Ty = GEP->getSourceElementType();
+ while (Ty->isArrayTy()) {
+ Ty = Ty->getArrayElementType();
+ StructMemberOperandIdx++;
+ }
+ GetElementPtrInst::op_iterator StructMemberIdxIter =
+ GEP->idx_begin() + StructMemberOperandIdx - 1;
+ assert(isa<ConstantInt>(*StructMemberIdxIter) &&
+ "Index to struct member must be constant!");
+
+ uint64_t StructMemberId =
+ cast<ConstantInt>(*StructMemberIdxIter)->getZExtValue();
+ Type *StructMemberTy = StructTy->getContainedType(StructMemberId);
+
+ Type *NewSrcElemTy =
+ replaceArrayLeafType(GEP->getSourceElementType(), StructMemberTy);
+ Value *NewPtr = getPtrForStructMemberAccess(
+ Builder, GEP->getPointerOperand(), StructMemberPtrMap, StructMemberTy,
+ StructMemberId);
+
+ SmallVector<Value *> NewIndices(GEP->idx_begin(), StructMemberIdxIter);
+ NewIndices.insert(NewIndices.end(), std::next(StructMemberIdxIter),
+ GEP->idx_end());
+ std::string NewName =
+ GEP->getName().str() + "." + std::to_string(StructMemberId);
+
+ Value *NewGEP = Builder.CreateGEP(NewSrcElemTy, NewPtr, NewIndices,
+ NewName, GEP->getNoWrapFlags());
+ GEP->replaceAllUsesWith(NewGEP);
+
+ } else if (IntrinsicInst *II = dyn_cast<IntrinsicInst>(StructAccess)) {
+ assert(II->isLifetimeStartOrEnd() &&
+ "Expected intrinsic instruction to be a lifetime start or end!");
+ // Replace lifetime start and end intrinsic instructions with M lifetime
+ // start and end intrinsic instructions, where M is the number of struct
+ // members.
+
+ Value *Ptr = II->getArgOperand(0);
+ assert(Ptr == &AI && "Expected pointer operand of lifetime start or end "
+ "to be the struct alloca!");
+ for (unsigned M = 0; M < StructTy->getNumContainedTypes(); ++M) {
+ assert(StructMemberPtrMap.contains({Ptr, M}) &&
+ "The struct member pointer map must contain Ptr!");
+ Value *MemberAlloca = StructMemberPtrMap[{Ptr, M}];
+ assert(isa<AllocaInst>(MemberAlloca) &&
+ "The struct member pointer map must contain an alloca "
+ "instruction for this member!");
+ if (II->getIntrinsicID() == Intrinsic::lifetime_start)
+ Builder.CreateLifetimeStart(MemberAlloca);
+ else if (II->getIntrinsicID() == Intrinsic::lifetime_end)
+ Builder.CreateLifetimeEnd(MemberAlloca);
+ }
+ } else
+ llvm_unreachable(
+ "Invalid instruction encountered during struct decomposition!");
+
+ DeadInsts.push_back(StructAccess);
+ }
+
+ ArrayRef<Instruction *> DeadUsers = SDA.getDeadUsers();
+ for (Instruction *I : DeadUsers)
+ DeadInsts.push_back(I);
+ DeadInsts.push_back(&AI);
+
+ // Process the allocas of each struct member alloca in case there are further
+ // SROA or struct decomposition opportunities.
+ for (unsigned I = 0; I < StructTy->getNumElements(); ++I)
+ Worklist.insert(cast<AllocaInst>(StructMemberPtrMap[{&AI, I}]));
+
+ return true;
+}
+
/// Pre-split loads and stores to simplify rewriting.
///
/// We want to break up the splittable load+store pairs as much as
@@ -5856,6 +6477,17 @@ SROA::runOnAlloca(AllocaInst &AI) {
Size.getFixedValue() == 0)
return {Changed, CFGChanged};
+ // Decompose allocas for structs and (multi-dimensional) arrays of structs.
+ if (DecomposeStructs || TTI->shouldDecomposeStructAllocas()) {
+ Type *Ty = AT;
+ while (Ty->isArrayTy())
+ Ty = Ty->getArrayElementType();
+ if (Ty->isStructTy()) {
+ Changed = decomposeStructAlloca(AI);
+ return {Changed, CFGChanged};
+ }
+ }
+
// First, split any FCA loads and stores touching this alloca to promote
// better splitting and promotion opportunities.
IRBuilderTy IRB(&AI);
@@ -6040,9 +6672,10 @@ std::pair<bool /*Changed*/, bool /*CFGChanged*/> SROA::runSROA(Function &F) {
PreservedAnalyses SROAPass::run(Function &F, FunctionAnalysisManager &AM) {
DominatorTree &DT = AM.getResult<DominatorTreeAnalysis>(F);
AssumptionCache &AC = AM.getResult<AssumptionAnalysis>(F);
+ TargetTransformInfo &TTI = AM.getResult<TargetIRAnalysis>(F);
DomTreeUpdater DTU(DT, DomTreeUpdater::UpdateStrategy::Lazy);
auto [Changed, CFGChanged] =
- SROA(&F.getContext(), &DTU, &AC, Options).runSROA(F);
+ SROA(&F.getContext(), &DTU, &TTI, &AC, Options).runSROA(F);
if (!Changed)
return PreservedAnalyses::all();
PreservedAnalyses PA;
@@ -6083,7 +6716,10 @@ class SROALegacyPass : public FunctionPass {
}
bool runOnFunction(Function &F) override {
- if (skipFunction(F))
+ TargetTransformInfo &TTI =
+ getAnalysis<TargetTransformInfoWrapperPass>().getTTI(F);
+
+ if (skipFunction(F) && !TTI.shouldDecomposeStructAllocas())
return false;
DominatorTree &DT = getAnalysis<DominatorTreeWrapperPass>().getDomTree();
@@ -6091,13 +6727,14 @@ class SROALegacyPass : public FunctionPass {
getAnalysis<AssumptionCacheTracker>().getAssumptionCache(F);
DomTreeUpdater DTU(DT, DomTreeUpdater::UpdateStrategy::Lazy);
auto [Changed, _] =
- SROA(&F.getContext(), &DTU, &AC, Options).runSROA(F);
+ SROA(&F.getContext(), &DTU, &TTI, &AC, Options).runSROA(F);
return Changed;
}
void getAnalysisUsage(AnalysisUsage &AU) const override {
AU.addRequired<AssumptionCacheTracker>();
AU.addRequired<DominatorTreeWrapperPass>();
+ AU.addRequired<TargetTransformInfoWrapperPass>();
AU.addPreserved<GlobalsAAWrapperPass>();
AU.addPreserved<DominatorTreeWrapperPass>();
}
diff --git a/llvm/test/Transforms/SROA/struct-decomposition.ll b/llvm/test/Transforms/SROA/struct-decomposition.ll
new file mode 100644
index 0000000000000..df617fa4ca9cf
--- /dev/null
+++ b/llvm/test/Transforms/SROA/struct-decomposition.ll
@@ -0,0 +1,277 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -S -passes='sroa<preserve-cfg;decompose-structs>' %s | FileCheck %s --check-prefixes=CHECK,CHECK-PRESERVE-CFG
+; RUN: opt -S -passes='sroa<modify-cfg;decompose-structs>' %s | FileCheck %s --check-prefixes=CHECK,CHECK-MODIFY-CFG
+
+%struct.basic = type { i32, [2 x i32] }
+
+define void @basic(i32 %i, i32 %v) {
+; CHECK-LABEL: define void @basic(
+; CHECK-SAME: i32 [[I:%.*]], i32 [[V:%.*]]) {
+; CHECK-NEXT: [[ALLOCA_1:%.*]] = alloca [2 x i32], align 4
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[ALLOCA_1]])
+; CHECK-NEXT: [[ARR_1:%.*]] = getelementptr inbounds [2 x i32], ptr [[ALLOCA_1]], i32 0
+; CHECK-NEXT: [[ELEM:%.*]] = getelementptr inbounds [2 x i32], ptr [[ARR_1]], i32 0, i32 [[I]]
+; CHECK-NEXT: store i32 [[V]], ptr [[ELEM]], align 4
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[ALLOCA_1]])
+; CHECK-NEXT: ret void
+;
+ %alloca = alloca %struct.basic
+ call void @llvm.lifetime.start.p0(ptr nonnull %alloca)
+ %arr = getelementptr inbounds %struct.basic, ptr %alloca, i32 0, i32 1
+ %elem = getelementptr inbounds [2 x i32], ptr %arr, i32 0, i32 %i
+ store i32 %v, ptr %elem
+ call void @llvm.lifetime.end.p0(ptr nonnull %alloca)
+ ret void
+}
+
+%struct.nested = type { [3 x double], %struct.basic }
+
+define void @nested(i32 %i, i32 %v) {
+; CHECK-LABEL: define void @nested(
+; CHECK-SAME: i32 [[I:%.*]], i32 [[V:%.*]]) {
+; CHECK-NEXT: [[ALLOCA_1_1:%.*]] = alloca [2 x i32], align 4
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[ALLOCA_1_1]])
+; CHECK-NEXT: [[BASIC_1_1:%.*]] = getelementptr inbounds [2 x i32], ptr [[ALLOCA_1_1]], i32 0
+; CHECK-NEXT: [[ARRAY_1:%.*]] = getelementptr inbounds [2 x i32], ptr [[BASIC_1_1]], i32 0
+; CHECK-NEXT: [[ELEM:%.*]] = getelementptr inbounds [2 x i32], ptr [[ARRAY_1]], i32 0, i32 [[I]]
+; CHECK-NEXT: store i32 [[V]], ptr [[ELEM]], align 4
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[ALLOCA_1_1]])
+; CHECK-NEXT: ret void
+;
+ %alloca = alloca %struct.nested
+ call void @llvm.lifetime.start.p0(ptr nonnull %alloca)
+ %basic = getelementptr inbounds %struct.nested, ptr %alloca, i32 0, i32 1
+ %array = getelementptr inbounds %struct.basic, ptr %basic, i32 0, i32 1
+ %elem = getelementptr inbounds [2 x i32], ptr %array, i32 0, i32 %i
+ store i32 %v, ptr %elem
+ call void @llvm.lifetime.end.p0(ptr nonnull %alloca)
+ ret void
+}
+
+define void @array1d(i32 %s, i32 %i) {
+;
+; CHECK-LABEL: define void @array1d(
+; CHECK-SAME: i32 [[S:%.*]], i32 [[I:%.*]]) {
+; CHECK-NEXT: [[ALLOCA_1:%.*]] = alloca [10 x [2 x i32]], align 4
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[ALLOCA_1]])
+; CHECK-NEXT: [[STRUCT_1:%.*]] = getelementptr inbounds [10 x [2 x i32]], ptr [[ALLOCA_1]], i32 0, i32 [[S]]
+; CHECK-NEXT: [[ARRAY_1:%.*]] = getelementptr inbounds [2 x i32], ptr [[STRUCT_1]], i32 0
+; CHECK-NEXT: [[ELEM:%.*]] = getelementptr inbounds [2 x i32], ptr [[ARRAY_1]], i32 0, i32 [[I]]
+; CHECK-NEXT: [[LOAD:%.*]] = load i32, ptr [[ELEM]], align 4
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[ALLOCA_1]])
+; CHECK-NEXT: ret void
+;
+ %alloca = alloca [10 x %struct.basic]
+ call void @llvm.lifetime.start.p0(ptr nonnull %alloca)
+ %struct = getelementptr inbounds [10 x %struct.basic], ptr %alloca, i32 0, i32 %s
+ %array = getelementptr inbounds %struct.basic, ptr %struct, i32 0, i32 1
+ %elem = getelementptr inbounds [2 x i32], ptr %array, i32 0, i32 %i
+ %load = load i32, ptr %elem
+ call void @llvm.lifetime.end.p0(ptr nonnull %alloca)
+ ret void
+}
+
+define void @array1d_2(i32 %s, i32 %i) {
+;
+; CHECK-LABEL: define void @array1d_2(
+; CHECK-SAME: i32 [[S:%.*]], i32 [[I:%.*]]) {
+; CHECK-NEXT: [[ALLOCA_1:%.*]] = alloca [10 x [2 x i32]], align 4
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[ALLOCA_1]])
+; CHECK-NEXT: [[ARRAY_1:%.*]] = getelementptr inbounds [2 x i32], ptr [[ALLOCA_1]], i32 [[S]]
+; CHECK-NEXT: [[ELEM:%.*]] = getelementptr inbounds [2 x i32], ptr [[ARRAY_1]], i32 0, i32 [[I]]
+; CHECK-NEXT: [[LOAD:%.*]] = load i32, ptr [[ELEM]], align 4
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[ALLOCA_1]])
+; CHECK-NEXT: ret void
+;
+ %alloca = alloca [10 x %struct.basic]
+ call void @llvm.lifetime.start.p0(ptr nonnull %alloca)
+ %array = getelementptr inbounds %struct.basic, ptr %alloca, i32 %s, i32 1
+ %elem = getelementptr inbounds [2 x i32], ptr %array, i32 0, i32 %i
+ %load = load i32, ptr %elem
+ call void @llvm.lifetime.end.p0(ptr nonnull %alloca)
+ ret void
+}
+
+define void @array2d(i32 %si, i32 %sj, i32 %i) {
+;
+; CHECK-LABEL: define void @array2d(
+; CHECK-SAME: i32 [[SI:%.*]], i32 [[SJ:%.*]], i32 [[I:%.*]]) {
+; CHECK-NEXT: [[ALLOCA_1:%.*]] = alloca [3 x [2 x [2 x i32]]], align 4
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[ALLOCA_1]])
+; CHECK-NEXT: [[STRUCT_1:%.*]] = getelementptr inbounds [3 x [2 x [2 x i32]]], ptr [[ALLOCA_1]], i32 0, i32 [[SI]], i32 [[SJ]]
+; CHECK-NEXT: [[ARRAY_1:%.*]] = getelementptr inbounds [2 x i32], ptr [[STRUCT_1]], i32 0
+; CHECK-NEXT: [[ELEM:%.*]] = getelementptr inbounds [2 x i32], ptr [[ARRAY_1]], i32 0, i32 [[I]]
+; CHECK-NEXT: [[LOAD:%.*]] = load i32, ptr [[ELEM]], align 4
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[ALLOCA_1]])
+; CHECK-NEXT: ret void
+;
+ %alloca = alloca [3 x [2 x %struct.basic]]
+ call void @llvm.lifetime.start.p0(ptr nonnull %alloca)
+ %struct = getelementptr inbounds [3 x [2 x %struct.basic]], ptr %alloca, i32 0, i32 %si, i32 %sj
+ %array = getelementptr inbounds %struct.basic, ptr %struct, i32 0, i32 1
+ %elem = getelementptr inbounds [2 x i32], ptr %array, i32 0, i32 %i
+ %load = load i32, ptr %elem
+ call void @llvm.lifetime.end.p0(ptr nonnull %alloca)
+ ret void
+}
+
+define void @array2d_2(i32 %si, i32 %sj, i32 %i) {
+;
+; CHECK-LABEL: define void @array2d_2(
+; CHECK-SAME: i32 [[SI:%.*]], i32 [[SJ:%.*]], i32 [[I:%.*]]) {
+; CHECK-NEXT: [[ALLOCA_1:%.*]] = alloca [3 x [2 x [2 x i32]]], align 4
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[ALLOCA_1]])
+; CHECK-NEXT: [[ELEM_1:%.*]] = getelementptr inbounds [2 x [2 x i32]], ptr [[ALLOCA_1]], i32 [[SI]], i32 [[SJ]], i32 [[I]]
+; CHECK-NEXT: [[LOAD:%.*]] = load i32, ptr [[ELEM_1]], align 4
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[ALLOCA_1]])
+; CHECK-NEXT: ret void
+;
+ %alloca = alloca [3 x [2 x %struct.basic]]
+ call void @llvm.lifetime.start.p0(ptr nonnull %alloca)
+ %elem = getelementptr inbounds [2 x %struct.basic], ptr %alloca, i32 %si, i32 %sj, i32 1, i32 %i
+ %load = load i32, ptr %elem
+ call void @llvm.lifetime.end.p0(ptr nonnull %alloca)
+ ret void
+}
+
+%struct.S = type { [2 x %struct.X], [2 x %struct.Y] }
+%struct.X = type { i32, float }
+%struct.Y = type { i32, i32 }
+
+define void @nested_structs_arrays(i32 %i) {
+; CHECK-LABEL: define void @nested_structs_arrays(
+; CHECK-SAME: i32 [[I:%.*]]) {
+; CHECK-NEXT: [[S_0_1:%.*]] = alloca [10 x [2 x float]], align 4
+; CHECK-NEXT: [[S_1_1:%.*]] = alloca [10 x [2 x i32]], align 4
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[S_0_1]])
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[S_1_1]])
+; CHECK-NEXT: [[SI_0_1:%.*]] = getelementptr inbounds [2 x float], ptr [[S_0_1]], i32 [[I]]
+; CHECK-NEXT: [[SI_1_1:%.*]] = getelementptr inbounds [2 x i32], ptr [[S_1_1]], i32 [[I]]
+; CHECK-NEXT: [[X_0_1:%.*]] = getelementptr inbounds [2 x float], ptr [[SI_0_1]], i32 0
+; CHECK-NEXT: [[B_1:%.*]] = getelementptr inbounds float, ptr [[X_0_1]], i32 [[I]]
+; CHECK-NEXT: [[LB:%.*]] = load float, ptr [[B_1]], align 1
+; CHECK-NEXT: [[Y_1_1:%.*]] = getelementptr inbounds [2 x i32], ptr [[SI_1_1]], i32 0
+; CHECK-NEXT: [[D_1:%.*]] = getelementptr inbounds i32, ptr [[Y_1_1]], i32 [[I]]
+; CHECK-NEXT: [[LD:%.*]] = load i32, ptr [[D_1]], align 1
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[S_0_1]])
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[S_1_1]])
+; CHECK-NEXT: ret void
+;
+ %s = alloca [10 x %struct.S]
+ call void @llvm.lifetime.start.p0(ptr nonnull %s)
+ %si = getelementptr inbounds %struct.S, ptr %s, i32 %i
+ %x = getelementptr inbounds %struct.S, ptr %si, i32 0, i32 0
+ %b = getelementptr inbounds %struct.X, ptr %x, i32 %i, i32 1
+ %lb = load float, ptr %b, align 1
+ %y = getelementptr inbounds %struct.S, ptr %si, i32 0, i32 1
+ %d = getelementptr inbounds %struct.Y, ptr %y, i32 %i, i32 1
+ %ld = load i32, ptr %d, align 1
+ call void @llvm.lifetime.end.p0(ptr nonnull %s)
+ ret void
+}
+
+define void @memset_single_struct() {
+; CHECK-LABEL: define void @memset_single_struct() {
+; CHECK-NEXT: [[ALLOCA_0:%.*]] = alloca i32, align 4
+; CHECK-NEXT: [[ALLOCA_1:%.*]] = alloca [2 x i32], align 4
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[ALLOCA_0]])
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[ALLOCA_1]])
+; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr align 4 [[ALLOCA_0]], i8 0, i64 4, i1 false)
+; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr align 4 [[ALLOCA_1]], i8 0, i64 8, i1 false)
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[ALLOCA_0]])
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[ALLOCA_1]])
+; CHECK-NEXT: ret void
+;
+ %alloca = alloca %struct.basic
+ call void @llvm.lifetime.start.p0(ptr nonnull %alloca)
+ call void @llvm.memset.p0.i32(ptr %alloca, i8 0, i32 12, i1 false)
+ call void @llvm.lifetime.end.p0(ptr nonnull %alloca)
+ ret void
+}
+
+define void @memset_array_of_structs() {
+;
+; CHECK-LABEL: define void @memset_array_of_structs() {
+; CHECK-NEXT: [[ALLOCA_0:%.*]] = alloca [2 x [2 x i32]], align 4
+; CHECK-NEXT: [[ALLOCA_1:%.*]] = alloca [2 x [2 x [2 x i32]]], align 4
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[ALLOCA_0]])
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[ALLOCA_1]])
+; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr align 4 [[ALLOCA_0]], i8 0, i64 16, i1 false)
+; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr align 4 [[ALLOCA_1]], i8 0, i64 32, i1 false)
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[ALLOCA_0]])
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[ALLOCA_1]])
+; CHECK-NEXT: ret void
+;
+ %alloca = alloca [2 x [2 x %struct.basic]]
+ call void @llvm.lifetime.start.p0(ptr nonnull %alloca)
+ call void @llvm.memset.p0.i32(ptr %alloca, i8 0, i32 48, i1 false)
+ call void @llvm.lifetime.end.p0(ptr nonnull %alloca)
+ ret void
+}
+
+define void @memcpy_single_struct(ptr byval(%struct.basic) %arg) {
+;
+; CHECK-LABEL: define void @memcpy_single_struct(
+; CHECK-SAME: ptr byval([[STRUCT_BASIC:%.*]]) [[ARG:%.*]]) {
+; CHECK-NEXT: [[ALLOCA_1:%.*]] = alloca [2 x i32], align 4
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[ALLOCA_1]])
+; CHECK-NEXT: [[ARG_0_0:%.*]] = getelementptr inbounds [[STRUCT_BASIC]], ptr [[ARG]], i32 0, i32 0
+; CHECK-NEXT: [[ALLOCA_0_0_COPYLOAD:%.*]] = load i32, ptr [[ARG_0_0]], align 1
+; CHECK-NEXT: [[ALLOCA_1_0:%.*]] = getelementptr inbounds [2 x i32], ptr [[ALLOCA_1]], i32 0
+; CHECK-NEXT: [[ARG_0_1:%.*]] = getelementptr inbounds [[STRUCT_BASIC]], ptr [[ARG]], i32 0, i32 1
+; CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr [[ALLOCA_1_0]], ptr [[ARG_0_1]], i64 8, i1 false)
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[ALLOCA_1]])
+; CHECK-NEXT: ret void
+;
+ %alloca = alloca %struct.basic
+ call void @llvm.lifetime.start.p0(ptr nonnull %alloca)
+ call void @llvm.memcpy.p0.p0.i32(ptr %alloca, ptr %arg, i32 12, i1 false)
+ call void @llvm.lifetime.end.p0(ptr nonnull %alloca)
+ ret void
+}
+
+define void @memcpy_array_of_structs(ptr byval([2 x [2 x %struct.basic]]) %arg) {
+; CHECK-LABEL: define void @memcpy_array_of_structs(
+; CHECK-SAME: ptr byval([2 x [2 x %struct.basic]]) [[ARG:%.*]]) {
+; CHECK-NEXT: [[ALLOCA_0:%.*]] = alloca [2 x [2 x i32]], align 4
+; CHECK-NEXT: [[ALLOCA_1:%.*]] = alloca [2 x [2 x [2 x i32]]], align 4
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[ALLOCA_0]])
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[ALLOCA_1]])
+; CHECK-NEXT: [[ALLOCA_0_0:%.*]] = getelementptr inbounds i32, ptr [[ALLOCA_0]], i32 0
+; CHECK-NEXT: [[ARG_0_0:%.*]] = getelementptr inbounds [[STRUCT_BASIC:%.*]], ptr [[ARG]], i32 0, i32 0
+; CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr [[ALLOCA_0_0]], ptr [[ARG_0_0]], i64 16, i1 false)
+; CHECK-NEXT: [[ALLOCA_0_1:%.*]] = getelementptr inbounds i32, ptr [[ALLOCA_0]], i32 1
+; CHECK-NEXT: [[ARG_1_0:%.*]] = getelementptr inbounds [[STRUCT_BASIC]], ptr [[ARG]], i32 1, i32 0
+; CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr [[ALLOCA_0_1]], ptr [[ARG_1_0]], i64 16, i1 false)
+; CHECK-NEXT: [[ALLOCA_0_2:%.*]] = getelementptr inbounds i32, ptr [[ALLOCA_0]], i32 2
+; CHECK-NEXT: [[ARG_2_0:%.*]] = getelementptr inbounds [[STRUCT_BASIC]], ptr [[ARG]], i32 2, i32 0
+; CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr [[ALLOCA_0_2]], ptr [[ARG_2_0]], i64 16, i1 false)
+; CHECK-NEXT: [[ALLOCA_0_3:%.*]] = getelementptr inbounds i32, ptr [[ALLOCA_0]], i32 3
+; CHECK-NEXT: [[ARG_3_0:%.*]] = getelementptr inbounds [[STRUCT_BASIC]], ptr [[ARG]], i32 3, i32 0
+; CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr [[ALLOCA_0_3]], ptr [[ARG_3_0]], i64 16, i1 false)
+; CHECK-NEXT: [[ALLOCA_1_0:%.*]] = getelementptr inbounds [2 x i32], ptr [[ALLOCA_1]], i32 0
+; CHECK-NEXT: [[ARG_0_1:%.*]] = getelementptr inbounds [[STRUCT_BASIC]], ptr [[ARG]], i32 0, i32 1
+; CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr [[ALLOCA_1_0]], ptr [[ARG_0_1]], i64 32, i1 false)
+; CHECK-NEXT: [[ALLOCA_1_1:%.*]] = getelementptr inbounds [2 x i32], ptr [[ALLOCA_1]], i32 1
+; CHECK-NEXT: [[ARG_1_1:%.*]] = getelementptr inbounds [[STRUCT_BASIC]], ptr [[ARG]], i32 1, i32 1
+; CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr [[ALLOCA_1_1]], ptr [[ARG_1_1]], i64 32, i1 false)
+; CHECK-NEXT: [[ALLOCA_1_2:%.*]] = getelementptr inbounds [2 x i32], ptr [[ALLOCA_1]], i32 2
+; CHECK-NEXT: [[ARG_2_1:%.*]] = getelementptr inbounds [[STRUCT_BASIC]], ptr [[ARG]], i32 2, i32 1
+; CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr [[ALLOCA_1_2]], ptr [[ARG_2_1]], i64 32, i1 false)
+; CHECK-NEXT: [[ALLOCA_1_3:%.*]] = getelementptr inbounds [2 x i32], ptr [[ALLOCA_1]], i32 3
+; CHECK-NEXT: [[ARG_3_1:%.*]] = getelementptr inbounds [[STRUCT_BASIC]], ptr [[ARG]], i32 3, i32 1
+; CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr [[ALLOCA_1_3]], ptr [[ARG_3_1]], i64 32, i1 false)
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[ALLOCA_0]])
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[ALLOCA_1]])
+; CHECK-NEXT: ret void
+;
+ %alloca = alloca [2 x [2 x %struct.basic]]
+ call void @llvm.lifetime.start.p0(ptr nonnull %alloca)
+ call void @llvm.memcpy.p0.p0.i32(ptr %alloca, ptr %arg, i32 48, i1 false)
+ call void @llvm.lifetime.end.p0(ptr nonnull %alloca)
+ ret void
+}
+
+;; NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line:
+; CHECK-MODIFY-CFG: {{.*}}
+; CHECK-PRESERVE-CFG: {{.*}}
>From 9b3b3d9567326ce81881a6436163f9a8e54d62ad Mon Sep 17 00:00:00 2001
From: Deric Cheung <cheung.deric at gmail.com>
Date: Wed, 1 Oct 2025 16:13:39 -0700
Subject: [PATCH 5/5] Apply clang-format
---
llvm/lib/Transforms/Scalar/SROA.cpp | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)
diff --git a/llvm/lib/Transforms/Scalar/SROA.cpp b/llvm/lib/Transforms/Scalar/SROA.cpp
index f62bbe23b0827..1e21f90367e8c 100644
--- a/llvm/lib/Transforms/Scalar/SROA.cpp
+++ b/llvm/lib/Transforms/Scalar/SROA.cpp
@@ -6689,9 +6689,8 @@ void SROAPass::printPipeline(
raw_ostream &OS, function_ref<StringRef(StringRef)> MapClassName2PassName) {
static_cast<PassInfoMixin<SROAPass> *>(this)->printPipeline(
OS, MapClassName2PassName);
- OS << (Options.PCFGOption == SROAOptions::PreserveCFG
- ? "<preserve-cfg>"
- : "<modify-cfg>");
+ OS << (Options.PCFGOption == SROAOptions::PreserveCFG ? "<preserve-cfg>"
+ : "<modify-cfg>");
}
SROAPass::SROAPass(SROAOptions::PreserveCFGOption PreserveCFG)
@@ -6707,10 +6706,9 @@ class SROALegacyPass : public FunctionPass {
public:
static char ID;
- SROALegacyPass(
- const SROAOptions &Options =
- {SROAOptions::PreserveCFGOption::PreserveCFG,
- SROAOptions::DecomposeStructsOption::NoDecomposeStructs})
+ SROALegacyPass(const SROAOptions &Options =
+ {SROAOptions::PreserveCFGOption::PreserveCFG,
+ SROAOptions::DecomposeStructsOption::NoDecomposeStructs})
: FunctionPass(ID), Options(Options) {
initializeSROALegacyPassPass(*PassRegistry::getPassRegistry());
}
More information about the llvm-commits
mailing list