[llvm] [SROA] Canonicalize homogeneous structs into fixed vectors (PR #165159)
Yaxun Liu via llvm-commits
llvm-commits at lists.llvm.org
Fri Jan 16 08:14:01 PST 2026
https://github.com/yxsamliu updated https://github.com/llvm/llvm-project/pull/165159
>From 798817524a1bbfeb3479614c4d57ff55279761b2 Mon Sep 17 00:00:00 2001
From: "Yaxun (Sam) Liu" <yaxun.liu at amd.com>
Date: Tue, 13 Jan 2026 10:08:22 -0500
Subject: [PATCH 1/2] [SROA] Canonicalize homogeneous structs into fixed
vectors
Motivation: SROA would keep temporary allocas (e.g. copies and zero-inits) for homogeneous, 16-byte structs. On targets like AMDGPU these map to scratch memory and can severely hurt performance.
The following example could not eliminate the allocas before this change:
```
struct alignas(16) myint4 {
int x, y, z, w;
};
void foo(myint4* x, myint4 y, int cond) {
myint4 temp = y;
myint4 zero{0,0,0,0};
myint4 data = cond ? temp : zero;
*x = data;
}
```
Method: During partition type selection, when the slice type is a struct of 2 or 4 identical element types, and DataLayout proves it is tightly packed (no padding; element offsets are i*EltSize; StructSize == N*EltSize), and the element type is a valid fixed-size vector element, and the total size is at or below a configurable threshold, rewrite the slice type to a fixed vector <N x EltTy>.
Why it works: For tightly packed homogeneous structs, the in-memory representation is bitwise-identical to the corresponding fixed vector, so the transformation is semantics-preserving. The vector form enables SROA/InstCombine/GVN to replace memcpy/memset and conditional copies with vector selects and a single vector store, allowing the allocas to be eliminated.
Control: Adds TTI hook getMaxHomogeneousStructToVectorSize() to control the maximum struct size for this transformation. Default is 0 (disabled) for most targets, 16 for AMDGPU. Also adds pass parameter max-struct-to-vector=N for testing.
---
.../llvm/Analysis/TargetTransformInfo.h | 7 +
.../llvm/Analysis/TargetTransformInfoImpl.h | 2 +
llvm/include/llvm/Transforms/Scalar/SROA.h | 18 +-
llvm/lib/Analysis/TargetTransformInfo.cpp | 4 +
llvm/lib/Passes/PassBuilder.cpp | 40 +-
llvm/lib/Passes/PassBuilderPipelines.cpp | 22 +-
llvm/lib/Passes/PassRegistry.def | 4 +-
.../Target/AMDGPU/AMDGPUTargetTransformInfo.h | 6 +
llvm/lib/Transforms/Scalar/SROA.cpp | 113 ++++-
llvm/test/Transforms/SROA/struct-to-vector.ll | 400 ++++++++++++++++++
10 files changed, 572 insertions(+), 44 deletions(-)
create mode 100644 llvm/test/Transforms/SROA/struct-to-vector.ll
diff --git a/llvm/include/llvm/Analysis/TargetTransformInfo.h b/llvm/include/llvm/Analysis/TargetTransformInfo.h
index 8b06b4aae26ce..c6920f12b55c4 100644
--- a/llvm/include/llvm/Analysis/TargetTransformInfo.h
+++ b/llvm/include/llvm/Analysis/TargetTransformInfo.h
@@ -546,6 +546,13 @@ class TargetTransformInfo {
/// optimize away.
LLVM_ABI unsigned getFlatAddressSpace() const;
+ /// Return the maximum size in bytes of a homogeneous struct that SROA should
+ /// canonicalize to a vector type. This enables better optimization of
+ /// tightly-packed structs on targets where scratch memory is expensive.
+ ///
+ /// \returns 0 to disable the transformation, or the maximum struct size.
+ LLVM_ABI unsigned getMaxHomogeneousStructToVectorSize() const;
+
/// Return any intrinsic address operand indexes which may be rewritten if
/// they use a flat address space pointer.
///
diff --git a/llvm/include/llvm/Analysis/TargetTransformInfoImpl.h b/llvm/include/llvm/Analysis/TargetTransformInfoImpl.h
index 74857a5b83aba..512cc812b3a52 100644
--- a/llvm/include/llvm/Analysis/TargetTransformInfoImpl.h
+++ b/llvm/include/llvm/Analysis/TargetTransformInfoImpl.h
@@ -145,6 +145,8 @@ class TargetTransformInfoImplBase {
virtual unsigned getFlatAddressSpace() const { return -1; }
+ virtual unsigned getMaxHomogeneousStructToVectorSize() const { return 0; }
+
virtual bool collectFlatAddressOperands(SmallVectorImpl<int> &OpIndexes,
Intrinsic::ID IID) const {
return false;
diff --git a/llvm/include/llvm/Transforms/Scalar/SROA.h b/llvm/include/llvm/Transforms/Scalar/SROA.h
index 8bb65bf7225e0..dcbccd0bad1bd 100644
--- a/llvm/include/llvm/Transforms/Scalar/SROA.h
+++ b/llvm/include/llvm/Transforms/Scalar/SROA.h
@@ -16,20 +16,28 @@
#define LLVM_TRANSFORMS_SCALAR_SROA_H
#include "llvm/IR/PassManager.h"
+#include <optional>
namespace llvm {
class Function;
-enum class SROAOptions : bool { ModifyCFG, PreserveCFG };
+/// Options for the SROA pass pipeline configuration.
+struct SROAPassOptions {
+ /// Whether to preserve the CFG (no modifications allowed).
+ /// Default is false (modify-cfg) to match the original SROA behavior.
+ bool PreserveCFG = false;
+ /// Maximum size in bytes of a homogeneous struct to convert to a vector.
+ /// If nullopt, uses the target's default from TTI.
+ std::optional<unsigned> MaxStructToVectorSize = std::nullopt;
+};
class SROAPass : public PassInfoMixin<SROAPass> {
- const SROAOptions PreserveCFG;
+ SROAPassOptions 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);
+ /// Construct SROA pass with the given options.
+ SROAPass(SROAPassOptions Options = {}) : Options(Options) {}
/// Run the pass over the function.
PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
diff --git a/llvm/lib/Analysis/TargetTransformInfo.cpp b/llvm/lib/Analysis/TargetTransformInfo.cpp
index 2961d9361e5fa..00ef1abef7e28 100644
--- a/llvm/lib/Analysis/TargetTransformInfo.cpp
+++ b/llvm/lib/Analysis/TargetTransformInfo.cpp
@@ -318,6 +318,10 @@ unsigned TargetTransformInfo::getFlatAddressSpace() const {
return TTIImpl->getFlatAddressSpace();
}
+unsigned TargetTransformInfo::getMaxHomogeneousStructToVectorSize() const {
+ return TTIImpl->getMaxHomogeneousStructToVectorSize();
+}
+
bool TargetTransformInfo::collectFlatAddressOperands(
SmallVectorImpl<int> &OpIndexes, Intrinsic::ID IID) const {
return TTIImpl->collectFlatAddressOperands(OpIndexes, IID);
diff --git a/llvm/lib/Passes/PassBuilder.cpp b/llvm/lib/Passes/PassBuilder.cpp
index 8bb78c8c7df63..5cf0514975e32 100644
--- a/llvm/lib/Passes/PassBuilder.cpp
+++ b/llvm/lib/Passes/PassBuilder.cpp
@@ -1392,17 +1392,35 @@ Expected<ScalarizerPassOptions> parseScalarizerOptions(StringRef Params) {
return Result;
}
-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());
+Expected<SROAPassOptions> parseSROAOptions(StringRef Params) {
+ SROAPassOptions Result;
+ while (!Params.empty()) {
+ StringRef ParamName;
+ std::tie(ParamName, Params) = Params.split(';');
+
+ if (ParamName == "modify-cfg") {
+ Result.PreserveCFG = false;
+ } else if (ParamName == "preserve-cfg") {
+ Result.PreserveCFG = true;
+ } else if (ParamName.consume_front("max-struct-to-vector=")) {
+ unsigned Value;
+ if (ParamName.getAsInteger(0, Value))
+ return make_error<StringError>(
+ formatv("invalid SROA pass parameter '{}' (expected integer)",
+ ParamName)
+ .str(),
+ inconvertibleErrorCode());
+ Result.MaxStructToVectorSize = Value;
+ } else {
+ return make_error<StringError>(
+ formatv("invalid SROA pass parameter '{}' (valid params: "
+ "preserve-cfg, modify-cfg, max-struct-to-vector=N)",
+ ParamName)
+ .str(),
+ inconvertibleErrorCode());
+ }
+ }
+ return Result;
}
Expected<StackLifetime::LivenessType>
diff --git a/llvm/lib/Passes/PassBuilderPipelines.cpp b/llvm/lib/Passes/PassBuilderPipelines.cpp
index 1584d30875570..0cb16b67264e7 100644
--- a/llvm/lib/Passes/PassBuilderPipelines.cpp
+++ b/llvm/lib/Passes/PassBuilderPipelines.cpp
@@ -458,7 +458,7 @@ PassBuilder::buildO1FunctionSimplificationPipeline(OptimizationLevel Level,
// Form SSA out of local memory accesses after breaking apart aggregates into
// scalars.
- FPM.addPass(SROAPass(SROAOptions::ModifyCFG));
+ FPM.addPass(SROAPass(SROAPassOptions{/*PreserveCFG=*/false}));
// Catch trivial redundancies
FPM.addPass(EarlyCSEPass(true /* Enable mem-ssa. */));
@@ -546,7 +546,7 @@ PassBuilder::buildO1FunctionSimplificationPipeline(OptimizationLevel Level,
/*UseMemorySSA=*/false));
// Delete small array after loop unroll.
- FPM.addPass(SROAPass(SROAOptions::ModifyCFG));
+ FPM.addPass(SROAPass(SROAPassOptions{/*PreserveCFG=*/false}));
// Specially optimize memory movement as it doesn't look like dataflow in SSA.
FPM.addPass(MemCpyOptPass());
@@ -599,7 +599,7 @@ PassBuilder::buildFunctionSimplificationPipeline(OptimizationLevel Level,
// Form SSA out of local memory accesses after breaking apart aggregates into
// scalars.
- FPM.addPass(SROAPass(SROAOptions::ModifyCFG));
+ FPM.addPass(SROAPass(SROAPassOptions{/*PreserveCFG=*/false}));
// Catch trivial redundancies
FPM.addPass(EarlyCSEPass(true /* Enable mem-ssa. */));
@@ -736,7 +736,7 @@ PassBuilder::buildFunctionSimplificationPipeline(OptimizationLevel Level,
/*UseMemorySSA=*/false));
// Delete small array after loop unroll.
- FPM.addPass(SROAPass(SROAOptions::ModifyCFG));
+ FPM.addPass(SROAPass(SROAPassOptions{/*PreserveCFG=*/false}));
// Try vectorization/scalarization transforms that are both improvements
// themselves and can allow further folds with GVN and InstCombine.
@@ -829,7 +829,7 @@ void PassBuilder::addPreInlinerPasses(ModulePassManager &MPM,
CGSCCPassManager &CGPipeline = MIWP.getPM();
FunctionPassManager FPM;
- FPM.addPass(SROAPass(SROAOptions::ModifyCFG));
+ FPM.addPass(SROAPass(SROAPassOptions{/*PreserveCFG=*/false}));
FPM.addPass(EarlyCSEPass()); // Catch trivial redundancies.
FPM.addPass(SimplifyCFGPass(SimplifyCFGOptions().convertSwitchRangeToICmp(
true))); // Merge & remove basic blocks.
@@ -1138,7 +1138,7 @@ PassBuilder::buildModuleSimplificationPipeline(OptimizationLevel Level,
// SimplifyCFG.
EarlyFPM.addPass(LowerExpectIntrinsicPass());
EarlyFPM.addPass(SimplifyCFGPass());
- EarlyFPM.addPass(SROAPass(SROAOptions::ModifyCFG));
+ EarlyFPM.addPass(SROAPass(SROAPassOptions{/*PreserveCFG=*/false}));
EarlyFPM.addPass(EarlyCSEPass());
if (Level == OptimizationLevel::O3)
EarlyFPM.addPass(CallSiteSplittingPass());
@@ -1350,7 +1350,7 @@ void PassBuilder::addVectorPasses(OptimizationLevel Level,
// NOTE: we are very late in the pipeline, and we don't have any LICM
// or SimplifyCFG passes scheduled after us, that would cleanup
// the CFG mess this may created if allowed to modify CFG, so forbid that.
- FPM.addPass(SROAPass(SROAOptions::PreserveCFG));
+ FPM.addPass(SROAPass(SROAPassOptions{/*PreserveCFG=*/true}));
}
if (!IsFullLTO) {
@@ -1442,7 +1442,7 @@ void PassBuilder::addVectorPasses(OptimizationLevel Level,
// NOTE: we are very late in the pipeline, and we don't have any LICM
// or SimplifyCFG passes scheduled after us, that would cleanup
// the CFG mess this may created if allowed to modify CFG, so forbid that.
- FPM.addPass(SROAPass(SROAOptions::PreserveCFG));
+ FPM.addPass(SROAPass(SROAPassOptions{/*PreserveCFG=*/true}));
}
FPM.addPass(InferAlignmentPass());
@@ -2018,8 +2018,8 @@ PassBuilder::buildLTODefaultPipeline(OptimizationLevel Level,
CGSCCPassManager CGPM;
CGPM.addPass(PostOrderFunctionAttrsPass());
CGPM.addPass(ArgumentPromotionPass());
- CGPM.addPass(
- createCGSCCToFunctionPassAdaptor(SROAPass(SROAOptions::ModifyCFG)));
+ CGPM.addPass(createCGSCCToFunctionPassAdaptor(
+ SROAPass(SROAPassOptions{/*PreserveCFG=*/false})));
MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM)));
// Propagate constants at call sites into the functions they call. This
@@ -2170,7 +2170,7 @@ PassBuilder::buildLTODefaultPipeline(OptimizationLevel Level,
}
// Break up allocas
- FPM.addPass(SROAPass(SROAOptions::ModifyCFG));
+ FPM.addPass(SROAPass(SROAPassOptions{/*PreserveCFG=*/false}));
// LTO provides additional opportunities for tailcall elimination due to
// link-time inlining, and visibility of nocapture attribute.
diff --git a/llvm/lib/Passes/PassRegistry.def b/llvm/lib/Passes/PassRegistry.def
index 2cfb5b2592601..fc3c690cbd17e 100644
--- a/llvm/lib/Passes/PassRegistry.def
+++ b/llvm/lib/Passes/PassRegistry.def
@@ -708,8 +708,8 @@ FUNCTION_PASS_WITH_PARAMS(
parseSpeculativeExecutionPassOptions, "only-if-divergent-target")
FUNCTION_PASS_WITH_PARAMS(
"sroa", "SROAPass",
- [](SROAOptions PreserveCFG) { return SROAPass(PreserveCFG); },
- parseSROAOptions, "preserve-cfg;modify-cfg")
+ [](SROAPassOptions Options) { return SROAPass(Options); }, parseSROAOptions,
+ "preserve-cfg;modify-cfg;max-struct-to-vector=N")
FUNCTION_PASS_WITH_PARAMS(
"structurizecfg", "StructurizeCFG",
[](bool SkipUniformRegions) {
diff --git a/llvm/lib/Target/AMDGPU/AMDGPUTargetTransformInfo.h b/llvm/lib/Target/AMDGPU/AMDGPUTargetTransformInfo.h
index 4dcf381a9af93..3e49b103d5f00 100644
--- a/llvm/lib/Target/AMDGPU/AMDGPUTargetTransformInfo.h
+++ b/llvm/lib/Target/AMDGPU/AMDGPUTargetTransformInfo.h
@@ -204,6 +204,12 @@ class GCNTTIImpl final : public BasicTTIImplBase<GCNTTIImpl> {
return AMDGPUAS::FLAT_ADDRESS;
}
+ unsigned getMaxHomogeneousStructToVectorSize() const override {
+ // Enable canonicalization of 16-byte homogeneous structs to vectors.
+ // This helps eliminate scratch memory usage for small struct copies.
+ return 16;
+ }
+
bool collectFlatAddressOperands(SmallVectorImpl<int> &OpIndexes,
Intrinsic::ID IID) const override;
diff --git a/llvm/lib/Transforms/Scalar/SROA.cpp b/llvm/lib/Transforms/Scalar/SROA.cpp
index 43a6269c357dd..f5b1108025b52 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"
@@ -179,6 +180,7 @@ class SROA {
DomTreeUpdater *const DTU;
AssumptionCache *const AC;
const bool PreserveCFG;
+ const unsigned MaxStructToVectorSize;
/// Worklist of alloca instructions to simplify.
///
@@ -241,9 +243,9 @@ class SROA {
public:
SROA(LLVMContext *C, DomTreeUpdater *DTU, AssumptionCache *AC,
- SROAOptions PreserveCFG_)
- : C(C), DTU(DTU), AC(AC),
- PreserveCFG(PreserveCFG_ == SROAOptions::PreserveCFG) {}
+ bool PreserveCFG_, unsigned MaxStructToVectorSize_)
+ : C(C), DTU(DTU), AC(AC), PreserveCFG(PreserveCFG_),
+ MaxStructToVectorSize(MaxStructToVectorSize_) {}
/// Main run method used by both the SROAPass and by the legacy pass.
std::pair<bool /*Changed*/, bool /*CFGChanged*/> runSROA(Function &F);
@@ -5221,6 +5223,71 @@ bool SROA::presplitLoadsAndStores(AllocaInst &AI, AllocaSlices &AS) {
return true;
}
+/// Try to canonicalize a homogeneous, tightly-packed struct to a vector type.
+///
+/// For structs where all elements have the same type and are tightly packed
+/// (no padding), we can represent them as a fixed vector which enables better
+/// optimization (e.g., vector selects instead of memcpy).
+///
+/// \param STy The struct type to try to canonicalize.
+/// \param DL The DataLayout for size/alignment queries.
+/// \param MaxBytes Maximum struct size in bytes to consider (0 = disabled).
+/// \returns The equivalent vector type, or nullptr if not applicable.
+static FixedVectorType *tryCanonicalizeStructToVector(StructType *STy,
+ const DataLayout &DL,
+ unsigned MaxBytes) {
+ if (MaxBytes == 0)
+ return nullptr;
+
+ // Only handle 2 or 4 element structs (common cases like float2/float4).
+ unsigned NumElts = STy->getNumElements();
+ if (NumElts != 2 && NumElts != 4)
+ return nullptr;
+
+ // All elements must be the same type.
+ Type *EltTy = STy->getElementType(0);
+ for (unsigned I = 1; I < NumElts; ++I)
+ if (STy->getElementType(I) != EltTy)
+ return nullptr;
+
+ // Element type must be valid for vectors.
+ if (!VectorType::isValidElementType(EltTy))
+ return nullptr;
+
+ // Only allow integer types >= 8 bits or floating point.
+ if (auto *IT = dyn_cast<IntegerType>(EltTy)) {
+ if (IT->getBitWidth() < 8)
+ return nullptr;
+ } else if (!EltTy->isFloatingPointTy()) {
+ return nullptr;
+ }
+
+ // Element size must be fixed and non-zero.
+ TypeSize EltTS = DL.getTypeAllocSize(EltTy);
+ if (!EltTS.isFixed())
+ return nullptr;
+ uint64_t EltSize = EltTS.getFixedValue();
+ if (EltSize < 1)
+ return nullptr;
+
+ // Struct size must be within the threshold.
+ const StructLayout *SL = DL.getStructLayout(STy);
+ uint64_t StructSize = SL->getSizeInBytes();
+ if (StructSize == 0 || StructSize > MaxBytes)
+ return nullptr;
+
+ // Must be tightly packed: size == NumElts * EltSize.
+ if (StructSize != NumElts * EltSize)
+ return nullptr;
+
+ // Verify each element is at the expected offset (no padding).
+ for (unsigned I = 0; I < NumElts; ++I)
+ if (SL->getElementOffset(I) != I * EltSize)
+ return nullptr;
+
+ return FixedVectorType::get(EltTy, NumElts);
+}
+
/// Select a partition type for an alloca partition.
///
/// Try to compute a friendly type for this partition of the alloca. This
@@ -5234,7 +5301,7 @@ bool SROA::presplitLoadsAndStores(AllocaInst &AI, AllocaSlices &AS) {
/// nullptr.
static std::tuple<Type *, bool, VectorType *>
selectPartitionType(Partition &P, const DataLayout &DL, AllocaInst &AI,
- LLVMContext &C) {
+ LLVMContext &C, unsigned MaxStructToVectorSize) {
// First check if the partition is viable for vector promotion.
//
// We prefer vector promotion over integer widening promotion when:
@@ -5294,6 +5361,12 @@ selectPartitionType(Partition &P, const DataLayout &DL, AllocaInst &AI,
isIntegerWideningViable(P, LargestIntTy, DL))
return {LargestIntTy, true, nullptr};
+ // Try homogeneous struct to vector canonicalization.
+ if (auto *STy = dyn_cast<StructType>(TypePartitionTy))
+ if (auto *VTy =
+ tryCanonicalizeStructToVector(STy, DL, MaxStructToVectorSize))
+ return {VTy, false, nullptr};
+
// Fallback to TypePartitionTy and we probably won't promote.
return {TypePartitionTy, false, nullptr};
}
@@ -5326,7 +5399,7 @@ AllocaInst *SROA::rewritePartition(AllocaInst &AI, AllocaSlices &AS,
const DataLayout &DL = AI.getDataLayout();
// Select the type for the new alloca that spans the partition.
auto [PartitionTy, IsIntegerWideningViable, VecTy] =
- selectPartitionType(P, DL, AI, *C);
+ selectPartitionType(P, DL, AI, *C, MaxStructToVectorSize);
// Check for the case where we're going to rewrite to a new alloca of the
// exact same type as the original, and with the same access offsets. In that
@@ -6167,9 +6240,14 @@ 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);
+ // Use pass parameter if set, otherwise use TTI default.
+ unsigned StructToVectorSize = Options.MaxStructToVectorSize.value_or(
+ TTI.getMaxHomogeneousStructToVectorSize());
auto [Changed, CFGChanged] =
- SROA(&F.getContext(), &DTU, &AC, PreserveCFG).runSROA(F);
+ SROA(&F.getContext(), &DTU, &AC, Options.PreserveCFG, StructToVectorSize)
+ .runSROA(F);
if (!Changed)
return PreservedAnalyses::all();
PreservedAnalyses PA;
@@ -6183,22 +6261,23 @@ 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 << '<';
+ OS << (Options.PreserveCFG ? "preserve-cfg" : "modify-cfg");
+ if (Options.MaxStructToVectorSize)
+ OS << ";max-struct-to-vector=" << *Options.MaxStructToVectorSize;
+ OS << '>';
}
-SROAPass::SROAPass(SROAOptions PreserveCFG) : PreserveCFG(PreserveCFG) {}
-
namespace {
/// A legacy pass for the legacy pass manager that wraps the \c SROA pass.
class SROALegacyPass : public FunctionPass {
- SROAOptions PreserveCFG;
+ bool PreserveCFG;
public:
static char ID;
- SROALegacyPass(SROAOptions PreserveCFG = SROAOptions::PreserveCFG)
+ SROALegacyPass(bool PreserveCFG = true)
: FunctionPass(ID), PreserveCFG(PreserveCFG) {
initializeSROALegacyPassPass(*PassRegistry::getPassRegistry());
}
@@ -6210,15 +6289,20 @@ class SROALegacyPass : public FunctionPass {
DominatorTree &DT = getAnalysis<DominatorTreeWrapperPass>().getDomTree();
AssumptionCache &AC =
getAnalysis<AssumptionCacheTracker>().getAssumptionCache(F);
+ TargetTransformInfo &TTI =
+ getAnalysis<TargetTransformInfoWrapperPass>().getTTI(F);
DomTreeUpdater DTU(DT, DomTreeUpdater::UpdateStrategy::Lazy);
+ unsigned MaxStructToVectorSize = TTI.getMaxHomogeneousStructToVectorSize();
auto [Changed, _] =
- SROA(&F.getContext(), &DTU, &AC, PreserveCFG).runSROA(F);
+ SROA(&F.getContext(), &DTU, &AC, PreserveCFG, MaxStructToVectorSize)
+ .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>();
}
@@ -6231,8 +6315,7 @@ class SROALegacyPass : public FunctionPass {
char SROALegacyPass::ID = 0;
FunctionPass *llvm::createSROAPass(bool PreserveCFG) {
- return new SROALegacyPass(PreserveCFG ? SROAOptions::PreserveCFG
- : SROAOptions::ModifyCFG);
+ return new SROALegacyPass(PreserveCFG);
}
INITIALIZE_PASS_BEGIN(SROALegacyPass, "sroa",
diff --git a/llvm/test/Transforms/SROA/struct-to-vector.ll b/llvm/test/Transforms/SROA/struct-to-vector.ll
new file mode 100644
index 0000000000000..fb8d1b1c36a39
--- /dev/null
+++ b/llvm/test/Transforms/SROA/struct-to-vector.ll
@@ -0,0 +1,400 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -passes='sroa<max-struct-to-vector=16>,gvn,instcombine,simplifycfg' -S %s \
+; RUN: | FileCheck %s \
+; RUN: --check-prefixes=FLAT,NESTED,PADDED,NONHOMO,I1,PTR
+%struct.myint4 = type { i32, i32, i32, i32 }
+
+define dso_local void @foo_flat(ptr noundef %x, i64 %y.coerce0, i64 %y.coerce1, i32 noundef %cond) {
+; FLAT-LABEL: define dso_local void @foo_flat(
+; FLAT-SAME: ptr noundef [[X:%.*]], i64 [[Y_COERCE0:%.*]], i64 [[Y_COERCE1:%.*]], i32 noundef [[COND:%.*]]) {
+; FLAT-NEXT: [[ENTRY:.*:]]
+; FLAT-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq i32 [[COND]], 0
+; FLAT-NEXT: [[Y_SROA_0_0_VEC_INSERT:%.*]] = insertelement <2 x i64> poison, i64 [[Y_COERCE0]], i64 0
+; FLAT-NEXT: [[Y_SROA_0_8_VEC_INSERT:%.*]] = insertelement <2 x i64> [[Y_SROA_0_0_VEC_INSERT]], i64 [[Y_COERCE1]], i64 1
+; FLAT-NEXT: [[TMP0:%.*]] = bitcast <2 x i64> [[Y_SROA_0_8_VEC_INSERT]] to <4 x i32>
+; FLAT-NEXT: [[COND1_SROA_SPECULATED:%.*]] = select i1 [[TOBOOL_NOT]], <4 x i32> zeroinitializer, <4 x i32> [[TMP0]]
+; FLAT-NEXT: store <4 x i32> [[COND1_SROA_SPECULATED]], ptr [[X]], align 16
+; FLAT-NEXT: ret void
+;
+entry:
+ %y = alloca %struct.myint4, align 16
+ %x.addr = alloca ptr, align 8
+ %cond.addr = alloca i32, align 4
+ %temp = alloca %struct.myint4, align 16
+ %zero = alloca %struct.myint4, align 16
+ %data = alloca %struct.myint4, align 16
+ %0 = getelementptr inbounds nuw { i64, i64 }, ptr %y, i32 0, i32 0
+ store i64 %y.coerce0, ptr %0, align 16
+ %1 = getelementptr inbounds nuw { i64, i64 }, ptr %y, i32 0, i32 1
+ store i64 %y.coerce1, ptr %1, align 8
+ store ptr %x, ptr %x.addr, align 8
+ store i32 %cond, ptr %cond.addr, align 4
+ call void @llvm.lifetime.start.p0(ptr %temp)
+ call void @llvm.memcpy.p0.p0.i64(ptr align 16 %temp, ptr align 16 %y, i64 16, i1 false)
+ call void @llvm.lifetime.start.p0(ptr %zero)
+ call void @llvm.memset.p0.i64(ptr align 16 %zero, i8 0, i64 16, i1 false)
+ call void @llvm.lifetime.start.p0(ptr %data)
+ %2 = load i32, ptr %cond.addr, align 4
+ %tobool = icmp ne i32 %2, 0
+ br i1 %tobool, label %cond.true, label %cond.false
+
+cond.true:
+ br label %cond.end
+
+cond.false:
+ br label %cond.end
+
+cond.end:
+ %cond1 = phi ptr [ %temp, %cond.true ], [ %zero, %cond.false ]
+ call void @llvm.memcpy.p0.p0.i64(ptr align 16 %data, ptr align 16 %cond1, i64 16, i1 false)
+ %3 = load ptr, ptr %x.addr, align 8
+ call void @llvm.memcpy.p0.p0.i64(ptr align 16 %3, ptr align 16 %data, i64 16, i1 false)
+ call void @llvm.lifetime.end.p0(ptr %data)
+ call void @llvm.lifetime.end.p0(ptr %zero)
+ call void @llvm.lifetime.end.p0(ptr %temp)
+ ret void
+}
+%struct.myint4_base_n = type { i32, i32, i32, i32 }
+%struct.myint4_nested = type { %struct.myint4_base_n }
+
+define dso_local void @foo_nested(ptr noundef %x, i64 %y.coerce0, i64 %y.coerce1, i32 noundef %cond) {
+; FLAT-LABEL: define dso_local void @foo_nested(
+; FLAT-SAME: ptr noundef [[X:%.*]], i64 [[Y_COERCE0:%.*]], i64 [[Y_COERCE1:%.*]], i32 noundef [[COND:%.*]]) {
+; FLAT-NEXT: [[ENTRY:.*:]]
+; FLAT-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq i32 [[COND]], 0
+; FLAT-NEXT: [[Y_SROA_0_0_VEC_INSERT:%.*]] = insertelement <2 x i64> poison, i64 [[Y_COERCE0]], i64 0
+; FLAT-NEXT: [[Y_SROA_0_8_VEC_INSERT:%.*]] = insertelement <2 x i64> [[Y_SROA_0_0_VEC_INSERT]], i64 [[Y_COERCE1]], i64 1
+; FLAT-NEXT: [[TMP0:%.*]] = bitcast <2 x i64> [[Y_SROA_0_8_VEC_INSERT]] to <4 x i32>
+; FLAT-NEXT: [[COND1_SROA_SPECULATED:%.*]] = select i1 [[TOBOOL_NOT]], <4 x i32> zeroinitializer, <4 x i32> [[TMP0]]
+; FLAT-NEXT: store <4 x i32> [[COND1_SROA_SPECULATED]], ptr [[X]], align 16
+; FLAT-NEXT: ret void
+;
+entry:
+ %y = alloca %struct.myint4_nested, align 16
+ %x.addr = alloca ptr, align 8
+ %cond.addr = alloca i32, align 4
+ %temp = alloca %struct.myint4_nested, align 16
+ %zero = alloca %struct.myint4_nested, align 16
+ %data = alloca %struct.myint4_nested, align 16
+ %0 = getelementptr inbounds nuw { i64, i64 }, ptr %y, i32 0, i32 0
+ store i64 %y.coerce0, ptr %0, align 16
+ %1 = getelementptr inbounds nuw { i64, i64 }, ptr %y, i32 0, i32 1
+ store i64 %y.coerce1, ptr %1, align 8
+ store ptr %x, ptr %x.addr, align 8
+ store i32 %cond, ptr %cond.addr, align 4
+ call void @llvm.lifetime.start.p0(ptr %temp)
+ call void @llvm.memcpy.p0.p0.i64(ptr align 16 %temp, ptr align 16 %y, i64 16, i1 false)
+ call void @llvm.lifetime.start.p0(ptr %zero)
+ call void @llvm.memset.p0.i64(ptr align 16 %zero, i8 0, i64 16, i1 false)
+ call void @llvm.lifetime.start.p0(ptr %data)
+ %2 = load i32, ptr %cond.addr, align 4
+ %tobool = icmp ne i32 %2, 0
+ br i1 %tobool, label %cond.true, label %cond.false
+
+cond.true:
+ br label %cond.end
+
+cond.false:
+ br label %cond.end
+
+cond.end:
+ %cond1 = phi ptr [ %temp, %cond.true ], [ %zero, %cond.false ]
+ call void @llvm.memcpy.p0.p0.i64(ptr align 16 %data, ptr align 16 %cond1, i64 16, i1 false)
+ %3 = load ptr, ptr %x.addr, align 8
+ call void @llvm.memcpy.p0.p0.i64(ptr align 16 %3, ptr align 16 %data, i64 16, i1 false)
+ call void @llvm.lifetime.end.p0(ptr %data)
+ call void @llvm.lifetime.end.p0(ptr %zero)
+ call void @llvm.lifetime.end.p0(ptr %temp)
+ ret void
+}
+
+%struct.padded = type { i32, i8, i32, i8 }
+define dso_local void @foo_padded(ptr noundef %x, i32 %a0, i8 %a1,
+; FLAT-LABEL: define dso_local void @foo_padded(
+; FLAT-SAME: ptr noundef [[X:%.*]], i32 [[A0:%.*]], i8 [[A1:%.*]], i32 [[A2:%.*]], i8 [[A3:%.*]], i32 noundef [[COND:%.*]]) {
+; FLAT-NEXT: [[ENTRY:.*:]]
+; FLAT-NEXT: [[TEMP:%.*]] = alloca [[STRUCT_PADDED:%.*]], align 4
+; FLAT-NEXT: [[ZERO:%.*]] = alloca [[STRUCT_PADDED]], align 4
+; FLAT-NEXT: [[DATA:%.*]] = alloca [[STRUCT_PADDED]], align 4
+; FLAT-NEXT: call void @llvm.lifetime.start.p0(ptr nonnull [[TEMP]])
+; FLAT-NEXT: store i32 [[A0]], ptr [[TEMP]], align 4
+; FLAT-NEXT: [[Y_SROA_2_0_TEMP_SROA_IDX:%.*]] = getelementptr inbounds nuw i8, ptr [[TEMP]], i64 4
+; FLAT-NEXT: store i8 [[A1]], ptr [[Y_SROA_2_0_TEMP_SROA_IDX]], align 4
+; FLAT-NEXT: [[Y_SROA_31_0_TEMP_SROA_IDX:%.*]] = getelementptr inbounds nuw i8, ptr [[TEMP]], i64 8
+; FLAT-NEXT: store i32 [[A2]], ptr [[Y_SROA_31_0_TEMP_SROA_IDX]], align 4
+; FLAT-NEXT: [[Y_SROA_4_0_TEMP_SROA_IDX:%.*]] = getelementptr inbounds nuw i8, ptr [[TEMP]], i64 12
+; FLAT-NEXT: store i8 [[A3]], ptr [[Y_SROA_4_0_TEMP_SROA_IDX]], align 4
+; FLAT-NEXT: call void @llvm.lifetime.start.p0(ptr nonnull [[ZERO]])
+; FLAT-NEXT: call void @llvm.memset.p0.i64(ptr noundef nonnull align 4 dereferenceable(16) [[ZERO]], i8 0, i64 16, i1 false)
+; FLAT-NEXT: call void @llvm.lifetime.start.p0(ptr nonnull [[DATA]])
+; FLAT-NEXT: [[TOBOOL_PAD_NOT:%.*]] = icmp eq i32 [[COND]], 0
+; FLAT-NEXT: [[ZERO_TEMP:%.*]] = select i1 [[TOBOOL_PAD_NOT]], ptr [[ZERO]], ptr [[TEMP]]
+; FLAT-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr noundef nonnull align 4 dereferenceable(16) [[DATA]], ptr noundef nonnull align 4 dereferenceable(16) [[ZERO_TEMP]], i64 16, i1 false)
+; FLAT-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr noundef nonnull align 4 dereferenceable(16) [[X]], ptr noundef nonnull align 4 dereferenceable(16) [[DATA]], i64 16, i1 false)
+; FLAT-NEXT: call void @llvm.lifetime.end.p0(ptr nonnull [[DATA]])
+; FLAT-NEXT: call void @llvm.lifetime.end.p0(ptr nonnull [[ZERO]])
+; FLAT-NEXT: call void @llvm.lifetime.end.p0(ptr nonnull [[TEMP]])
+; FLAT-NEXT: ret void
+;
+ i32 %a2, i8 %a3,
+ i32 noundef %cond) {
+entry:
+ %y = alloca %struct.padded, align 4
+ %x.addr = alloca ptr, align 8
+ %cond.addr = alloca i32, align 4
+ %temp = alloca %struct.padded, align 4
+ %zero = alloca %struct.padded, align 4
+ %data = alloca %struct.padded, align 4
+ %y_i32_0 = getelementptr inbounds %struct.padded, ptr %y, i32 0, i32 0
+ store i32 %a0, ptr %y_i32_0, align 4
+ %y_i8_1 = getelementptr inbounds %struct.padded, ptr %y, i32 0, i32 1
+ store i8 %a1, ptr %y_i8_1, align 1
+ %y_i32_2 = getelementptr inbounds %struct.padded, ptr %y, i32 0, i32 2
+ store i32 %a2, ptr %y_i32_2, align 4
+ %y_i8_3 = getelementptr inbounds %struct.padded, ptr %y, i32 0, i32 3
+ store i8 %a3, ptr %y_i8_3, align 1
+ store ptr %x, ptr %x.addr, align 8
+ store i32 %cond, ptr %cond.addr, align 4
+ call void @llvm.lifetime.start.p0(ptr %temp)
+ call void @llvm.memcpy.p0.p0.i64(ptr align 4 %temp, ptr align 4 %y,
+ i64 16, i1 false)
+ call void @llvm.lifetime.start.p0(ptr %zero)
+ call void @llvm.memset.p0.i64(ptr align 4 %zero, i8 0, i64 16, i1 false)
+ call void @llvm.lifetime.start.p0(ptr %data)
+ %c.pad = load i32, ptr %cond.addr, align 4
+ %tobool.pad = icmp ne i32 %c.pad, 0
+ br i1 %tobool.pad, label %cond.true.pad, label %cond.false.pad
+
+cond.true.pad:
+ br label %cond.end.pad
+
+cond.false.pad:
+ br label %cond.end.pad
+
+cond.end.pad:
+ %cond1.pad = phi ptr [ %temp, %cond.true.pad ], [ %zero, %cond.false.pad ]
+ call void @llvm.memcpy.p0.p0.i64(ptr align 4 %data, ptr align 4 %cond1.pad,
+ i64 16, i1 false)
+ %xv.pad = load ptr, ptr %x.addr, align 8
+ call void @llvm.memcpy.p0.p0.i64(ptr align 4 %xv.pad, ptr align 4 %data,
+ i64 16, i1 false)
+ call void @llvm.lifetime.end.p0(ptr %data)
+ call void @llvm.lifetime.end.p0(ptr %zero)
+ call void @llvm.lifetime.end.p0(ptr %temp)
+ ret void
+}
+
+%struct.nonhomo = type { i32, i64, i32, i64 }
+define dso_local void @foo_nonhomo(ptr noundef %x, i32 %a0, i64 %a1,
+; FLAT-LABEL: define dso_local void @foo_nonhomo(
+; FLAT-SAME: ptr noundef [[X:%.*]], i32 [[A0:%.*]], i64 [[A1:%.*]], i32 [[A2:%.*]], i64 [[A3:%.*]], i32 noundef [[COND:%.*]]) {
+; FLAT-NEXT: [[ENTRY:.*:]]
+; FLAT-NEXT: [[TEMP:%.*]] = alloca [[STRUCT_NONHOMO:%.*]], align 8
+; FLAT-NEXT: [[ZERO:%.*]] = alloca [[STRUCT_NONHOMO]], align 8
+; FLAT-NEXT: [[DATA:%.*]] = alloca [[STRUCT_NONHOMO]], align 8
+; FLAT-NEXT: call void @llvm.lifetime.start.p0(ptr nonnull [[TEMP]])
+; FLAT-NEXT: store i32 [[A0]], ptr [[TEMP]], align 8
+; FLAT-NEXT: [[Y_SROA_2_0_TEMP_SROA_IDX:%.*]] = getelementptr inbounds nuw i8, ptr [[TEMP]], i64 4
+; FLAT-NEXT: store i64 [[A1]], ptr [[Y_SROA_2_0_TEMP_SROA_IDX]], align 4
+; FLAT-NEXT: [[Y_SROA_3_0_TEMP_SROA_IDX:%.*]] = getelementptr inbounds nuw i8, ptr [[TEMP]], i64 12
+; FLAT-NEXT: store i32 [[A2]], ptr [[Y_SROA_3_0_TEMP_SROA_IDX]], align 4
+; FLAT-NEXT: [[Y_SROA_4_0_TEMP_SROA_IDX:%.*]] = getelementptr inbounds nuw i8, ptr [[TEMP]], i64 16
+; FLAT-NEXT: store i64 [[A3]], ptr [[Y_SROA_4_0_TEMP_SROA_IDX]], align 8
+; FLAT-NEXT: call void @llvm.lifetime.start.p0(ptr nonnull [[ZERO]])
+; FLAT-NEXT: call void @llvm.memset.p0.i64(ptr noundef nonnull align 8 dereferenceable(32) [[ZERO]], i8 0, i64 32, i1 false)
+; FLAT-NEXT: call void @llvm.lifetime.start.p0(ptr nonnull [[DATA]])
+; FLAT-NEXT: [[TOBOOL_NH_NOT:%.*]] = icmp eq i32 [[COND]], 0
+; FLAT-NEXT: [[ZERO_TEMP:%.*]] = select i1 [[TOBOOL_NH_NOT]], ptr [[ZERO]], ptr [[TEMP]]
+; FLAT-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr noundef nonnull align 8 dereferenceable(32) [[DATA]], ptr noundef nonnull align 8 dereferenceable(32) [[ZERO_TEMP]], i64 32, i1 false)
+; FLAT-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr noundef nonnull align 8 dereferenceable(32) [[X]], ptr noundef nonnull align 8 dereferenceable(32) [[DATA]], i64 32, i1 false)
+; FLAT-NEXT: call void @llvm.lifetime.end.p0(ptr nonnull [[DATA]])
+; FLAT-NEXT: call void @llvm.lifetime.end.p0(ptr nonnull [[ZERO]])
+; FLAT-NEXT: call void @llvm.lifetime.end.p0(ptr nonnull [[TEMP]])
+; FLAT-NEXT: ret void
+;
+ i32 %a2, i64 %a3,
+ i32 noundef %cond) {
+entry:
+ %y = alloca %struct.nonhomo, align 8
+ %x.addr = alloca ptr, align 8
+ %cond.addr = alloca i32, align 4
+ %temp = alloca %struct.nonhomo, align 8
+ %zero = alloca %struct.nonhomo, align 8
+ %data = alloca %struct.nonhomo, align 8
+ %y_i32_0n = getelementptr inbounds %struct.nonhomo, ptr %y, i32 0, i32 0
+ store i32 %a0, ptr %y_i32_0n, align 4
+ %y_i64_1n = getelementptr inbounds %struct.nonhomo, ptr %y, i32 0, i32 1
+ store i64 %a1, ptr %y_i64_1n, align 8
+ %y_i32_2n = getelementptr inbounds %struct.nonhomo, ptr %y, i32 0, i32 2
+ store i32 %a2, ptr %y_i32_2n, align 4
+ %y_i64_3n = getelementptr inbounds %struct.nonhomo, ptr %y, i32 0, i32 3
+ store i64 %a3, ptr %y_i64_3n, align 8
+ store ptr %x, ptr %x.addr, align 8
+ store i32 %cond, ptr %cond.addr, align 4
+ call void @llvm.lifetime.start.p0(ptr %temp)
+ call void @llvm.memcpy.p0.p0.i64(ptr align 8 %temp, ptr align 8 %y,
+ i64 32, i1 false)
+ call void @llvm.lifetime.start.p0(ptr %zero)
+ call void @llvm.memset.p0.i64(ptr align 8 %zero, i8 0, i64 32, i1 false)
+ call void @llvm.lifetime.start.p0(ptr %data)
+ %c.nh = load i32, ptr %cond.addr, align 4
+ %tobool.nh = icmp ne i32 %c.nh, 0
+ br i1 %tobool.nh, label %cond.true.nh, label %cond.false.nh
+
+cond.true.nh:
+ br label %cond.end.nh
+
+cond.false.nh:
+ br label %cond.end.nh
+
+cond.end.nh:
+ %cond1.nh = phi ptr [ %temp, %cond.true.nh ], [ %zero, %cond.false.nh ]
+ call void @llvm.memcpy.p0.p0.i64(ptr align 8 %data, ptr align 8 %cond1.nh,
+ i64 32, i1 false)
+ %xv.nh = load ptr, ptr %x.addr, align 8
+ call void @llvm.memcpy.p0.p0.i64(ptr align 8 %xv.nh, ptr align 8 %data,
+ i64 32, i1 false)
+ call void @llvm.lifetime.end.p0(ptr %data)
+ call void @llvm.lifetime.end.p0(ptr %zero)
+ call void @llvm.lifetime.end.p0(ptr %temp)
+ ret void
+}
+
+%struct.i1x4 = type { i1, i1, i1, i1 }
+define dso_local void @foo_i1(ptr noundef %x, i64 %dummy0, i64 %dummy1,
+; FLAT-LABEL: define dso_local void @foo_i1(
+; FLAT-SAME: ptr noundef [[X:%.*]], i64 [[DUMMY0:%.*]], i64 [[DUMMY1:%.*]], i32 noundef [[COND:%.*]]) {
+; FLAT-NEXT: [[ENTRY:.*:]]
+; FLAT-NEXT: [[TEMP:%.*]] = alloca [[STRUCT_I1X4:%.*]], align 1
+; FLAT-NEXT: [[ZERO:%.*]] = alloca [[STRUCT_I1X4]], align 1
+; FLAT-NEXT: call void @llvm.lifetime.start.p0(ptr nonnull [[TEMP]])
+; FLAT-NEXT: call void @llvm.lifetime.start.p0(ptr nonnull [[ZERO]])
+; FLAT-NEXT: store i32 0, ptr [[ZERO]], align 1
+; FLAT-NEXT: [[TOBOOL_I1_NOT:%.*]] = icmp eq i32 [[COND]], 0
+; FLAT-NEXT: [[ZERO_TEMP:%.*]] = select i1 [[TOBOOL_I1_NOT]], ptr [[ZERO]], ptr [[TEMP]]
+; FLAT-NEXT: [[TMP0:%.*]] = load i32, ptr [[ZERO_TEMP]], align 1
+; FLAT-NEXT: store i32 [[TMP0]], ptr [[X]], align 1
+; FLAT-NEXT: call void @llvm.lifetime.end.p0(ptr nonnull [[ZERO]])
+; FLAT-NEXT: call void @llvm.lifetime.end.p0(ptr nonnull [[TEMP]])
+; FLAT-NEXT: ret void
+;
+ i32 noundef %cond) {
+entry:
+ %y = alloca %struct.i1x4, align 1
+ %x.addr = alloca ptr, align 8
+ %cond.addr = alloca i32, align 4
+ %temp = alloca %struct.i1x4, align 1
+ %zero = alloca %struct.i1x4, align 1
+ %data = alloca %struct.i1x4, align 1
+ store ptr %x, ptr %x.addr, align 8
+ store i32 %cond, ptr %cond.addr, align 4
+ call void @llvm.lifetime.start.p0(ptr %temp)
+ call void @llvm.memcpy.p0.p0.i64(ptr align 1 %temp, ptr align 1 %y,
+ i64 4, i1 false)
+ call void @llvm.lifetime.start.p0(ptr %zero)
+ call void @llvm.memset.p0.i64(ptr align 1 %zero, i8 0, i64 4, i1 false)
+ call void @llvm.lifetime.start.p0(ptr %data)
+ %c.i1 = load i32, ptr %cond.addr, align 4
+ %tobool.i1 = icmp ne i32 %c.i1, 0
+ br i1 %tobool.i1, label %cond.true.i1, label %cond.false.i1
+
+cond.true.i1:
+ br label %cond.end.i1
+
+cond.false.i1:
+ br label %cond.end.i1
+
+cond.end.i1:
+ %cond1.i1 = phi ptr [ %temp, %cond.true.i1 ], [ %zero, %cond.false.i1 ]
+ call void @llvm.memcpy.p0.p0.i64(ptr align 1 %data, ptr align 1 %cond1.i1,
+ i64 4, i1 false)
+ %xv.i1 = load ptr, ptr %x.addr, align 8
+ call void @llvm.memcpy.p0.p0.i64(ptr align 1 %xv.i1, ptr align 1 %data,
+ i64 4, i1 false)
+ call void @llvm.lifetime.end.p0(ptr %data)
+ call void @llvm.lifetime.end.p0(ptr %zero)
+ call void @llvm.lifetime.end.p0(ptr %temp)
+ ret void
+}
+
+%struct.ptr4 = type { ptr, ptr, ptr, ptr }
+define dso_local void @foo_ptr(ptr noundef %x, ptr %p0, ptr %p1,
+; FLAT-LABEL: define dso_local void @foo_ptr(
+; FLAT-SAME: ptr noundef [[X:%.*]], ptr [[P0:%.*]], ptr [[P1:%.*]], ptr [[P2:%.*]], ptr [[P3:%.*]], i32 noundef [[COND:%.*]]) {
+; FLAT-NEXT: [[ENTRY:.*:]]
+; FLAT-NEXT: [[TEMP:%.*]] = alloca [[STRUCT_PTR4:%.*]], align 8
+; FLAT-NEXT: [[ZERO:%.*]] = alloca [[STRUCT_PTR4]], align 8
+; FLAT-NEXT: [[DATA:%.*]] = alloca [[STRUCT_PTR4]], align 8
+; FLAT-NEXT: call void @llvm.lifetime.start.p0(ptr nonnull [[TEMP]])
+; FLAT-NEXT: store ptr [[P0]], ptr [[TEMP]], align 8
+; FLAT-NEXT: [[Y_SROA_2_0_TEMP_SROA_IDX:%.*]] = getelementptr inbounds nuw i8, ptr [[TEMP]], i64 8
+; FLAT-NEXT: store ptr [[P1]], ptr [[Y_SROA_2_0_TEMP_SROA_IDX]], align 8
+; FLAT-NEXT: [[Y_SROA_3_0_TEMP_SROA_IDX:%.*]] = getelementptr inbounds nuw i8, ptr [[TEMP]], i64 16
+; FLAT-NEXT: store ptr [[P2]], ptr [[Y_SROA_3_0_TEMP_SROA_IDX]], align 8
+; FLAT-NEXT: [[Y_SROA_4_0_TEMP_SROA_IDX:%.*]] = getelementptr inbounds nuw i8, ptr [[TEMP]], i64 24
+; FLAT-NEXT: store ptr [[P3]], ptr [[Y_SROA_4_0_TEMP_SROA_IDX]], align 8
+; FLAT-NEXT: call void @llvm.lifetime.start.p0(ptr nonnull [[ZERO]])
+; FLAT-NEXT: call void @llvm.memset.p0.i64(ptr noundef nonnull align 8 dereferenceable(32) [[ZERO]], i8 0, i64 32, i1 false)
+; FLAT-NEXT: call void @llvm.lifetime.start.p0(ptr nonnull [[DATA]])
+; FLAT-NEXT: [[TOBOOL_PTR_NOT:%.*]] = icmp eq i32 [[COND]], 0
+; FLAT-NEXT: [[ZERO_TEMP:%.*]] = select i1 [[TOBOOL_PTR_NOT]], ptr [[ZERO]], ptr [[TEMP]]
+; FLAT-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr noundef nonnull align 8 dereferenceable(32) [[DATA]], ptr noundef nonnull align 8 dereferenceable(32) [[ZERO_TEMP]], i64 32, i1 false)
+; FLAT-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr noundef nonnull align 8 dereferenceable(32) [[X]], ptr noundef nonnull align 8 dereferenceable(32) [[DATA]], i64 32, i1 false)
+; FLAT-NEXT: call void @llvm.lifetime.end.p0(ptr nonnull [[DATA]])
+; FLAT-NEXT: call void @llvm.lifetime.end.p0(ptr nonnull [[ZERO]])
+; FLAT-NEXT: call void @llvm.lifetime.end.p0(ptr nonnull [[TEMP]])
+; FLAT-NEXT: ret void
+;
+ ptr %p2, ptr %p3,
+ i32 noundef %cond) {
+entry:
+ %y = alloca %struct.ptr4, align 8
+ %x.addr = alloca ptr, align 8
+ %cond.addr = alloca i32, align 4
+ %temp = alloca %struct.ptr4, align 8
+ %zero = alloca %struct.ptr4, align 8
+ %data = alloca %struct.ptr4, align 8
+ %y_p0 = getelementptr inbounds %struct.ptr4, ptr %y, i32 0, i32 0
+ store ptr %p0, ptr %y_p0, align 8
+ %y_p1 = getelementptr inbounds %struct.ptr4, ptr %y, i32 0, i32 1
+ store ptr %p1, ptr %y_p1, align 8
+ %y_p2 = getelementptr inbounds %struct.ptr4, ptr %y, i32 0, i32 2
+ store ptr %p2, ptr %y_p2, align 8
+ %y_p3 = getelementptr inbounds %struct.ptr4, ptr %y, i32 0, i32 3
+ store ptr %p3, ptr %y_p3, align 8
+ store ptr %x, ptr %x.addr, align 8
+ store i32 %cond, ptr %cond.addr, align 4
+ call void @llvm.lifetime.start.p0(ptr %temp)
+ call void @llvm.memcpy.p0.p0.i64(ptr align 8 %temp, ptr align 8 %y,
+ i64 32, i1 false)
+ call void @llvm.lifetime.start.p0(ptr %zero)
+ call void @llvm.memset.p0.i64(ptr align 8 %zero, i8 0, i64 32, i1 false)
+ call void @llvm.lifetime.start.p0(ptr %data)
+ %c.ptr = load i32, ptr %cond.addr, align 4
+ %tobool.ptr = icmp ne i32 %c.ptr, 0
+ br i1 %tobool.ptr, label %cond.true.ptr, label %cond.false.ptr
+
+cond.true.ptr:
+ br label %cond.end.ptr
+
+cond.false.ptr:
+ br label %cond.end.ptr
+
+cond.end.ptr:
+ %cond1.ptr = phi ptr [ %temp, %cond.true.ptr ], [ %zero, %cond.false.ptr ]
+ call void @llvm.memcpy.p0.p0.i64(ptr align 8 %data, ptr align 8 %cond1.ptr,
+ i64 32, i1 false)
+ %xv.ptr = load ptr, ptr %x.addr, align 8
+ call void @llvm.memcpy.p0.p0.i64(ptr align 8 %xv.ptr, ptr align 8 %data,
+ i64 32, i1 false)
+ call void @llvm.lifetime.end.p0(ptr %data)
+ call void @llvm.lifetime.end.p0(ptr %zero)
+ call void @llvm.lifetime.end.p0(ptr %temp)
+ ret void
+}
+;; NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line:
+; I1: {{.*}}
+; NESTED: {{.*}}
+; NONHOMO: {{.*}}
+; PADDED: {{.*}}
+; PTR: {{.*}}
>From 5c21f21c8eb088ea2bbc0044e05a5a98f92d1414 Mon Sep 17 00:00:00 2001
From: "Yaxun (Sam) Liu" <yaxun.liu at amd.com>
Date: Fri, 16 Jan 2026 10:32:49 -0500
Subject: [PATCH 2/2] [SROA] Mark public entry points with LLVM_ABI
ids ABI checks scan modified headers and require LLVM_ABI on public,
out-of-line methods. Annotate SROAPass::run and printPipeline and
include Compiler.h so the check passes consistently.
---
llvm/include/llvm/Transforms/Scalar/SROA.h | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/llvm/include/llvm/Transforms/Scalar/SROA.h b/llvm/include/llvm/Transforms/Scalar/SROA.h
index dcbccd0bad1bd..733586fdc80e2 100644
--- a/llvm/include/llvm/Transforms/Scalar/SROA.h
+++ b/llvm/include/llvm/Transforms/Scalar/SROA.h
@@ -16,6 +16,7 @@
#define LLVM_TRANSFORMS_SCALAR_SROA_H
#include "llvm/IR/PassManager.h"
+#include "llvm/Support/Compiler.h"
#include <optional>
namespace llvm {
@@ -40,10 +41,11 @@ class SROAPass : public PassInfoMixin<SROAPass> {
SROAPass(SROAPassOptions Options = {}) : Options(Options) {}
/// Run the pass over the function.
- PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
+ LLVM_ABI PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
- void printPipeline(raw_ostream &OS,
- function_ref<StringRef(StringRef)> MapClassName2PassName);
+ LLVM_ABI void printPipeline(
+ raw_ostream &OS,
+ function_ref<StringRef(StringRef)> MapClassName2PassName);
};
} // end namespace llvm
More information about the llvm-commits
mailing list