[llvm] [LSROA] Add logical SROA pass (PR #192058)
Nathan Gauër via llvm-commits
llvm-commits at lists.llvm.org
Thu Jul 16 06:58:29 PDT 2026
https://github.com/Keenuts updated https://github.com/llvm/llvm-project/pull/192058
>From e726fd37a539486b4caa3618b995327709961d80 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <brioche at google.com>
Date: Tue, 14 Apr 2026 14:58:39 +0200
Subject: [PATCH 01/25] [IR] Add logical SROA pass
This commit adds a logical-pointer compatible SROA pass.
As-is, the pass does not optimizes nested structs, arrays, or
usages of structured alloca with non-structured GEP instructions.
Adding support for both is not complex, but increase the size of
the PR, hence we can start with this.
Next step will be to allow mem2reg to apply on logical alloca/ptr,
and to add support for nesting/arrays.
---
llvm/include/llvm/IR/IRBuilder.h | 10 +
llvm/include/llvm/InitializePasses.h | 1 +
llvm/include/llvm/LinkAllPasses.h | 1 +
llvm/include/llvm/Transforms/Scalar.h | 7 +
llvm/include/llvm/Transforms/Scalar/LSROA.h | 35 ++++
llvm/lib/Passes/PassBuilder.cpp | 1 +
llvm/lib/Passes/PassRegistry.def | 1 +
llvm/lib/Transforms/Scalar/CMakeLists.txt | 1 +
llvm/lib/Transforms/Scalar/LSROA.cpp | 205 ++++++++++++++++++++
llvm/test/Transforms/LSROA/array.ll | 24 +++
llvm/test/Transforms/LSROA/basictest.ll | 185 ++++++++++++++++++
llvm/test/Transforms/LSROA/nesting.ll | 33 ++++
llvm/test/Transforms/LSROA/normal-gep.ll | 25 +++
13 files changed, 529 insertions(+)
create mode 100644 llvm/include/llvm/Transforms/Scalar/LSROA.h
create mode 100644 llvm/lib/Transforms/Scalar/LSROA.cpp
create mode 100644 llvm/test/Transforms/LSROA/array.ll
create mode 100644 llvm/test/Transforms/LSROA/basictest.ll
create mode 100644 llvm/test/Transforms/LSROA/nesting.ll
create mode 100644 llvm/test/Transforms/LSROA/normal-gep.ll
diff --git a/llvm/include/llvm/IR/IRBuilder.h b/llvm/include/llvm/IR/IRBuilder.h
index bdec42516b8cd..05f744ad71246 100644
--- a/llvm/include/llvm/IR/IRBuilder.h
+++ b/llvm/include/llvm/IR/IRBuilder.h
@@ -1873,6 +1873,16 @@ class IRBuilderBase {
return Insert(new AllocaInst(Ty, AddrSpace, ArraySize, AllocaAlign), Name);
}
+ CallInst *CreateStructuredAlloca(Type *BaseType, const Twine &Name = "") {
+ const DataLayout &DL = BB->getDataLayout();
+ PointerType *PtrTy = DL.getAllocaPtrType(Context);
+ CallInst *Output =
+ CreateIntrinsic(Intrinsic::structured_alloca, {PtrTy}, {}, {}, Name);
+ Output->addRetAttr(
+ Attribute::get(getContext(), Attribute::ElementType, BaseType));
+ return Output;
+ }
+
/// Provided to resolve 'CreateLoad(Ty, Ptr, "...")' correctly, instead of
/// converting the string to 'bool' for the isVolatile parameter.
LoadInst *CreateLoad(Type *Ty, Value *Ptr, const char *Name) {
diff --git a/llvm/include/llvm/InitializePasses.h b/llvm/include/llvm/InitializePasses.h
index 604118cee9e2e..f7f8fd3ecde27 100644
--- a/llvm/include/llvm/InitializePasses.h
+++ b/llvm/include/llvm/InitializePasses.h
@@ -294,6 +294,7 @@ LLVM_ABI void initializeResetMachineFunctionPass(PassRegistry &);
LLVM_ABI void initializeRuntimeLibraryInfoWrapperPass(PassRegistry &);
LLVM_ABI void initializeSCEVAAWrapperPassPass(PassRegistry &);
LLVM_ABI void initializeSROALegacyPassPass(PassRegistry &);
+LLVM_ABI void initializeLSROALegacyPassPass(PassRegistry &);
LLVM_ABI void initializeSafeStackLegacyPassPass(PassRegistry &);
LLVM_ABI void initializeSafepointIRVerifierPass(PassRegistry &);
LLVM_ABI void initializeSelectOptimizePass(PassRegistry &);
diff --git a/llvm/include/llvm/LinkAllPasses.h b/llvm/include/llvm/LinkAllPasses.h
index a01a67f136710..00d56e0cb903c 100644
--- a/llvm/include/llvm/LinkAllPasses.h
+++ b/llvm/include/llvm/LinkAllPasses.h
@@ -119,6 +119,7 @@ struct ForcePassLinking {
(void)llvm::createRegionViewerPass();
(void)llvm::createSafeStackPass();
(void)llvm::createSROAPass();
+ (void)llvm::createLSROAPass();
(void)llvm::createSingleLoopExtractorPass();
(void)llvm::createTailCallEliminationPass();
(void)llvm::createConstantHoistingPass();
diff --git a/llvm/include/llvm/Transforms/Scalar.h b/llvm/include/llvm/Transforms/Scalar.h
index e2a236458dd79..9d53dba325652 100644
--- a/llvm/include/llvm/Transforms/Scalar.h
+++ b/llvm/include/llvm/Transforms/Scalar.h
@@ -46,6 +46,13 @@ LLVM_ABI FunctionPass *createDeadStoreEliminationPass();
//
LLVM_ABI FunctionPass *createSROAPass(bool PreserveCFG = true);
+//===----------------------------------------------------------------------===//
+//
+// LSROA - Replace aggregates or pieces of aggregates with scalar SSA values
+// when used when logical allocation/GEP instructions.
+//
+LLVM_ABI FunctionPass *createLSROAPass();
+
//===----------------------------------------------------------------------===//
//
// LICM - This pass is a loop invariant code motion and memory promotion pass.
diff --git a/llvm/include/llvm/Transforms/Scalar/LSROA.h b/llvm/include/llvm/Transforms/Scalar/LSROA.h
new file mode 100644
index 0000000000000..9c730a78489e0
--- /dev/null
+++ b/llvm/include/llvm/Transforms/Scalar/LSROA.h
@@ -0,0 +1,35 @@
+//===- LSROA.h - Logical Scalar Replacement Of Aggregates -------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+/// \file
+/// This file provides the interface for LLVM's Logical Scalar Replacement of
+/// Aggregates pass. This pass provides both aggregate splitting and the
+/// primary SSA formation used in the compiler when used with structured GEP
+/// and allocas.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_TRANSFORMS_SCALAR_LSROA_H
+#define LLVM_TRANSFORMS_SCALAR_LSROA_H
+
+#include "llvm/IR/PassManager.h"
+
+namespace llvm {
+
+class Function;
+
+class LSROAPass : public PassInfoMixin<LSROAPass> {
+public:
+ LSROAPass();
+
+ /// Run the pass over the function.
+ PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
+};
+
+} // end namespace llvm
+
+#endif // LLVM_TRANSFORMS_SCALAR_LSROA_H
diff --git a/llvm/lib/Passes/PassBuilder.cpp b/llvm/lib/Passes/PassBuilder.cpp
index 715f4d2c0abc4..acb88ef15d564 100644
--- a/llvm/lib/Passes/PassBuilder.cpp
+++ b/llvm/lib/Passes/PassBuilder.cpp
@@ -294,6 +294,7 @@
#include "llvm/Transforms/Scalar/JumpTableToSwitch.h"
#include "llvm/Transforms/Scalar/JumpThreading.h"
#include "llvm/Transforms/Scalar/LICM.h"
+#include "llvm/Transforms/Scalar/LSROA.h"
#include "llvm/Transforms/Scalar/LoopAccessAnalysisPrinter.h"
#include "llvm/Transforms/Scalar/LoopBoundSplit.h"
#include "llvm/Transforms/Scalar/LoopDataPrefetch.h"
diff --git a/llvm/lib/Passes/PassRegistry.def b/llvm/lib/Passes/PassRegistry.def
index 9fe40d441e0af..97ae1f3170d9f 100644
--- a/llvm/lib/Passes/PassRegistry.def
+++ b/llvm/lib/Passes/PassRegistry.def
@@ -566,6 +566,7 @@ FUNCTION_PASS("view-dom-only", DomOnlyViewer())
FUNCTION_PASS("view-post-dom", PostDomViewer())
FUNCTION_PASS("view-post-dom-only", PostDomOnlyViewer())
FUNCTION_PASS("wasm-eh-prepare", WasmEHPreparePass())
+FUNCTION_PASS("lsroa", LSROAPass())
#undef FUNCTION_PASS
#ifndef FUNCTION_PASS_WITH_PARAMS
diff --git a/llvm/lib/Transforms/Scalar/CMakeLists.txt b/llvm/lib/Transforms/Scalar/CMakeLists.txt
index 4f6b208c2e729..f631e60b01380 100644
--- a/llvm/lib/Transforms/Scalar/CMakeLists.txt
+++ b/llvm/lib/Transforms/Scalar/CMakeLists.txt
@@ -69,6 +69,7 @@ add_llvm_component_library(LLVMScalarOpts
RewriteStatepointsForGC.cpp
SCCP.cpp
SROA.cpp
+ LSROA.cpp
Scalar.cpp
Scalarizer.cpp
ScalarizeMaskedMemIntrin.cpp
diff --git a/llvm/lib/Transforms/Scalar/LSROA.cpp b/llvm/lib/Transforms/Scalar/LSROA.cpp
new file mode 100644
index 0000000000000..0f85c4f4d649b
--- /dev/null
+++ b/llvm/lib/Transforms/Scalar/LSROA.cpp
@@ -0,0 +1,205 @@
+//===- LSROA.cpp - Logical Scalar Replacement Of Aggregates ---------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+/// \file
+/// This transformation implements the well known scalar replacement of
+/// aggregates transformation. It tries to identify promotable elements of an
+/// aggregate alloca, and promote them to registers. It will also try to
+/// convert uses of an element (or set of elements) of an alloca into a vector
+/// or bitfield-style integer scalar if appropriate.
+///
+/// It works to do this with minimal slicing of the alloca so that regions
+/// which are merely transferred in and out of external memory remain unchanged
+/// and are not decomposed to scalar code.
+///
+/// Because this also performs alloca promotion, it can be thought of as also
+/// serving the purpose of SSA formation. The algorithm iterates on the
+/// function until all opportunities for promotion have been realized.
+///
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Transforms/Scalar/LSROA.h"
+#include "llvm/ADT/SmallPtrSet.h"
+#include "llvm/ADT/SmallSet.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Analysis/DomTreeUpdater.h"
+#include "llvm/IR/IRBuilder.h"
+#include "llvm/IR/IntrinsicInst.h"
+#include "llvm/IR/PassManager.h"
+#include "llvm/InitializePasses.h"
+#include "llvm/Pass.h"
+#include "llvm/Transforms/Scalar.h"
+
+using namespace llvm;
+
+#define DEBUG_TYPE "lsroa"
+
+namespace {
+
+class LSROA {
+public:
+ LSROA() {}
+
+ bool runLSROA(Function &F);
+
+private:
+ bool runOnStructuredAlloca(StructuredAllocaInst &SAI);
+};
+
+} // end anonymous namespace
+
+bool LSROA::runOnStructuredAlloca(StructuredAllocaInst &SAI) {
+ // For now, LSROA only handles SGEP on structs.
+ StructType *ST = dyn_cast<StructType>(SAI.getAllocationType());
+ if (!ST)
+ return false;
+
+ SmallVector<SmallVector<StructuredGEPInst *, 4>, 4> FieldUsers(
+ ST->getNumElements());
+ for (const auto &user : SAI.users()) {
+ // Lifetime intrinsincs are handled differently.
+ auto II = dyn_cast<IntrinsicInst>(user);
+ if (II && II->isLifetimeStartOrEnd())
+ continue;
+
+ auto SGEP = dyn_cast<StructuredGEPInst>(user);
+ // If any user is not an SGEP, we bail out.
+ if (!SGEP) {
+ return false;
+ }
+
+ // If the SGEP has no indices, this means we have a pointer on the whole
+ // struct. For now, we bail out: if it was not used, it would be DCE'd, so
+ // there is probably a reference to the whole struct somewhere.
+ if (SGEP->getNumIndices() == 0)
+ return false;
+
+ // IR rule: SGEP on struct can only use constant int as indices.
+ ConstantInt *Index = cast<ConstantInt>(SGEP->getIndexOperand(0));
+ assert(Index->getZExtValue() < FieldUsers.size());
+ FieldUsers[Index->getZExtValue()].push_back(SGEP);
+ }
+
+ bool Changed = false;
+ SmallPtrSet<Instruction *, 4> DeadLifetimeInstrs;
+ IRBuilder B(&SAI);
+ for (size_t I = 0; I < FieldUsers.size(); ++I) {
+ if (FieldUsers[I].size() == 0)
+ continue;
+ Changed = true;
+
+ B.SetInsertPoint(&SAI);
+ StructuredAllocaInst *NSAI = cast<StructuredAllocaInst>(
+ B.CreateStructuredAlloca(ST->getElementType(I)));
+
+ // Step 1: for each lifetime intrinsic, generate one per newly created NSAI.
+ for (const auto &user : SAI.users()) {
+ auto II = dyn_cast<IntrinsicInst>(user);
+ if (II && II->getIntrinsicID() == Intrinsic::lifetime_start) {
+ B.SetInsertPoint(II);
+ B.CreateLifetimeStart(NSAI);
+ DeadLifetimeInstrs.insert(II);
+ continue;
+ }
+
+ if (II && II->getIntrinsicID() == Intrinsic::lifetime_end) {
+ B.SetInsertPoint(II);
+ B.CreateLifetimeEnd(NSAI);
+ DeadLifetimeInstrs.insert(II);
+ continue;
+ }
+ }
+
+ // Step 2: replace each SGEP usage with the new alloca
+ for (StructuredGEPInst *SGEP : FieldUsers[I]) {
+ if (SGEP->getNumIndices() == 1) {
+ SGEP->replaceAllUsesWith(NSAI);
+ SGEP->eraseFromParent();
+ continue;
+ }
+
+ SmallVector<Value *, 4> Indices;
+ for (unsigned J = 1; J < SGEP->getNumIndices(); ++J)
+ Indices.push_back(SGEP->getIndexOperand(J));
+
+ B.SetInsertPoint(SGEP);
+ StructuredGEPInst *NSGEP = cast<StructuredGEPInst>(B.CreateStructuredGEP(
+ ST->getElementType(I), NSAI, Indices, SGEP->getName()));
+ SGEP->replaceAllUsesWith(NSGEP);
+ SGEP->eraseFromParent();
+ }
+ }
+
+ for (Instruction *I : DeadLifetimeInstrs)
+ I->eraseFromParent();
+ SAI.eraseFromParent();
+
+ return Changed;
+}
+
+bool LSROA::runLSROA(Function &F) {
+ BasicBlock &EntryBB = F.getEntryBlock();
+ SmallVector<StructuredAllocaInst *> Worklist;
+
+ for (BasicBlock::iterator I = EntryBB.begin(), E = std::prev(EntryBB.end());
+ I != E; ++I) {
+ if (StructuredAllocaInst *SAI = dyn_cast<StructuredAllocaInst>(I))
+ Worklist.push_back(SAI);
+ }
+
+ bool Changed = false;
+ for (StructuredAllocaInst *SAI : Worklist)
+ Changed |= runOnStructuredAlloca(*SAI);
+ return Changed;
+}
+
+PreservedAnalyses LSROAPass::run(Function &F, FunctionAnalysisManager &AM) {
+ if (!LSROA().runLSROA(F))
+ return PreservedAnalyses::all();
+
+ PreservedAnalyses PA;
+ PA.preserveSet<CFGAnalyses>();
+ PA.preserve<DominatorTreeAnalysis>();
+ return PA;
+}
+
+LSROAPass::LSROAPass() {}
+
+namespace {
+
+/// A legacy pass for the legacy pass manager that wraps the LSROA pass.
+class LSROALegacyPass : public FunctionPass {
+public:
+ static char ID;
+
+ LSROALegacyPass() : FunctionPass(ID) {
+ initializeLSROALegacyPassPass(*PassRegistry::getPassRegistry());
+ }
+
+ bool runOnFunction(Function &F) override {
+ if (skipFunction(F))
+ return false;
+ return LSROA().runLSROA(F);
+ }
+
+ void getAnalysisUsage(AnalysisUsage &AU) const override {
+ AU.addPreserved<DominatorTreeWrapperPass>();
+ }
+
+ StringRef getPassName() const override { return "LSROA"; }
+};
+
+} // end anonymous namespace
+
+char LSROALegacyPass::ID = 0;
+
+FunctionPass *llvm::createLSROAPass() { return new LSROALegacyPass(); }
+
+INITIALIZE_PASS_BEGIN(LSROALegacyPass, "lsroa",
+ "Logical Scalar Replacement Of Aggregates", false, false)
+INITIALIZE_PASS_END(LSROALegacyPass, "lsroa",
+ "Logical Scalar Replacement Of Aggregates", false, false)
diff --git a/llvm/test/Transforms/LSROA/array.ll b/llvm/test/Transforms/LSROA/array.ll
new file mode 100644
index 0000000000000..036d1d15092b6
--- /dev/null
+++ b/llvm/test/Transforms/LSROA/array.ll
@@ -0,0 +1,24 @@
+; RUN: opt < %s -passes='lsroa' -S -debug | FileCheck %s --check-prefixes=CHECK
+
+declare void @llvm.lifetime.start.p0(ptr nocapture)
+declare void @llvm.lifetime.end.p0(ptr nocapture)
+declare ptr @llvm.structured.alloca.p0()
+declare ptr @llvm.structured.gep.p0(ptr, ...)
+
+define i32 @test_simple_array() {
+; CHECK-LABEL: @test_simple_array(
+; CHECK-NEXT: entry:
+entry:
+ %tmp = call elementtype([10 x i32]) ptr @llvm.structured.alloca.p0()
+ %ptr = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([10 x i32]) %tmp, i32 0)
+ store i32 0, ptr %ptr
+ %res = load i32, ptr %ptr
+ ret i32 %res
+
+; CHECK-NEXT: %tmp = call elementtype([10 x i32]) ptr @llvm.structured.alloca.p0()
+; CHECK-NEXT: %ptr = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([10 x i32]) %tmp, i32 0)
+; CHECK-NEXT: store i32 0, ptr %ptr
+; CHECK-NEXT: %res = load i32, ptr %ptr
+; CHECK-NEXT: ret i32 %res
+}
+
diff --git a/llvm/test/Transforms/LSROA/basictest.ll b/llvm/test/Transforms/LSROA/basictest.ll
new file mode 100644
index 0000000000000..a3fa797af9938
--- /dev/null
+++ b/llvm/test/Transforms/LSROA/basictest.ll
@@ -0,0 +1,185 @@
+; RUN: opt < %s -passes='lsroa' -S -debug | FileCheck %s --check-prefixes=CHECK
+
+declare void @llvm.lifetime.start.p0(ptr nocapture)
+declare void @llvm.lifetime.end.p0(ptr nocapture)
+declare ptr @llvm.structured.alloca.p0()
+declare ptr @llvm.structured.gep.p0(ptr, ...)
+
+define i32 @test_simple_scalar() {
+; CHECK-LABEL: @test_simple_scalar(
+; CHECK-NEXT: entry:
+entry:
+ %tmp = call elementtype(i32) ptr @llvm.structured.alloca.p0()
+ call void @llvm.lifetime.start.p0(ptr %tmp)
+ store i32 0, ptr %tmp
+ %res = load i32, ptr %tmp
+ call void @llvm.lifetime.end.p0(ptr %tmp)
+ ret i32 %res
+; CHECK: %tmp = call elementtype(i32) ptr @llvm.structured.alloca.p0()
+; CHECK: call void @llvm.lifetime.start.p0(ptr %tmp)
+; CHECK: store i32 0, ptr %tmp
+; CHECK: %res = load i32, ptr %tmp
+; CHECK: call void @llvm.lifetime.end.p0(ptr %tmp)
+}
+
+define i32 @test_simple_struct_entire_write_read() {
+; CHECK-LABEL: @test_simple_struct_entire_write_read(
+; CHECK-NEXT: entry:
+entry:
+ %tmp = call elementtype({ i32, i32 }) ptr @llvm.structured.alloca.p0()
+ %ptr0 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) %tmp, i32 0)
+ %ptr1 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) %tmp, i32 1)
+
+ call void @llvm.lifetime.start.p0(ptr %tmp)
+ store i32 0, ptr %ptr0
+ store i32 1, ptr %ptr1
+ %a = load i32, ptr %ptr0
+ %b = load i32, ptr %ptr1
+ call void @llvm.lifetime.end.p0(ptr %tmp)
+
+ %res = add i32 %a, %b
+ ret i32 %res
+
+; CHECK-NEXT: %[[#a:]] = call elementtype(i32) ptr @llvm.structured.alloca.p0()
+; CHECK-NEXT: %[[#b:]] = call elementtype(i32) ptr @llvm.structured.alloca.p0()
+
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr %[[#a]])
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr %[[#b]])
+; CHECK-NEXT: store i32 0, ptr %[[#a]]
+; CHECK-NEXT: store i32 1, ptr %[[#b]]
+; CHECK-NEXT: %a = load i32, ptr %[[#a]]
+; CHECK-NEXT: %b = load i32, ptr %[[#b]]
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %[[#a]])
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %[[#b]])
+}
+
+define i32 @test_simple_struct_aliasing() {
+; CHECK-LABEL: @test_simple_struct_aliasing(
+; CHECK-NEXT: entry:
+entry:
+ %tmp = call elementtype({ i32, i32 }) ptr @llvm.structured.alloca.p0()
+ %ptr0 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) %tmp, i32 0)
+ %ptr1 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) %tmp, i32 0)
+
+ call void @llvm.lifetime.start.p0(ptr %tmp)
+ store i32 0, ptr %ptr0
+ store i32 1, ptr %ptr1
+ %a = load i32, ptr %ptr0
+ %b = load i32, ptr %ptr1
+ call void @llvm.lifetime.end.p0(ptr %tmp)
+
+ %res = add i32 %a, %b
+ ret i32 %res
+
+; CHECK-NEXT: %[[#a:]] = call elementtype(i32) ptr @llvm.structured.alloca.p0()
+
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr %[[#a]])
+; CHECK-NEXT: store i32 0, ptr %[[#a]]
+; CHECK-NEXT: store i32 1, ptr %[[#a]]
+; CHECK-NEXT: %a = load i32, ptr %[[#a]]
+; CHECK-NEXT: %b = load i32, ptr %[[#a]]
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %[[#a]])
+}
+
+define i32 @test_simple_struct_partial_write_read() {
+; CHECK-LABEL: @test_simple_struct_partial_write_read(
+; CHECK-NEXT: entry:
+entry:
+ %tmp = call elementtype({ i32, i32 }) ptr @llvm.structured.alloca.p0()
+ call void @llvm.lifetime.start.p0(ptr %tmp)
+ %ptr0 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) %tmp, i32 0)
+ store i32 0, ptr %ptr0
+ %a = load i32, ptr %ptr0
+ call void @llvm.lifetime.end.p0(ptr %tmp)
+
+; CHECK-NEXT: %[[#a:]] = call elementtype(i32) ptr @llvm.structured.alloca.p0()
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr %[[#a]])
+; CHECK-NEXT: store i32 0, ptr %[[#a]]
+; CHECK-NEXT: %a = load i32, ptr %[[#a]]
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %[[#a]])
+
+ ret i32 %a
+}
+
+define i32 @test_struct_use_across_lifetime() {
+; CHECK-LABEL: @test_struct_use_across_lifetime(
+; CHECK-NEXT: entry:
+entry:
+ %tmp = call elementtype({ i32, i32 }) ptr @llvm.structured.alloca.p0()
+ %ptr0 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) %tmp, i32 0)
+ %ptr1 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) %tmp, i32 1)
+; CHECK-NEXT: %[[#a:]] = call elementtype(i32) ptr @llvm.structured.alloca.p0()
+; CHECK-NEXT: %[[#b:]] = call elementtype(i32) ptr @llvm.structured.alloca.p0()
+
+ call void @llvm.lifetime.start.p0(ptr %tmp)
+ store i32 0, ptr %ptr0
+ %a = load i32, ptr %ptr0
+ call void @llvm.lifetime.end.p0(ptr %tmp)
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr %[[#a]])
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr %[[#b]])
+; CHECK-NEXT: store i32 0, ptr %[[#a]]
+; CHECK-NEXT: %a = load i32, ptr %[[#a]]
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %[[#a]])
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %[[#b]])
+
+ call void @llvm.lifetime.start.p0(ptr %tmp)
+ store i32 0, ptr %ptr1
+ %b = load i32, ptr %ptr1
+ call void @llvm.lifetime.end.p0(ptr %tmp)
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr %[[#a]])
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr %[[#b]])
+; CHECK-NEXT: store i32 0, ptr %[[#b]]
+; CHECK-NEXT: %b = load i32, ptr %[[#b]]
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %[[#a]])
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %[[#b]])
+
+ %c = add i32 %b, %a
+ ret i32 %c
+}
+
+define i32 @test_partial_use_phi_node(i1 %cond) {
+; CHECK-LABEL: @test_partial_use_phi_node(
+; CHECK-NEXT: entry:
+entry:
+ %tmp = call elementtype({ i32, i32 }) ptr @llvm.structured.alloca.p0()
+; CHECK-NEXT: %[[#a:]] = call elementtype(i32) ptr @llvm.structured.alloca.p0()
+; CHECK-NEXT: %[[#b:]] = call elementtype(i32) ptr @llvm.structured.alloca.p0()
+ %ptr0 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) %tmp, i32 0)
+ %ptr1 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) %tmp, i32 1)
+ br i1 %cond, label %l1, label %l2
+
+l1:
+ call void @llvm.lifetime.start.p0(ptr %tmp)
+ store i32 0, ptr %ptr0
+ br label %l3
+; CHECK: l1: ; preds = %entry
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr %[[#a]])
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr %[[#b]])
+; CHECK-NEXT: store i32 0, ptr %[[#a]]
+; CHECK-NEXT: br label %l3
+
+l2:
+ call void @llvm.lifetime.start.p0(ptr %tmp)
+ store i32 1, ptr %ptr1
+ br label %l3
+; CHECK: l2: ; preds = %entry
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr %[[#a]])
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr %[[#b]])
+; CHECK-NEXT: store i32 1, ptr %[[#b]]
+; CHECK-NEXT: br label %l3
+
+l3:
+ %ptr = phi ptr [ %ptr0, %l1 ], [ %ptr1, %l2 ]
+ %a = load i32, ptr %ptr
+ call void @llvm.lifetime.end.p0(ptr %tmp)
+ br label %exit
+; CHECK: l3: ; preds = %l2, %l1
+; CHECK-NEXT: %ptr = phi ptr [ %[[#a]], %l1 ], [ %[[#b]], %l2 ]
+; CHECK-NEXT: %a = load i32, ptr %ptr
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %[[#a]])
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %[[#b]])
+; CHECK-NEXT: br label %exit
+
+exit:
+ ret i32 %a
+}
diff --git a/llvm/test/Transforms/LSROA/nesting.ll b/llvm/test/Transforms/LSROA/nesting.ll
new file mode 100644
index 0000000000000..5c329b481d391
--- /dev/null
+++ b/llvm/test/Transforms/LSROA/nesting.ll
@@ -0,0 +1,33 @@
+; RUN: opt < %s -passes='lsroa' -S -debug | FileCheck %s --check-prefixes=CHECK
+
+declare void @llvm.lifetime.start.p0(ptr nocapture)
+declare void @llvm.lifetime.end.p0(ptr nocapture)
+declare ptr @llvm.structured.alloca.p0()
+declare ptr @llvm.structured.gep.p0(ptr, ...)
+
+%S = type { i32, { i32, i32 } }
+
+define i32 @test_nested_struct() {
+; CHECK-LABEL: @test_nested_struct(
+; CHECK-NEXT: entry:
+entry:
+ %tmp = call elementtype(%S) ptr @llvm.structured.alloca.p0()
+ %0 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%S) %tmp, i32 0)
+ %1 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%S) %tmp, i32 1, i32 0)
+
+; CHECK: %[[#a:]] = call elementtype(i32) ptr @llvm.structured.alloca.p0()
+; CHECK: %[[#b:]] = call elementtype({ i32, i32 }) ptr @llvm.structured.alloca.p0()
+; CHECK: %[[#ptr:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) %[[#b]], i32 0)
+
+ store i32 0, ptr %0
+ store i32 1, ptr %1
+ %a = load i32, ptr %0
+ %b = load i32, ptr %1
+; CHECK: store i32 0, ptr %[[#a]]
+; CHECK: store i32 1, ptr %[[#ptr]]
+; CHECK: %a = load i32, ptr %[[#a]]
+; CHECK: %b = load i32, ptr %[[#ptr]]
+
+ %c = add i32 %a, %b
+ ret i32 %c
+}
diff --git a/llvm/test/Transforms/LSROA/normal-gep.ll b/llvm/test/Transforms/LSROA/normal-gep.ll
new file mode 100644
index 0000000000000..04cf6b38d9e1a
--- /dev/null
+++ b/llvm/test/Transforms/LSROA/normal-gep.ll
@@ -0,0 +1,25 @@
+; RUN: opt < %s -passes='lsroa' -S -debug | FileCheck %s --check-prefixes=CHECK
+
+define i32 @test_normal_gep() {
+; CHECK-LABEL: @test_normal_gep(
+; CHECK-NEXT: entry:
+entry:
+ %tmp = call elementtype({ i32, i32 }) ptr @llvm.structured.alloca.p0()
+ %0 = getelementptr i8, ptr %tmp, i32 0
+ %1 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) %tmp, i32 1)
+; CHECK: %tmp = call elementtype({ i32, i32 }) ptr @llvm.structured.alloca.p0()
+; CHECK: %[[#a:]] = getelementptr i8, ptr %tmp, i32 0
+; CHECK: %[[#b:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) %tmp, i32 1)
+
+ store i32 0, ptr %0
+ store i32 1, ptr %1
+ %a = load i32, ptr %0
+ %b = load i32, ptr %1
+; CHECK: store i32 0, ptr %[[#a]]
+; CHECK: store i32 1, ptr %[[#b]]
+; CHECK: %a = load i32, ptr %[[#a]]
+; CHECK: %b = load i32, ptr %[[#b]]
+
+ %c = add i32 %a, %b
+ ret i32 %c
+}
>From a24dd28edf5006e372f96260d401d06d9a513d67 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <brioche at google.com>
Date: Wed, 15 Apr 2026 11:22:11 +0200
Subject: [PATCH 02/25] lsroa -> logical-sroa
---
llvm/lib/Passes/PassRegistry.def | 2 +-
llvm/lib/Transforms/Scalar/LSROA.cpp | 8 ++++----
llvm/test/Transforms/LSROA/array.ll | 2 +-
llvm/test/Transforms/LSROA/basictest.ll | 2 +-
llvm/test/Transforms/LSROA/nesting.ll | 2 +-
llvm/test/Transforms/LSROA/normal-gep.ll | 2 +-
6 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/llvm/lib/Passes/PassRegistry.def b/llvm/lib/Passes/PassRegistry.def
index 97ae1f3170d9f..0903443e72207 100644
--- a/llvm/lib/Passes/PassRegistry.def
+++ b/llvm/lib/Passes/PassRegistry.def
@@ -463,6 +463,7 @@ FUNCTION_PASS("kernel-info", KernelInfoPrinter(TM))
FUNCTION_PASS("lcssa", LCSSAPass())
FUNCTION_PASS("libcalls-shrinkwrap", LibCallsShrinkWrapPass())
FUNCTION_PASS("load-store-vectorizer", LoadStoreVectorizerPass())
+FUNCTION_PASS("logical-sroa", LSROAPass())
FUNCTION_PASS("loop-data-prefetch", LoopDataPrefetchPass())
FUNCTION_PASS("loop-distribute", LoopDistributePass())
FUNCTION_PASS("loop-fusion", LoopFusePass())
@@ -566,7 +567,6 @@ FUNCTION_PASS("view-dom-only", DomOnlyViewer())
FUNCTION_PASS("view-post-dom", PostDomViewer())
FUNCTION_PASS("view-post-dom-only", PostDomOnlyViewer())
FUNCTION_PASS("wasm-eh-prepare", WasmEHPreparePass())
-FUNCTION_PASS("lsroa", LSROAPass())
#undef FUNCTION_PASS
#ifndef FUNCTION_PASS_WITH_PARAMS
diff --git a/llvm/lib/Transforms/Scalar/LSROA.cpp b/llvm/lib/Transforms/Scalar/LSROA.cpp
index 0f85c4f4d649b..3b803b6b64206 100644
--- a/llvm/lib/Transforms/Scalar/LSROA.cpp
+++ b/llvm/lib/Transforms/Scalar/LSROA.cpp
@@ -1,4 +1,4 @@
-//===- LSROA.cpp - Logical Scalar Replacement Of Aggregates ---------------===//
+//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
@@ -36,7 +36,7 @@
using namespace llvm;
-#define DEBUG_TYPE "lsroa"
+#define DEBUG_TYPE "logical-sroa"
namespace {
@@ -199,7 +199,7 @@ char LSROALegacyPass::ID = 0;
FunctionPass *llvm::createLSROAPass() { return new LSROALegacyPass(); }
-INITIALIZE_PASS_BEGIN(LSROALegacyPass, "lsroa",
+INITIALIZE_PASS_BEGIN(LSROALegacyPass, "logical-sroa",
"Logical Scalar Replacement Of Aggregates", false, false)
-INITIALIZE_PASS_END(LSROALegacyPass, "lsroa",
+INITIALIZE_PASS_END(LSROALegacyPass, "logical-sroa",
"Logical Scalar Replacement Of Aggregates", false, false)
diff --git a/llvm/test/Transforms/LSROA/array.ll b/llvm/test/Transforms/LSROA/array.ll
index 036d1d15092b6..95a5c15315438 100644
--- a/llvm/test/Transforms/LSROA/array.ll
+++ b/llvm/test/Transforms/LSROA/array.ll
@@ -1,4 +1,4 @@
-; RUN: opt < %s -passes='lsroa' -S -debug | FileCheck %s --check-prefixes=CHECK
+; RUN: opt < %s -passes='logical-sroa' -S -debug | FileCheck %s --check-prefixes=CHECK
declare void @llvm.lifetime.start.p0(ptr nocapture)
declare void @llvm.lifetime.end.p0(ptr nocapture)
diff --git a/llvm/test/Transforms/LSROA/basictest.ll b/llvm/test/Transforms/LSROA/basictest.ll
index a3fa797af9938..d35cec5eea951 100644
--- a/llvm/test/Transforms/LSROA/basictest.ll
+++ b/llvm/test/Transforms/LSROA/basictest.ll
@@ -1,4 +1,4 @@
-; RUN: opt < %s -passes='lsroa' -S -debug | FileCheck %s --check-prefixes=CHECK
+; RUN: opt < %s -passes='logical-sroa' -S -debug | FileCheck %s --check-prefixes=CHECK
declare void @llvm.lifetime.start.p0(ptr nocapture)
declare void @llvm.lifetime.end.p0(ptr nocapture)
diff --git a/llvm/test/Transforms/LSROA/nesting.ll b/llvm/test/Transforms/LSROA/nesting.ll
index 5c329b481d391..20d659b10e687 100644
--- a/llvm/test/Transforms/LSROA/nesting.ll
+++ b/llvm/test/Transforms/LSROA/nesting.ll
@@ -1,4 +1,4 @@
-; RUN: opt < %s -passes='lsroa' -S -debug | FileCheck %s --check-prefixes=CHECK
+; RUN: opt < %s -passes='logical-sroa' -S -debug | FileCheck %s --check-prefixes=CHECK
declare void @llvm.lifetime.start.p0(ptr nocapture)
declare void @llvm.lifetime.end.p0(ptr nocapture)
diff --git a/llvm/test/Transforms/LSROA/normal-gep.ll b/llvm/test/Transforms/LSROA/normal-gep.ll
index 04cf6b38d9e1a..2a14c93e71f3f 100644
--- a/llvm/test/Transforms/LSROA/normal-gep.ll
+++ b/llvm/test/Transforms/LSROA/normal-gep.ll
@@ -1,4 +1,4 @@
-; RUN: opt < %s -passes='lsroa' -S -debug | FileCheck %s --check-prefixes=CHECK
+; RUN: opt < %s -passes='logical-sroa' -S -debug | FileCheck %s --check-prefixes=CHECK
define i32 @test_normal_gep() {
; CHECK-LABEL: @test_normal_gep(
>From 13871490700340314e44dec3a8191e0a1cf39102 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <brioche at google.com>
Date: Wed, 15 Apr 2026 11:22:59 +0200
Subject: [PATCH 03/25] remove redundant preserve
---
llvm/lib/Transforms/Scalar/LSROA.cpp | 1 -
1 file changed, 1 deletion(-)
diff --git a/llvm/lib/Transforms/Scalar/LSROA.cpp b/llvm/lib/Transforms/Scalar/LSROA.cpp
index 3b803b6b64206..2753493325fe6 100644
--- a/llvm/lib/Transforms/Scalar/LSROA.cpp
+++ b/llvm/lib/Transforms/Scalar/LSROA.cpp
@@ -163,7 +163,6 @@ PreservedAnalyses LSROAPass::run(Function &F, FunctionAnalysisManager &AM) {
PreservedAnalyses PA;
PA.preserveSet<CFGAnalyses>();
- PA.preserve<DominatorTreeAnalysis>();
return PA;
}
>From 50f11505bd2e113c84c3cf82c2ca1518568152ad Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <brioche at google.com>
Date: Wed, 15 Apr 2026 11:23:39 +0200
Subject: [PATCH 04/25] drop -debug
---
llvm/test/Transforms/LSROA/array.ll | 2 +-
llvm/test/Transforms/LSROA/basictest.ll | 2 +-
llvm/test/Transforms/LSROA/nesting.ll | 2 +-
llvm/test/Transforms/LSROA/normal-gep.ll | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/llvm/test/Transforms/LSROA/array.ll b/llvm/test/Transforms/LSROA/array.ll
index 95a5c15315438..cbc421bd1e1c7 100644
--- a/llvm/test/Transforms/LSROA/array.ll
+++ b/llvm/test/Transforms/LSROA/array.ll
@@ -1,4 +1,4 @@
-; RUN: opt < %s -passes='logical-sroa' -S -debug | FileCheck %s --check-prefixes=CHECK
+; RUN: opt < %s -passes='logical-sroa' -S | FileCheck %s --check-prefixes=CHECK
declare void @llvm.lifetime.start.p0(ptr nocapture)
declare void @llvm.lifetime.end.p0(ptr nocapture)
diff --git a/llvm/test/Transforms/LSROA/basictest.ll b/llvm/test/Transforms/LSROA/basictest.ll
index d35cec5eea951..8bb90220f358e 100644
--- a/llvm/test/Transforms/LSROA/basictest.ll
+++ b/llvm/test/Transforms/LSROA/basictest.ll
@@ -1,4 +1,4 @@
-; RUN: opt < %s -passes='logical-sroa' -S -debug | FileCheck %s --check-prefixes=CHECK
+; RUN: opt < %s -passes='logical-sroa' -S | FileCheck %s --check-prefixes=CHECK
declare void @llvm.lifetime.start.p0(ptr nocapture)
declare void @llvm.lifetime.end.p0(ptr nocapture)
diff --git a/llvm/test/Transforms/LSROA/nesting.ll b/llvm/test/Transforms/LSROA/nesting.ll
index 20d659b10e687..55d4d6283ce11 100644
--- a/llvm/test/Transforms/LSROA/nesting.ll
+++ b/llvm/test/Transforms/LSROA/nesting.ll
@@ -1,4 +1,4 @@
-; RUN: opt < %s -passes='logical-sroa' -S -debug | FileCheck %s --check-prefixes=CHECK
+; RUN: opt < %s -passes='logical-sroa' -S | FileCheck %s --check-prefixes=CHECK
declare void @llvm.lifetime.start.p0(ptr nocapture)
declare void @llvm.lifetime.end.p0(ptr nocapture)
diff --git a/llvm/test/Transforms/LSROA/normal-gep.ll b/llvm/test/Transforms/LSROA/normal-gep.ll
index 2a14c93e71f3f..35f5447afb04d 100644
--- a/llvm/test/Transforms/LSROA/normal-gep.ll
+++ b/llvm/test/Transforms/LSROA/normal-gep.ll
@@ -1,4 +1,4 @@
-; RUN: opt < %s -passes='logical-sroa' -S -debug | FileCheck %s --check-prefixes=CHECK
+; RUN: opt < %s -passes='logical-sroa' -S | FileCheck %s --check-prefixes=CHECK
define i32 @test_normal_gep() {
; CHECK-LABEL: @test_normal_gep(
>From 6c39ad0fd0cb258b5c0fcda30a1a44eafb064710 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <brioche at google.com>
Date: Wed, 15 Apr 2026 16:34:42 +0200
Subject: [PATCH 05/25] reorganize code
---
llvm/lib/Transforms/Scalar/LSROA.cpp | 163 +++++++++++++++------------
1 file changed, 91 insertions(+), 72 deletions(-)
diff --git a/llvm/lib/Transforms/Scalar/LSROA.cpp b/llvm/lib/Transforms/Scalar/LSROA.cpp
index 2753493325fe6..13c38f8955775 100644
--- a/llvm/lib/Transforms/Scalar/LSROA.cpp
+++ b/llvm/lib/Transforms/Scalar/LSROA.cpp
@@ -40,108 +40,125 @@ using namespace llvm;
namespace {
-class LSROA {
-public:
- LSROA() {}
+// Return all lifetime intrinsics with the instruction I as operand.
+SmallVector<LifetimeIntrinsic *>
+collectLifetimeIntrinsicsUsing(Instruction &I) {
+ SmallVector<LifetimeIntrinsic *> Output;
- bool runLSROA(Function &F);
+ for (const auto &user : I.users()) {
+ auto II = dyn_cast<IntrinsicInst>(user);
+ if (II && isLifetimeIntrinsic(II->getIntrinsicID()))
+ Output.push_back(cast<LifetimeIntrinsic>(II));
+ }
-private:
- bool runOnStructuredAlloca(StructuredAllocaInst &SAI);
-};
+ return Output;
+}
-} // end anonymous namespace
+using SGEPVec = SmallVector<StructuredGEPInst *>;
-bool LSROA::runOnStructuredAlloca(StructuredAllocaInst &SAI) {
- // For now, LSROA only handles SGEP on structs.
- StructType *ST = dyn_cast<StructType>(SAI.getAllocationType());
- if (!ST)
- return false;
+// Returns a vector with one element for each field of the struct allocated by
+// SAI. Each element is a vector of SGEP instruction referencing this field.
+//
+// If any user of SAI is not an SGEP, or an SGEP referencing the whole struct,
+// this function returns an empty array. This function ignores lifetime
+// intrinsics.
+SmallVector<SmallVector<StructuredGEPInst *>>
+collectPerFieldSGEP(StructuredAllocaInst &SAI) {
+ StructType *ST = cast<StructType>(SAI.getAllocationType());
+ SmallVector<SmallVector<StructuredGEPInst *>> Output(ST->getNumElements());
- SmallVector<SmallVector<StructuredGEPInst *, 4>, 4> FieldUsers(
- ST->getNumElements());
for (const auto &user : SAI.users()) {
- // Lifetime intrinsincs are handled differently.
auto II = dyn_cast<IntrinsicInst>(user);
if (II && II->isLifetimeStartOrEnd())
continue;
auto SGEP = dyn_cast<StructuredGEPInst>(user);
- // If any user is not an SGEP, we bail out.
- if (!SGEP) {
- return false;
- }
+ if (!SGEP)
+ return {};
// If the SGEP has no indices, this means we have a pointer on the whole
// struct. For now, we bail out: if it was not used, it would be DCE'd, so
// there is probably a reference to the whole struct somewhere.
if (SGEP->getNumIndices() == 0)
- return false;
+ return {};
// IR rule: SGEP on struct can only use constant int as indices.
ConstantInt *Index = cast<ConstantInt>(SGEP->getIndexOperand(0));
- assert(Index->getZExtValue() < FieldUsers.size());
- FieldUsers[Index->getZExtValue()].push_back(SGEP);
+ assert(Index->getZExtValue() < Output.size());
+ Output[Index->getZExtValue()].push_back(SGEP);
}
- bool Changed = false;
- SmallPtrSet<Instruction *, 4> DeadLifetimeInstrs;
+ return Output;
+}
+
+// For each lifetime intrinsic in LifetimeIntrinsics, creates a new one, but
+// uses V as operand.
+void copyLifetimeIntrinsicFor(IRBuilder<> &B, LifetimeIntrinsic *II, Value *V) {
+ if (II->getIntrinsicID() == Intrinsic::lifetime_start) {
+ B.SetInsertPoint(II);
+ B.CreateLifetimeStart(V);
+ } else if (II->getIntrinsicID() == Intrinsic::lifetime_end) {
+ B.SetInsertPoint(II);
+ B.CreateLifetimeEnd(V);
+ } else
+ llvm_unreachable("invalid argument: expected a lifetime intrinsic");
+}
+
+void rewriteSGEPChain(IRBuilder<> &B, StructuredGEPInst *SGEP,
+ StructuredAllocaInst *FieldAlloca) {
+ if (SGEP->getNumIndices() == 1) {
+ SGEP->replaceAllUsesWith(FieldAlloca);
+ SGEP->eraseFromParent();
+ return;
+ }
+
+ SmallVector<Value *, 4> Indices;
+ for (unsigned J = 1; J < SGEP->getNumIndices(); ++J)
+ Indices.push_back(SGEP->getIndexOperand(J));
+
+ B.SetInsertPoint(SGEP);
+ auto *I = B.CreateStructuredGEP(FieldAlloca->getAllocationType(), FieldAlloca,
+ Indices, SGEP->getName());
+ SGEP->replaceAllUsesWith(I);
+ SGEP->eraseFromParent();
+}
+
+bool runOnStructuredAlloca(StructuredAllocaInst &SAI) {
+ // For now, LSROA only handles SGEP on structs.
+ StructType *ST = dyn_cast<StructType>(SAI.getAllocationType());
+ if (!ST)
+ return false;
+
+ SmallVector<LifetimeIntrinsic *> LifetimeIntrinsics =
+ collectLifetimeIntrinsicsUsing(SAI);
+ auto PerFieldSGEP = collectPerFieldSGEP(SAI);
+ if (PerFieldSGEP.size() == 0)
+ return false;
+
IRBuilder B(&SAI);
- for (size_t I = 0; I < FieldUsers.size(); ++I) {
- if (FieldUsers[I].size() == 0)
+ for (size_t I = 0; I < PerFieldSGEP.size(); ++I) {
+ auto &Users = PerFieldSGEP[I];
+ if (Users.size() == 0)
continue;
- Changed = true;
B.SetInsertPoint(&SAI);
- StructuredAllocaInst *NSAI = cast<StructuredAllocaInst>(
+ StructuredAllocaInst *FieldAlloca = cast<StructuredAllocaInst>(
B.CreateStructuredAlloca(ST->getElementType(I)));
- // Step 1: for each lifetime intrinsic, generate one per newly created NSAI.
- for (const auto &user : SAI.users()) {
- auto II = dyn_cast<IntrinsicInst>(user);
- if (II && II->getIntrinsicID() == Intrinsic::lifetime_start) {
- B.SetInsertPoint(II);
- B.CreateLifetimeStart(NSAI);
- DeadLifetimeInstrs.insert(II);
- continue;
- }
-
- if (II && II->getIntrinsicID() == Intrinsic::lifetime_end) {
- B.SetInsertPoint(II);
- B.CreateLifetimeEnd(NSAI);
- DeadLifetimeInstrs.insert(II);
- continue;
- }
- }
-
- // Step 2: replace each SGEP usage with the new alloca
- for (StructuredGEPInst *SGEP : FieldUsers[I]) {
- if (SGEP->getNumIndices() == 1) {
- SGEP->replaceAllUsesWith(NSAI);
- SGEP->eraseFromParent();
- continue;
- }
-
- SmallVector<Value *, 4> Indices;
- for (unsigned J = 1; J < SGEP->getNumIndices(); ++J)
- Indices.push_back(SGEP->getIndexOperand(J));
-
- B.SetInsertPoint(SGEP);
- StructuredGEPInst *NSGEP = cast<StructuredGEPInst>(B.CreateStructuredGEP(
- ST->getElementType(I), NSAI, Indices, SGEP->getName()));
- SGEP->replaceAllUsesWith(NSGEP);
- SGEP->eraseFromParent();
- }
+ for (auto II : LifetimeIntrinsics)
+ copyLifetimeIntrinsicFor(B, II, FieldAlloca);
+
+ for (StructuredGEPInst *SGEP : Users)
+ rewriteSGEPChain(B, SGEP, FieldAlloca);
}
- for (Instruction *I : DeadLifetimeInstrs)
- I->eraseFromParent();
+ for (auto *II : LifetimeIntrinsics)
+ II->eraseFromParent();
SAI.eraseFromParent();
-
- return Changed;
+ return true;
}
-bool LSROA::runLSROA(Function &F) {
+bool runLSROA(Function &F) {
BasicBlock &EntryBB = F.getEntryBlock();
SmallVector<StructuredAllocaInst *> Worklist;
@@ -157,8 +174,10 @@ bool LSROA::runLSROA(Function &F) {
return Changed;
}
+} // end anonymous namespace
+
PreservedAnalyses LSROAPass::run(Function &F, FunctionAnalysisManager &AM) {
- if (!LSROA().runLSROA(F))
+ if (!runLSROA(F))
return PreservedAnalyses::all();
PreservedAnalyses PA;
@@ -182,7 +201,7 @@ class LSROALegacyPass : public FunctionPass {
bool runOnFunction(Function &F) override {
if (skipFunction(F))
return false;
- return LSROA().runLSROA(F);
+ return runLSROA(F);
}
void getAnalysisUsage(AnalysisUsage &AU) const override {
>From 7e95269ca74f982aa41120c351f1424a93c405a9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <brioche at google.com>
Date: Wed, 15 Apr 2026 16:55:01 +0200
Subject: [PATCH 06/25] iterate on all BB
---
llvm/lib/Transforms/Scalar/LSROA.cpp | 11 +++++------
1 file changed, 5 insertions(+), 6 deletions(-)
diff --git a/llvm/lib/Transforms/Scalar/LSROA.cpp b/llvm/lib/Transforms/Scalar/LSROA.cpp
index 13c38f8955775..df23af45d5473 100644
--- a/llvm/lib/Transforms/Scalar/LSROA.cpp
+++ b/llvm/lib/Transforms/Scalar/LSROA.cpp
@@ -159,13 +159,12 @@ bool runOnStructuredAlloca(StructuredAllocaInst &SAI) {
}
bool runLSROA(Function &F) {
- BasicBlock &EntryBB = F.getEntryBlock();
SmallVector<StructuredAllocaInst *> Worklist;
-
- for (BasicBlock::iterator I = EntryBB.begin(), E = std::prev(EntryBB.end());
- I != E; ++I) {
- if (StructuredAllocaInst *SAI = dyn_cast<StructuredAllocaInst>(I))
- Worklist.push_back(SAI);
+ for (auto &BB : F) {
+ for (auto &I : BB) {
+ if (StructuredAllocaInst *SAI = dyn_cast<StructuredAllocaInst>(&I))
+ Worklist.push_back(SAI);
+ }
}
bool Changed = false;
>From 7097b1a4999b77e68a89f9be80c83f8e11b3cc0f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <brioche at google.com>
Date: Wed, 15 Apr 2026 16:56:56 +0200
Subject: [PATCH 07/25] use UTC generator
---
llvm/test/Transforms/LSROA/array.ll | 16 ++-
llvm/test/Transforms/LSROA/basictest.ll | 163 +++++++++++++----------
llvm/test/Transforms/LSROA/nesting.ll | 22 +--
llvm/test/Transforms/LSROA/normal-gep.ll | 22 +--
4 files changed, 126 insertions(+), 97 deletions(-)
diff --git a/llvm/test/Transforms/LSROA/array.ll b/llvm/test/Transforms/LSROA/array.ll
index cbc421bd1e1c7..f8ec94ef65623 100644
--- a/llvm/test/Transforms/LSROA/array.ll
+++ b/llvm/test/Transforms/LSROA/array.ll
@@ -1,3 +1,4 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
; RUN: opt < %s -passes='logical-sroa' -S | FileCheck %s --check-prefixes=CHECK
declare void @llvm.lifetime.start.p0(ptr nocapture)
@@ -6,8 +7,14 @@ declare ptr @llvm.structured.alloca.p0()
declare ptr @llvm.structured.gep.p0(ptr, ...)
define i32 @test_simple_array() {
-; CHECK-LABEL: @test_simple_array(
-; CHECK-NEXT: entry:
+; CHECK-LABEL: define i32 @test_simple_array() {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP:%.*]] = call elementtype([10 x i32]) ptr @llvm.structured.alloca.p0()
+; CHECK-NEXT: [[PTR:%.*]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([10 x i32]) [[TMP]], i32 0)
+; CHECK-NEXT: store i32 0, ptr [[PTR]], align 4
+; CHECK-NEXT: [[RES:%.*]] = load i32, ptr [[PTR]], align 4
+; CHECK-NEXT: ret i32 [[RES]]
+;
entry:
%tmp = call elementtype([10 x i32]) ptr @llvm.structured.alloca.p0()
%ptr = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([10 x i32]) %tmp, i32 0)
@@ -15,10 +22,5 @@ entry:
%res = load i32, ptr %ptr
ret i32 %res
-; CHECK-NEXT: %tmp = call elementtype([10 x i32]) ptr @llvm.structured.alloca.p0()
-; CHECK-NEXT: %ptr = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([10 x i32]) %tmp, i32 0)
-; CHECK-NEXT: store i32 0, ptr %ptr
-; CHECK-NEXT: %res = load i32, ptr %ptr
-; CHECK-NEXT: ret i32 %res
}
diff --git a/llvm/test/Transforms/LSROA/basictest.ll b/llvm/test/Transforms/LSROA/basictest.ll
index 8bb90220f358e..3d7f6079e954d 100644
--- a/llvm/test/Transforms/LSROA/basictest.ll
+++ b/llvm/test/Transforms/LSROA/basictest.ll
@@ -1,3 +1,4 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
; RUN: opt < %s -passes='logical-sroa' -S | FileCheck %s --check-prefixes=CHECK
declare void @llvm.lifetime.start.p0(ptr nocapture)
@@ -6,8 +7,15 @@ declare ptr @llvm.structured.alloca.p0()
declare ptr @llvm.structured.gep.p0(ptr, ...)
define i32 @test_simple_scalar() {
-; CHECK-LABEL: @test_simple_scalar(
-; CHECK-NEXT: entry:
+; CHECK-LABEL: define i32 @test_simple_scalar() {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP:%.*]] = call elementtype(i32) ptr @llvm.structured.alloca.p0()
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[TMP]])
+; CHECK-NEXT: store i32 0, ptr [[TMP]], align 4
+; CHECK-NEXT: [[RES:%.*]] = load i32, ptr [[TMP]], align 4
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[TMP]])
+; CHECK-NEXT: ret i32 [[RES]]
+;
entry:
%tmp = call elementtype(i32) ptr @llvm.structured.alloca.p0()
call void @llvm.lifetime.start.p0(ptr %tmp)
@@ -15,16 +23,24 @@ entry:
%res = load i32, ptr %tmp
call void @llvm.lifetime.end.p0(ptr %tmp)
ret i32 %res
-; CHECK: %tmp = call elementtype(i32) ptr @llvm.structured.alloca.p0()
-; CHECK: call void @llvm.lifetime.start.p0(ptr %tmp)
-; CHECK: store i32 0, ptr %tmp
-; CHECK: %res = load i32, ptr %tmp
-; CHECK: call void @llvm.lifetime.end.p0(ptr %tmp)
}
define i32 @test_simple_struct_entire_write_read() {
-; CHECK-LABEL: @test_simple_struct_entire_write_read(
-; CHECK-NEXT: entry:
+; CHECK-LABEL: define i32 @test_simple_struct_entire_write_read() {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP0:%.*]] = call elementtype(i32) ptr @llvm.structured.alloca.p0()
+; CHECK-NEXT: [[TMP1:%.*]] = call elementtype(i32) ptr @llvm.structured.alloca.p0()
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[TMP0]])
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[TMP1]])
+; CHECK-NEXT: store i32 0, ptr [[TMP0]], align 4
+; CHECK-NEXT: store i32 1, ptr [[TMP1]], align 4
+; CHECK-NEXT: [[A:%.*]] = load i32, ptr [[TMP0]], align 4
+; CHECK-NEXT: [[B:%.*]] = load i32, ptr [[TMP1]], align 4
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[TMP0]])
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[TMP1]])
+; CHECK-NEXT: [[RES:%.*]] = add i32 [[A]], [[B]]
+; CHECK-NEXT: ret i32 [[RES]]
+;
entry:
%tmp = call elementtype({ i32, i32 }) ptr @llvm.structured.alloca.p0()
%ptr0 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) %tmp, i32 0)
@@ -40,22 +56,22 @@ entry:
%res = add i32 %a, %b
ret i32 %res
-; CHECK-NEXT: %[[#a:]] = call elementtype(i32) ptr @llvm.structured.alloca.p0()
-; CHECK-NEXT: %[[#b:]] = call elementtype(i32) ptr @llvm.structured.alloca.p0()
-
-; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr %[[#a]])
-; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr %[[#b]])
-; CHECK-NEXT: store i32 0, ptr %[[#a]]
-; CHECK-NEXT: store i32 1, ptr %[[#b]]
-; CHECK-NEXT: %a = load i32, ptr %[[#a]]
-; CHECK-NEXT: %b = load i32, ptr %[[#b]]
-; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %[[#a]])
-; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %[[#b]])
+
}
define i32 @test_simple_struct_aliasing() {
-; CHECK-LABEL: @test_simple_struct_aliasing(
-; CHECK-NEXT: entry:
+; CHECK-LABEL: define i32 @test_simple_struct_aliasing() {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP0:%.*]] = call elementtype(i32) ptr @llvm.structured.alloca.p0()
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[TMP0]])
+; CHECK-NEXT: store i32 0, ptr [[TMP0]], align 4
+; CHECK-NEXT: store i32 1, ptr [[TMP0]], align 4
+; CHECK-NEXT: [[A:%.*]] = load i32, ptr [[TMP0]], align 4
+; CHECK-NEXT: [[B:%.*]] = load i32, ptr [[TMP0]], align 4
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[TMP0]])
+; CHECK-NEXT: [[RES:%.*]] = add i32 [[A]], [[B]]
+; CHECK-NEXT: ret i32 [[RES]]
+;
entry:
%tmp = call elementtype({ i32, i32 }) ptr @llvm.structured.alloca.p0()
%ptr0 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) %tmp, i32 0)
@@ -71,19 +87,19 @@ entry:
%res = add i32 %a, %b
ret i32 %res
-; CHECK-NEXT: %[[#a:]] = call elementtype(i32) ptr @llvm.structured.alloca.p0()
-; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr %[[#a]])
-; CHECK-NEXT: store i32 0, ptr %[[#a]]
-; CHECK-NEXT: store i32 1, ptr %[[#a]]
-; CHECK-NEXT: %a = load i32, ptr %[[#a]]
-; CHECK-NEXT: %b = load i32, ptr %[[#a]]
-; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %[[#a]])
}
define i32 @test_simple_struct_partial_write_read() {
-; CHECK-LABEL: @test_simple_struct_partial_write_read(
-; CHECK-NEXT: entry:
+; CHECK-LABEL: define i32 @test_simple_struct_partial_write_read() {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP0:%.*]] = call elementtype(i32) ptr @llvm.structured.alloca.p0()
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[TMP0]])
+; CHECK-NEXT: store i32 0, ptr [[TMP0]], align 4
+; CHECK-NEXT: [[A:%.*]] = load i32, ptr [[TMP0]], align 4
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[TMP0]])
+; CHECK-NEXT: ret i32 [[A]]
+;
entry:
%tmp = call elementtype({ i32, i32 }) ptr @llvm.structured.alloca.p0()
call void @llvm.lifetime.start.p0(ptr %tmp)
@@ -92,58 +108,77 @@ entry:
%a = load i32, ptr %ptr0
call void @llvm.lifetime.end.p0(ptr %tmp)
-; CHECK-NEXT: %[[#a:]] = call elementtype(i32) ptr @llvm.structured.alloca.p0()
-; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr %[[#a]])
-; CHECK-NEXT: store i32 0, ptr %[[#a]]
-; CHECK-NEXT: %a = load i32, ptr %[[#a]]
-; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %[[#a]])
ret i32 %a
}
define i32 @test_struct_use_across_lifetime() {
-; CHECK-LABEL: @test_struct_use_across_lifetime(
-; CHECK-NEXT: entry:
+; CHECK-LABEL: define i32 @test_struct_use_across_lifetime() {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP0:%.*]] = call elementtype(i32) ptr @llvm.structured.alloca.p0()
+; CHECK-NEXT: [[TMP1:%.*]] = call elementtype(i32) ptr @llvm.structured.alloca.p0()
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[TMP0]])
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[TMP1]])
+; CHECK-NEXT: store i32 0, ptr [[TMP0]], align 4
+; CHECK-NEXT: [[A:%.*]] = load i32, ptr [[TMP0]], align 4
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[TMP0]])
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[TMP1]])
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[TMP0]])
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[TMP1]])
+; CHECK-NEXT: store i32 0, ptr [[TMP1]], align 4
+; CHECK-NEXT: [[B:%.*]] = load i32, ptr [[TMP1]], align 4
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[TMP0]])
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[TMP1]])
+; CHECK-NEXT: [[C:%.*]] = add i32 [[B]], [[A]]
+; CHECK-NEXT: ret i32 [[C]]
+;
entry:
%tmp = call elementtype({ i32, i32 }) ptr @llvm.structured.alloca.p0()
%ptr0 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) %tmp, i32 0)
%ptr1 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) %tmp, i32 1)
-; CHECK-NEXT: %[[#a:]] = call elementtype(i32) ptr @llvm.structured.alloca.p0()
-; CHECK-NEXT: %[[#b:]] = call elementtype(i32) ptr @llvm.structured.alloca.p0()
call void @llvm.lifetime.start.p0(ptr %tmp)
store i32 0, ptr %ptr0
%a = load i32, ptr %ptr0
call void @llvm.lifetime.end.p0(ptr %tmp)
-; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr %[[#a]])
-; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr %[[#b]])
-; CHECK-NEXT: store i32 0, ptr %[[#a]]
-; CHECK-NEXT: %a = load i32, ptr %[[#a]]
-; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %[[#a]])
-; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %[[#b]])
call void @llvm.lifetime.start.p0(ptr %tmp)
store i32 0, ptr %ptr1
%b = load i32, ptr %ptr1
call void @llvm.lifetime.end.p0(ptr %tmp)
-; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr %[[#a]])
-; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr %[[#b]])
-; CHECK-NEXT: store i32 0, ptr %[[#b]]
-; CHECK-NEXT: %b = load i32, ptr %[[#b]]
-; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %[[#a]])
-; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %[[#b]])
%c = add i32 %b, %a
ret i32 %c
}
define i32 @test_partial_use_phi_node(i1 %cond) {
-; CHECK-LABEL: @test_partial_use_phi_node(
-; CHECK-NEXT: entry:
+; CHECK-LABEL: define i32 @test_partial_use_phi_node(
+; CHECK-SAME: i1 [[COND:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP0:%.*]] = call elementtype(i32) ptr @llvm.structured.alloca.p0()
+; CHECK-NEXT: [[TMP1:%.*]] = call elementtype(i32) ptr @llvm.structured.alloca.p0()
+; CHECK-NEXT: br i1 [[COND]], label %[[L1:.*]], label %[[L2:.*]]
+; CHECK: [[L1]]:
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[TMP0]])
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[TMP1]])
+; CHECK-NEXT: store i32 0, ptr [[TMP0]], align 4
+; CHECK-NEXT: br label %[[L3:.*]]
+; CHECK: [[L2]]:
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[TMP0]])
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[TMP1]])
+; CHECK-NEXT: store i32 1, ptr [[TMP1]], align 4
+; CHECK-NEXT: br label %[[L3]]
+; CHECK: [[L3]]:
+; CHECK-NEXT: [[PTR:%.*]] = phi ptr [ [[TMP0]], %[[L1]] ], [ [[TMP1]], %[[L2]] ]
+; CHECK-NEXT: [[A:%.*]] = load i32, ptr [[PTR]], align 4
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[TMP0]])
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[TMP1]])
+; CHECK-NEXT: br label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret i32 [[A]]
+;
entry:
%tmp = call elementtype({ i32, i32 }) ptr @llvm.structured.alloca.p0()
-; CHECK-NEXT: %[[#a:]] = call elementtype(i32) ptr @llvm.structured.alloca.p0()
-; CHECK-NEXT: %[[#b:]] = call elementtype(i32) ptr @llvm.structured.alloca.p0()
%ptr0 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) %tmp, i32 0)
%ptr1 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) %tmp, i32 1)
br i1 %cond, label %l1, label %l2
@@ -152,33 +187,17 @@ l1:
call void @llvm.lifetime.start.p0(ptr %tmp)
store i32 0, ptr %ptr0
br label %l3
-; CHECK: l1: ; preds = %entry
-; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr %[[#a]])
-; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr %[[#b]])
-; CHECK-NEXT: store i32 0, ptr %[[#a]]
-; CHECK-NEXT: br label %l3
l2:
call void @llvm.lifetime.start.p0(ptr %tmp)
store i32 1, ptr %ptr1
br label %l3
-; CHECK: l2: ; preds = %entry
-; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr %[[#a]])
-; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr %[[#b]])
-; CHECK-NEXT: store i32 1, ptr %[[#b]]
-; CHECK-NEXT: br label %l3
l3:
%ptr = phi ptr [ %ptr0, %l1 ], [ %ptr1, %l2 ]
%a = load i32, ptr %ptr
call void @llvm.lifetime.end.p0(ptr %tmp)
br label %exit
-; CHECK: l3: ; preds = %l2, %l1
-; CHECK-NEXT: %ptr = phi ptr [ %[[#a]], %l1 ], [ %[[#b]], %l2 ]
-; CHECK-NEXT: %a = load i32, ptr %ptr
-; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %[[#a]])
-; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %[[#b]])
-; CHECK-NEXT: br label %exit
exit:
ret i32 %a
diff --git a/llvm/test/Transforms/LSROA/nesting.ll b/llvm/test/Transforms/LSROA/nesting.ll
index 55d4d6283ce11..c93e86ba30d1f 100644
--- a/llvm/test/Transforms/LSROA/nesting.ll
+++ b/llvm/test/Transforms/LSROA/nesting.ll
@@ -1,3 +1,4 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
; RUN: opt < %s -passes='logical-sroa' -S | FileCheck %s --check-prefixes=CHECK
declare void @llvm.lifetime.start.p0(ptr nocapture)
@@ -8,25 +9,28 @@ declare ptr @llvm.structured.gep.p0(ptr, ...)
%S = type { i32, { i32, i32 } }
define i32 @test_nested_struct() {
-; CHECK-LABEL: @test_nested_struct(
-; CHECK-NEXT: entry:
+; CHECK-LABEL: define i32 @test_nested_struct() {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP0:%.*]] = call elementtype(i32) ptr @llvm.structured.alloca.p0()
+; CHECK-NEXT: [[TMP1:%.*]] = call elementtype({ i32, i32 }) ptr @llvm.structured.alloca.p0()
+; CHECK-NEXT: [[TMP2:%.*]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) [[TMP1]], i32 0)
+; CHECK-NEXT: store i32 0, ptr [[TMP0]], align 4
+; CHECK-NEXT: store i32 1, ptr [[TMP2]], align 4
+; CHECK-NEXT: [[A:%.*]] = load i32, ptr [[TMP0]], align 4
+; CHECK-NEXT: [[B:%.*]] = load i32, ptr [[TMP2]], align 4
+; CHECK-NEXT: [[C:%.*]] = add i32 [[A]], [[B]]
+; CHECK-NEXT: ret i32 [[C]]
+;
entry:
%tmp = call elementtype(%S) ptr @llvm.structured.alloca.p0()
%0 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%S) %tmp, i32 0)
%1 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%S) %tmp, i32 1, i32 0)
-; CHECK: %[[#a:]] = call elementtype(i32) ptr @llvm.structured.alloca.p0()
-; CHECK: %[[#b:]] = call elementtype({ i32, i32 }) ptr @llvm.structured.alloca.p0()
-; CHECK: %[[#ptr:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) %[[#b]], i32 0)
store i32 0, ptr %0
store i32 1, ptr %1
%a = load i32, ptr %0
%b = load i32, ptr %1
-; CHECK: store i32 0, ptr %[[#a]]
-; CHECK: store i32 1, ptr %[[#ptr]]
-; CHECK: %a = load i32, ptr %[[#a]]
-; CHECK: %b = load i32, ptr %[[#ptr]]
%c = add i32 %a, %b
ret i32 %c
diff --git a/llvm/test/Transforms/LSROA/normal-gep.ll b/llvm/test/Transforms/LSROA/normal-gep.ll
index 35f5447afb04d..4358d9790c2bd 100644
--- a/llvm/test/Transforms/LSROA/normal-gep.ll
+++ b/llvm/test/Transforms/LSROA/normal-gep.ll
@@ -1,24 +1,28 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
; RUN: opt < %s -passes='logical-sroa' -S | FileCheck %s --check-prefixes=CHECK
define i32 @test_normal_gep() {
-; CHECK-LABEL: @test_normal_gep(
-; CHECK-NEXT: entry:
+; CHECK-LABEL: define i32 @test_normal_gep() {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP:%.*]] = call elementtype({ i32, i32 }) ptr @llvm.structured.alloca.p0()
+; CHECK-NEXT: [[TMP0:%.*]] = getelementptr i8, ptr [[TMP]], i32 0
+; CHECK-NEXT: [[TMP1:%.*]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) [[TMP]], i32 1)
+; CHECK-NEXT: store i32 0, ptr [[TMP0]], align 4
+; CHECK-NEXT: store i32 1, ptr [[TMP1]], align 4
+; CHECK-NEXT: [[A:%.*]] = load i32, ptr [[TMP0]], align 4
+; CHECK-NEXT: [[B:%.*]] = load i32, ptr [[TMP1]], align 4
+; CHECK-NEXT: [[C:%.*]] = add i32 [[A]], [[B]]
+; CHECK-NEXT: ret i32 [[C]]
+;
entry:
%tmp = call elementtype({ i32, i32 }) ptr @llvm.structured.alloca.p0()
%0 = getelementptr i8, ptr %tmp, i32 0
%1 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) %tmp, i32 1)
-; CHECK: %tmp = call elementtype({ i32, i32 }) ptr @llvm.structured.alloca.p0()
-; CHECK: %[[#a:]] = getelementptr i8, ptr %tmp, i32 0
-; CHECK: %[[#b:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) %tmp, i32 1)
store i32 0, ptr %0
store i32 1, ptr %1
%a = load i32, ptr %0
%b = load i32, ptr %1
-; CHECK: store i32 0, ptr %[[#a]]
-; CHECK: store i32 1, ptr %[[#b]]
-; CHECK: %a = load i32, ptr %[[#a]]
-; CHECK: %b = load i32, ptr %[[#b]]
%c = add i32 %a, %b
ret i32 %c
>From 241a37c719502e266535a4374388cdd24ea619a9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <brioche at google.com>
Date: Wed, 15 Apr 2026 16:58:33 +0200
Subject: [PATCH 08/25] style nits
---
llvm/include/llvm/LinkAllPasses.h | 2 +-
llvm/include/llvm/Transforms/Scalar/LSROA.h | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/llvm/include/llvm/LinkAllPasses.h b/llvm/include/llvm/LinkAllPasses.h
index 00d56e0cb903c..e2edb8ee3eee9 100644
--- a/llvm/include/llvm/LinkAllPasses.h
+++ b/llvm/include/llvm/LinkAllPasses.h
@@ -103,6 +103,7 @@ struct ForcePassLinking {
(void)llvm::createLowerGlobalDtorsLegacyPass();
(void)llvm::createLowerInvokePass();
(void)llvm::createLowerSwitchPass();
+ (void)llvm::createLSROAPass();
(void)llvm::createNaryReassociatePass();
(void)llvm::createObjCARCContractPass();
(void)llvm::createPromoteMemoryToRegisterPass();
@@ -119,7 +120,6 @@ struct ForcePassLinking {
(void)llvm::createRegionViewerPass();
(void)llvm::createSafeStackPass();
(void)llvm::createSROAPass();
- (void)llvm::createLSROAPass();
(void)llvm::createSingleLoopExtractorPass();
(void)llvm::createTailCallEliminationPass();
(void)llvm::createConstantHoistingPass();
diff --git a/llvm/include/llvm/Transforms/Scalar/LSROA.h b/llvm/include/llvm/Transforms/Scalar/LSROA.h
index 9c730a78489e0..d524f864a4637 100644
--- a/llvm/include/llvm/Transforms/Scalar/LSROA.h
+++ b/llvm/include/llvm/Transforms/Scalar/LSROA.h
@@ -1,4 +1,4 @@
-//===- LSROA.h - Logical Scalar Replacement Of Aggregates -------*- C++ -*-===//
+//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
>From 4f4d4dd5104e1efc2468b48675316369ee65b1de Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <brioche at google.com>
Date: Fri, 17 Apr 2026 11:44:11 +0200
Subject: [PATCH 09/25] pr-feedback
---
llvm/include/llvm/IR/IntrinsicInst.h | 4 +++
llvm/include/llvm/Transforms/Scalar.h | 2 +-
llvm/lib/Transforms/Scalar/LSROA.cpp | 38 +++++++++------------------
llvm/test/Transforms/LSROA/array.ll | 1 -
4 files changed, 17 insertions(+), 28 deletions(-)
diff --git a/llvm/include/llvm/IR/IntrinsicInst.h b/llvm/include/llvm/IR/IntrinsicInst.h
index 78a0cd569d5bd..268223935bb52 100644
--- a/llvm/include/llvm/IR/IntrinsicInst.h
+++ b/llvm/include/llvm/IR/IntrinsicInst.h
@@ -1846,6 +1846,10 @@ class StructuredGEPInst : public IntrinsicInst {
return getOperand(Index + 1);
}
+ inline iterator_range<op_iterator> indices() {
+ return make_range(op_begin() + 1, op_begin() + 1 + getNumIndices());
+ }
+
Type *getResultElementType() const {
Type *CurrentType = getBaseType();
for (unsigned I = 0; I < getNumIndices(); I++) {
diff --git a/llvm/include/llvm/Transforms/Scalar.h b/llvm/include/llvm/Transforms/Scalar.h
index 9d53dba325652..c7ab762a16675 100644
--- a/llvm/include/llvm/Transforms/Scalar.h
+++ b/llvm/include/llvm/Transforms/Scalar.h
@@ -49,7 +49,7 @@ LLVM_ABI FunctionPass *createSROAPass(bool PreserveCFG = true);
//===----------------------------------------------------------------------===//
//
// LSROA - Replace aggregates or pieces of aggregates with scalar SSA values
-// when used when logical allocation/GEP instructions.
+// when used with logical allocation/GEP instructions.
//
LLVM_ABI FunctionPass *createLSROAPass();
diff --git a/llvm/lib/Transforms/Scalar/LSROA.cpp b/llvm/lib/Transforms/Scalar/LSROA.cpp
index df23af45d5473..b719106f785d2 100644
--- a/llvm/lib/Transforms/Scalar/LSROA.cpp
+++ b/llvm/lib/Transforms/Scalar/LSROA.cpp
@@ -7,24 +7,16 @@
//===----------------------------------------------------------------------===//
/// \file
/// This transformation implements the well known scalar replacement of
-/// aggregates transformation. It tries to identify promotable elements of an
-/// aggregate alloca, and promote them to registers. It will also try to
-/// convert uses of an element (or set of elements) of an alloca into a vector
-/// or bitfield-style integer scalar if appropriate.
+/// aggregates transformation but for logical pointers.
+/// It tries to identify promotable elements of an aggregate alloca, and
+/// promote them to multiple allocas of scalar type.
///
-/// It works to do this with minimal slicing of the alloca so that regions
-/// which are merely transferred in and out of external memory remain unchanged
-/// and are not decomposed to scalar code.
-///
-/// Because this also performs alloca promotion, it can be thought of as also
-/// serving the purpose of SSA formation. The algorithm iterates on the
-/// function until all opportunities for promotion have been realized.
+/// FIXME: nested aggregates are not fully optimized (#192619).
+/// FIXME: array are not optimized (#192620).
///
//===----------------------------------------------------------------------===//
#include "llvm/Transforms/Scalar/LSROA.h"
-#include "llvm/ADT/SmallPtrSet.h"
-#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Analysis/DomTreeUpdater.h"
#include "llvm/IR/IRBuilder.h"
@@ -45,8 +37,8 @@ SmallVector<LifetimeIntrinsic *>
collectLifetimeIntrinsicsUsing(Instruction &I) {
SmallVector<LifetimeIntrinsic *> Output;
- for (const auto &user : I.users()) {
- auto II = dyn_cast<IntrinsicInst>(user);
+ for (User *U : I.users()) {
+ auto II = dyn_cast<IntrinsicInst>(U);
if (II && isLifetimeIntrinsic(II->getIntrinsicID()))
Output.push_back(cast<LifetimeIntrinsic>(II));
}
@@ -54,8 +46,6 @@ collectLifetimeIntrinsicsUsing(Instruction &I) {
return Output;
}
-using SGEPVec = SmallVector<StructuredGEPInst *>;
-
// Returns a vector with one element for each field of the struct allocated by
// SAI. Each element is a vector of SGEP instruction referencing this field.
//
@@ -67,12 +57,12 @@ collectPerFieldSGEP(StructuredAllocaInst &SAI) {
StructType *ST = cast<StructType>(SAI.getAllocationType());
SmallVector<SmallVector<StructuredGEPInst *>> Output(ST->getNumElements());
- for (const auto &user : SAI.users()) {
- auto II = dyn_cast<IntrinsicInst>(user);
+ for (User *U : SAI.users()) {
+ auto II = dyn_cast<IntrinsicInst>(U);
if (II && II->isLifetimeStartOrEnd())
continue;
- auto SGEP = dyn_cast<StructuredGEPInst>(user);
+ auto SGEP = dyn_cast<StructuredGEPInst>(U);
if (!SGEP)
return {};
@@ -112,10 +102,7 @@ void rewriteSGEPChain(IRBuilder<> &B, StructuredGEPInst *SGEP,
return;
}
- SmallVector<Value *, 4> Indices;
- for (unsigned J = 1; J < SGEP->getNumIndices(); ++J)
- Indices.push_back(SGEP->getIndexOperand(J));
-
+ SmallVector<Value *, 4> Indices(llvm::drop_begin(SGEP->indices()));
B.SetInsertPoint(SGEP);
auto *I = B.CreateStructuredGEP(FieldAlloca->getAllocationType(), FieldAlloca,
Indices, SGEP->getName());
@@ -129,12 +116,11 @@ bool runOnStructuredAlloca(StructuredAllocaInst &SAI) {
if (!ST)
return false;
- SmallVector<LifetimeIntrinsic *> LifetimeIntrinsics =
- collectLifetimeIntrinsicsUsing(SAI);
auto PerFieldSGEP = collectPerFieldSGEP(SAI);
if (PerFieldSGEP.size() == 0)
return false;
+ auto LifetimeIntrinsics = collectLifetimeIntrinsicsUsing(SAI);
IRBuilder B(&SAI);
for (size_t I = 0; I < PerFieldSGEP.size(); ++I) {
auto &Users = PerFieldSGEP[I];
diff --git a/llvm/test/Transforms/LSROA/array.ll b/llvm/test/Transforms/LSROA/array.ll
index f8ec94ef65623..b808da827f932 100644
--- a/llvm/test/Transforms/LSROA/array.ll
+++ b/llvm/test/Transforms/LSROA/array.ll
@@ -23,4 +23,3 @@ entry:
ret i32 %res
}
-
>From 1388bb9c320feb702fd39601b7aec58326711de5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <brioche at google.com>
Date: Fri, 17 Apr 2026 17:38:45 +0200
Subject: [PATCH 10/25] pr-feedback
---
llvm/lib/Transforms/Scalar/LSROA.cpp | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/llvm/lib/Transforms/Scalar/LSROA.cpp b/llvm/lib/Transforms/Scalar/LSROA.cpp
index b719106f785d2..1e88425ed5784 100644
--- a/llvm/lib/Transforms/Scalar/LSROA.cpp
+++ b/llvm/lib/Transforms/Scalar/LSROA.cpp
@@ -117,19 +117,18 @@ bool runOnStructuredAlloca(StructuredAllocaInst &SAI) {
return false;
auto PerFieldSGEP = collectPerFieldSGEP(SAI);
- if (PerFieldSGEP.size() == 0)
+ if (PerFieldSGEP.empty())
return false;
auto LifetimeIntrinsics = collectLifetimeIntrinsicsUsing(SAI);
IRBuilder B(&SAI);
- for (size_t I = 0; I < PerFieldSGEP.size(); ++I) {
- auto &Users = PerFieldSGEP[I];
- if (Users.size() == 0)
+ for (const auto &[FieldIndex, Users] : llvm::enumerate(PerFieldSGEP)) {
+ if (Users.empty())
continue;
B.SetInsertPoint(&SAI);
StructuredAllocaInst *FieldAlloca = cast<StructuredAllocaInst>(
- B.CreateStructuredAlloca(ST->getElementType(I)));
+ B.CreateStructuredAlloca(ST->getElementType(FieldIndex)));
for (auto II : LifetimeIntrinsics)
copyLifetimeIntrinsicFor(B, II, FieldAlloca);
>From 40f5e6bf863f56c999ee876114fbd343fec30cac Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <brioche at google.com>
Date: Fri, 17 Apr 2026 17:40:06 +0200
Subject: [PATCH 11/25] regroupe insert point
---
llvm/lib/Transforms/Scalar/LSROA.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/llvm/lib/Transforms/Scalar/LSROA.cpp b/llvm/lib/Transforms/Scalar/LSROA.cpp
index 1e88425ed5784..2801a53e606ba 100644
--- a/llvm/lib/Transforms/Scalar/LSROA.cpp
+++ b/llvm/lib/Transforms/Scalar/LSROA.cpp
@@ -84,11 +84,11 @@ collectPerFieldSGEP(StructuredAllocaInst &SAI) {
// For each lifetime intrinsic in LifetimeIntrinsics, creates a new one, but
// uses V as operand.
void copyLifetimeIntrinsicFor(IRBuilder<> &B, LifetimeIntrinsic *II, Value *V) {
+ B.SetInsertPoint(II);
+
if (II->getIntrinsicID() == Intrinsic::lifetime_start) {
- B.SetInsertPoint(II);
B.CreateLifetimeStart(V);
} else if (II->getIntrinsicID() == Intrinsic::lifetime_end) {
- B.SetInsertPoint(II);
B.CreateLifetimeEnd(V);
} else
llvm_unreachable("invalid argument: expected a lifetime intrinsic");
>From 23c91bc64b56974e30ca6c6ed53d814bc9cd5724 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <brioche at google.com>
Date: Tue, 21 Apr 2026 16:35:51 +0200
Subject: [PATCH 12/25] rename LSROA -> LogicalSROA
---
llvm/include/llvm/InitializePasses.h | 2 +-
llvm/include/llvm/LinkAllPasses.h | 2 +-
llvm/include/llvm/Transforms/Scalar.h | 6 ++--
.../Scalar/{LSROA.h => LogicalSROA.h} | 10 +++---
llvm/lib/Passes/PassBuilder.cpp | 2 +-
llvm/lib/Passes/PassRegistry.def | 2 +-
llvm/lib/Transforms/Scalar/CMakeLists.txt | 2 +-
.../Scalar/{LSROA.cpp => LogicalSROA.cpp} | 35 ++++++++++---------
8 files changed, 32 insertions(+), 29 deletions(-)
rename llvm/include/llvm/Transforms/Scalar/{LSROA.h => LogicalSROA.h} (80%)
rename llvm/lib/Transforms/Scalar/{LSROA.cpp => LogicalSROA.cpp} (86%)
diff --git a/llvm/include/llvm/InitializePasses.h b/llvm/include/llvm/InitializePasses.h
index a81c80d1a0cc3..0cc7756be2c62 100644
--- a/llvm/include/llvm/InitializePasses.h
+++ b/llvm/include/llvm/InitializePasses.h
@@ -174,6 +174,7 @@ LLVM_ABI void initializeLoadStoreOptPass(PassRegistry &);
LLVM_ABI void initializeLoadStoreVectorizerLegacyPassPass(PassRegistry &);
LLVM_ABI void initializeLocalStackSlotPassPass(PassRegistry &);
LLVM_ABI void initializeLocalizerPass(PassRegistry &);
+LLVM_ABI void initializeLogicalSROALegacyPassPass(PassRegistry &);
LLVM_ABI void initializeLoopDataPrefetchLegacyPassPass(PassRegistry &);
LLVM_ABI void initializeLoopExtractorLegacyPassPass(PassRegistry &);
LLVM_ABI void initializeLoopInfoWrapperPassPass(PassRegistry &);
@@ -294,7 +295,6 @@ LLVM_ABI void initializeResetMachineFunctionPass(PassRegistry &);
LLVM_ABI void initializeRuntimeLibraryInfoWrapperPass(PassRegistry &);
LLVM_ABI void initializeSCEVAAWrapperPassPass(PassRegistry &);
LLVM_ABI void initializeSROALegacyPassPass(PassRegistry &);
-LLVM_ABI void initializeLSROALegacyPassPass(PassRegistry &);
LLVM_ABI void initializeSafeStackLegacyPassPass(PassRegistry &);
LLVM_ABI void initializeSafepointIRVerifierPass(PassRegistry &);
LLVM_ABI void initializeSelectOptimizePass(PassRegistry &);
diff --git a/llvm/include/llvm/LinkAllPasses.h b/llvm/include/llvm/LinkAllPasses.h
index e2edb8ee3eee9..d54803f60736a 100644
--- a/llvm/include/llvm/LinkAllPasses.h
+++ b/llvm/include/llvm/LinkAllPasses.h
@@ -95,6 +95,7 @@ struct ForcePassLinking {
(void)llvm::createLCSSAPass();
(void)llvm::createLICMPass();
(void)llvm::createLazyValueInfoPass();
+ (void)llvm::createLogicalSROAPass();
(void)llvm::createLoopExtractorPass();
(void)llvm::createLoopSimplifyPass();
(void)llvm::createLoopStrengthReducePass();
@@ -103,7 +104,6 @@ struct ForcePassLinking {
(void)llvm::createLowerGlobalDtorsLegacyPass();
(void)llvm::createLowerInvokePass();
(void)llvm::createLowerSwitchPass();
- (void)llvm::createLSROAPass();
(void)llvm::createNaryReassociatePass();
(void)llvm::createObjCARCContractPass();
(void)llvm::createPromoteMemoryToRegisterPass();
diff --git a/llvm/include/llvm/Transforms/Scalar.h b/llvm/include/llvm/Transforms/Scalar.h
index c7ab762a16675..5ba47853076b6 100644
--- a/llvm/include/llvm/Transforms/Scalar.h
+++ b/llvm/include/llvm/Transforms/Scalar.h
@@ -48,10 +48,10 @@ LLVM_ABI FunctionPass *createSROAPass(bool PreserveCFG = true);
//===----------------------------------------------------------------------===//
//
-// LSROA - Replace aggregates or pieces of aggregates with scalar SSA values
-// when used with logical allocation/GEP instructions.
+// LogicalSROA - Replace aggregates or pieces of aggregates with scalar SSA
+// values when used with logical allocation/GEP instructions.
//
-LLVM_ABI FunctionPass *createLSROAPass();
+LLVM_ABI FunctionPass *createLogicalSROAPass();
//===----------------------------------------------------------------------===//
//
diff --git a/llvm/include/llvm/Transforms/Scalar/LSROA.h b/llvm/include/llvm/Transforms/Scalar/LogicalSROA.h
similarity index 80%
rename from llvm/include/llvm/Transforms/Scalar/LSROA.h
rename to llvm/include/llvm/Transforms/Scalar/LogicalSROA.h
index d524f864a4637..786d8911e639f 100644
--- a/llvm/include/llvm/Transforms/Scalar/LSROA.h
+++ b/llvm/include/llvm/Transforms/Scalar/LogicalSROA.h
@@ -13,8 +13,8 @@
///
//===----------------------------------------------------------------------===//
-#ifndef LLVM_TRANSFORMS_SCALAR_LSROA_H
-#define LLVM_TRANSFORMS_SCALAR_LSROA_H
+#ifndef LLVM_TRANSFORMS_SCALAR_LOGICALSROA_H
+#define LLVM_TRANSFORMS_SCALAR_LOGICALSROA_H
#include "llvm/IR/PassManager.h"
@@ -22,9 +22,9 @@ namespace llvm {
class Function;
-class LSROAPass : public PassInfoMixin<LSROAPass> {
+class LogicalSROAPass : public PassInfoMixin<LogicalSROAPass> {
public:
- LSROAPass();
+ LogicalSROAPass();
/// Run the pass over the function.
PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
@@ -32,4 +32,4 @@ class LSROAPass : public PassInfoMixin<LSROAPass> {
} // end namespace llvm
-#endif // LLVM_TRANSFORMS_SCALAR_LSROA_H
+#endif // LLVM_TRANSFORMS_SCALAR_LOGICALSROA_H
diff --git a/llvm/lib/Passes/PassBuilder.cpp b/llvm/lib/Passes/PassBuilder.cpp
index 25482096c5115..bf3db1ebceb83 100644
--- a/llvm/lib/Passes/PassBuilder.cpp
+++ b/llvm/lib/Passes/PassBuilder.cpp
@@ -294,7 +294,7 @@
#include "llvm/Transforms/Scalar/JumpTableToSwitch.h"
#include "llvm/Transforms/Scalar/JumpThreading.h"
#include "llvm/Transforms/Scalar/LICM.h"
-#include "llvm/Transforms/Scalar/LSROA.h"
+#include "llvm/Transforms/Scalar/LogicalSROA.h"
#include "llvm/Transforms/Scalar/LoopAccessAnalysisPrinter.h"
#include "llvm/Transforms/Scalar/LoopBoundSplit.h"
#include "llvm/Transforms/Scalar/LoopDataPrefetch.h"
diff --git a/llvm/lib/Passes/PassRegistry.def b/llvm/lib/Passes/PassRegistry.def
index 04d29da8bd47a..88e4e4f1ba47d 100644
--- a/llvm/lib/Passes/PassRegistry.def
+++ b/llvm/lib/Passes/PassRegistry.def
@@ -469,7 +469,7 @@ FUNCTION_PASS("kernel-info", KernelInfoPrinter(TM))
FUNCTION_PASS("lcssa", LCSSAPass())
FUNCTION_PASS("libcalls-shrinkwrap", LibCallsShrinkWrapPass())
FUNCTION_PASS("load-store-vectorizer", LoadStoreVectorizerPass())
-FUNCTION_PASS("logical-sroa", LSROAPass())
+FUNCTION_PASS("logical-sroa", LogicalSROAPass())
FUNCTION_PASS("loop-data-prefetch", LoopDataPrefetchPass())
FUNCTION_PASS("loop-distribute", LoopDistributePass())
FUNCTION_PASS("loop-fusion", LoopFusePass())
diff --git a/llvm/lib/Transforms/Scalar/CMakeLists.txt b/llvm/lib/Transforms/Scalar/CMakeLists.txt
index f631e60b01380..c92fd202af968 100644
--- a/llvm/lib/Transforms/Scalar/CMakeLists.txt
+++ b/llvm/lib/Transforms/Scalar/CMakeLists.txt
@@ -29,6 +29,7 @@ add_llvm_component_library(LLVMScalarOpts
JumpThreading.cpp
JumpTableToSwitch.cpp
LICM.cpp
+ LogicalSROA.cpp
LoopAccessAnalysisPrinter.cpp
LoopBoundSplit.cpp
LoopSink.cpp
@@ -69,7 +70,6 @@ add_llvm_component_library(LLVMScalarOpts
RewriteStatepointsForGC.cpp
SCCP.cpp
SROA.cpp
- LSROA.cpp
Scalar.cpp
Scalarizer.cpp
ScalarizeMaskedMemIntrin.cpp
diff --git a/llvm/lib/Transforms/Scalar/LSROA.cpp b/llvm/lib/Transforms/Scalar/LogicalSROA.cpp
similarity index 86%
rename from llvm/lib/Transforms/Scalar/LSROA.cpp
rename to llvm/lib/Transforms/Scalar/LogicalSROA.cpp
index 2801a53e606ba..e42612bccadd4 100644
--- a/llvm/lib/Transforms/Scalar/LSROA.cpp
+++ b/llvm/lib/Transforms/Scalar/LogicalSROA.cpp
@@ -16,7 +16,7 @@
///
//===----------------------------------------------------------------------===//
-#include "llvm/Transforms/Scalar/LSROA.h"
+#include "llvm/Transforms/Scalar/LogicalSROA.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Analysis/DomTreeUpdater.h"
#include "llvm/IR/IRBuilder.h"
@@ -111,7 +111,7 @@ void rewriteSGEPChain(IRBuilder<> &B, StructuredGEPInst *SGEP,
}
bool runOnStructuredAlloca(StructuredAllocaInst &SAI) {
- // For now, LSROA only handles SGEP on structs.
+ // For now, LogicalSROA only handles SGEP on structs.
StructType *ST = dyn_cast<StructType>(SAI.getAllocationType());
if (!ST)
return false;
@@ -143,7 +143,7 @@ bool runOnStructuredAlloca(StructuredAllocaInst &SAI) {
return true;
}
-bool runLSROA(Function &F) {
+bool runLogicalSROA(Function &F) {
SmallVector<StructuredAllocaInst *> Worklist;
for (auto &BB : F) {
for (auto &I : BB) {
@@ -160,8 +160,9 @@ bool runLSROA(Function &F) {
} // end anonymous namespace
-PreservedAnalyses LSROAPass::run(Function &F, FunctionAnalysisManager &AM) {
- if (!runLSROA(F))
+PreservedAnalyses LogicalSROAPass::run(Function &F,
+ FunctionAnalysisManager &AM) {
+ if (!runLogicalSROA(F))
return PreservedAnalyses::all();
PreservedAnalyses PA;
@@ -169,39 +170,41 @@ PreservedAnalyses LSROAPass::run(Function &F, FunctionAnalysisManager &AM) {
return PA;
}
-LSROAPass::LSROAPass() {}
+LogicalSROAPass::LogicalSROAPass() {}
namespace {
-/// A legacy pass for the legacy pass manager that wraps the LSROA pass.
-class LSROALegacyPass : public FunctionPass {
+/// A legacy pass for the legacy pass manager that wraps the LogicalSROA pass.
+class LogicalSROALegacyPass : public FunctionPass {
public:
static char ID;
- LSROALegacyPass() : FunctionPass(ID) {
- initializeLSROALegacyPassPass(*PassRegistry::getPassRegistry());
+ LogicalSROALegacyPass() : FunctionPass(ID) {
+ initializeLogicalSROALegacyPassPass(*PassRegistry::getPassRegistry());
}
bool runOnFunction(Function &F) override {
if (skipFunction(F))
return false;
- return runLSROA(F);
+ return runLogicalSROA(F);
}
void getAnalysisUsage(AnalysisUsage &AU) const override {
AU.addPreserved<DominatorTreeWrapperPass>();
}
- StringRef getPassName() const override { return "LSROA"; }
+ StringRef getPassName() const override { return "LogicalSROA"; }
};
} // end anonymous namespace
-char LSROALegacyPass::ID = 0;
+char LogicalSROALegacyPass::ID = 0;
-FunctionPass *llvm::createLSROAPass() { return new LSROALegacyPass(); }
+FunctionPass *llvm::createLogicalSROAPass() {
+ return new LogicalSROALegacyPass();
+}
-INITIALIZE_PASS_BEGIN(LSROALegacyPass, "logical-sroa",
+INITIALIZE_PASS_BEGIN(LogicalSROALegacyPass, "logical-sroa",
"Logical Scalar Replacement Of Aggregates", false, false)
-INITIALIZE_PASS_END(LSROALegacyPass, "logical-sroa",
+INITIALIZE_PASS_END(LogicalSROALegacyPass, "logical-sroa",
"Logical Scalar Replacement Of Aggregates", false, false)
>From 94644a038b5a35b74137e64c3dbff1b5bc740a2e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <brioche at google.com>
Date: Tue, 21 Apr 2026 16:41:06 +0200
Subject: [PATCH 13/25] anonymous NS -> static
---
llvm/lib/Transforms/Scalar/LogicalSROA.cpp | 19 ++++++++-----------
1 file changed, 8 insertions(+), 11 deletions(-)
diff --git a/llvm/lib/Transforms/Scalar/LogicalSROA.cpp b/llvm/lib/Transforms/Scalar/LogicalSROA.cpp
index e42612bccadd4..d93af9cad2450 100644
--- a/llvm/lib/Transforms/Scalar/LogicalSROA.cpp
+++ b/llvm/lib/Transforms/Scalar/LogicalSROA.cpp
@@ -30,10 +30,8 @@ using namespace llvm;
#define DEBUG_TYPE "logical-sroa"
-namespace {
-
// Return all lifetime intrinsics with the instruction I as operand.
-SmallVector<LifetimeIntrinsic *>
+static SmallVector<LifetimeIntrinsic *>
collectLifetimeIntrinsicsUsing(Instruction &I) {
SmallVector<LifetimeIntrinsic *> Output;
@@ -52,7 +50,7 @@ collectLifetimeIntrinsicsUsing(Instruction &I) {
// If any user of SAI is not an SGEP, or an SGEP referencing the whole struct,
// this function returns an empty array. This function ignores lifetime
// intrinsics.
-SmallVector<SmallVector<StructuredGEPInst *>>
+static SmallVector<SmallVector<StructuredGEPInst *>>
collectPerFieldSGEP(StructuredAllocaInst &SAI) {
StructType *ST = cast<StructType>(SAI.getAllocationType());
SmallVector<SmallVector<StructuredGEPInst *>> Output(ST->getNumElements());
@@ -83,7 +81,8 @@ collectPerFieldSGEP(StructuredAllocaInst &SAI) {
// For each lifetime intrinsic in LifetimeIntrinsics, creates a new one, but
// uses V as operand.
-void copyLifetimeIntrinsicFor(IRBuilder<> &B, LifetimeIntrinsic *II, Value *V) {
+static void copyLifetimeIntrinsicFor(IRBuilder<> &B, LifetimeIntrinsic *II,
+ Value *V) {
B.SetInsertPoint(II);
if (II->getIntrinsicID() == Intrinsic::lifetime_start) {
@@ -94,8 +93,8 @@ void copyLifetimeIntrinsicFor(IRBuilder<> &B, LifetimeIntrinsic *II, Value *V) {
llvm_unreachable("invalid argument: expected a lifetime intrinsic");
}
-void rewriteSGEPChain(IRBuilder<> &B, StructuredGEPInst *SGEP,
- StructuredAllocaInst *FieldAlloca) {
+static void rewriteSGEPChain(IRBuilder<> &B, StructuredGEPInst *SGEP,
+ StructuredAllocaInst *FieldAlloca) {
if (SGEP->getNumIndices() == 1) {
SGEP->replaceAllUsesWith(FieldAlloca);
SGEP->eraseFromParent();
@@ -110,7 +109,7 @@ void rewriteSGEPChain(IRBuilder<> &B, StructuredGEPInst *SGEP,
SGEP->eraseFromParent();
}
-bool runOnStructuredAlloca(StructuredAllocaInst &SAI) {
+static bool runOnStructuredAlloca(StructuredAllocaInst &SAI) {
// For now, LogicalSROA only handles SGEP on structs.
StructType *ST = dyn_cast<StructType>(SAI.getAllocationType());
if (!ST)
@@ -143,7 +142,7 @@ bool runOnStructuredAlloca(StructuredAllocaInst &SAI) {
return true;
}
-bool runLogicalSROA(Function &F) {
+static bool runLogicalSROA(Function &F) {
SmallVector<StructuredAllocaInst *> Worklist;
for (auto &BB : F) {
for (auto &I : BB) {
@@ -158,8 +157,6 @@ bool runLogicalSROA(Function &F) {
return Changed;
}
-} // end anonymous namespace
-
PreservedAnalyses LogicalSROAPass::run(Function &F,
FunctionAnalysisManager &AM) {
if (!runLogicalSROA(F))
>From c63bd1ce64cd93fd40b713be45216ea3adc945f9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <brioche at google.com>
Date: Tue, 21 Apr 2026 16:42:22 +0200
Subject: [PATCH 14/25] use dyncast lifetime
---
llvm/lib/Transforms/Scalar/LogicalSROA.cpp | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/llvm/lib/Transforms/Scalar/LogicalSROA.cpp b/llvm/lib/Transforms/Scalar/LogicalSROA.cpp
index d93af9cad2450..c5fdc1941324a 100644
--- a/llvm/lib/Transforms/Scalar/LogicalSROA.cpp
+++ b/llvm/lib/Transforms/Scalar/LogicalSROA.cpp
@@ -36,9 +36,8 @@ collectLifetimeIntrinsicsUsing(Instruction &I) {
SmallVector<LifetimeIntrinsic *> Output;
for (User *U : I.users()) {
- auto II = dyn_cast<IntrinsicInst>(U);
- if (II && isLifetimeIntrinsic(II->getIntrinsicID()))
- Output.push_back(cast<LifetimeIntrinsic>(II));
+ if (auto LI = dyn_cast<LifetimeIntrinsic>(U))
+ Output.push_back(cast<LifetimeIntrinsic>(LI));
}
return Output;
>From 7cd1f7b84e7f9aa09c417762ecfab52acea926f8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <brioche at google.com>
Date: Tue, 21 Apr 2026 16:42:39 +0200
Subject: [PATCH 15/25] only look at the entrypoint
---
llvm/lib/Transforms/Scalar/LogicalSROA.cpp | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/llvm/lib/Transforms/Scalar/LogicalSROA.cpp b/llvm/lib/Transforms/Scalar/LogicalSROA.cpp
index c5fdc1941324a..49c28ae6ca2af 100644
--- a/llvm/lib/Transforms/Scalar/LogicalSROA.cpp
+++ b/llvm/lib/Transforms/Scalar/LogicalSROA.cpp
@@ -59,7 +59,7 @@ collectPerFieldSGEP(StructuredAllocaInst &SAI) {
if (II && II->isLifetimeStartOrEnd())
continue;
- auto SGEP = dyn_cast<StructuredGEPInst>(U);
+ auto *SGEP = dyn_cast<StructuredGEPInst>(U);
if (!SGEP)
return {};
@@ -125,7 +125,7 @@ static bool runOnStructuredAlloca(StructuredAllocaInst &SAI) {
continue;
B.SetInsertPoint(&SAI);
- StructuredAllocaInst *FieldAlloca = cast<StructuredAllocaInst>(
+ auto *FieldAlloca = cast<StructuredAllocaInst>(
B.CreateStructuredAlloca(ST->getElementType(FieldIndex)));
for (auto II : LifetimeIntrinsics)
@@ -143,11 +143,11 @@ static bool runOnStructuredAlloca(StructuredAllocaInst &SAI) {
static bool runLogicalSROA(Function &F) {
SmallVector<StructuredAllocaInst *> Worklist;
- for (auto &BB : F) {
- for (auto &I : BB) {
- if (StructuredAllocaInst *SAI = dyn_cast<StructuredAllocaInst>(&I))
- Worklist.push_back(SAI);
- }
+ BasicBlock &EntryBB = F.getEntryBlock();
+ for (BasicBlock::iterator I = EntryBB.begin(), E = std::prev(EntryBB.end());
+ I != E; ++I) {
+ if (StructuredAllocaInst *SAI = dyn_cast<StructuredAllocaInst>(&*I))
+ Worklist.push_back(SAI);
}
bool Changed = false;
>From c1f12be70fc229c6a826227dc8a9cf843f2b1643 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <brioche at google.com>
Date: Tue, 21 Apr 2026 17:09:25 +0200
Subject: [PATCH 16/25] langref update
---
llvm/docs/LangRef.rst | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index 6f34005f3e945..f88464222e3d8 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -15318,6 +15318,14 @@ If the source pointer is poison, the instruction returns poison.
The resulting pointer belongs to the same address space as ``source``.
This instruction does not dereference the pointer.
+Chaining indices in a single ``llvm.structured.gep`` instruction or spliting
+is sequentialy over multiple has exactly the same behavior. As such, two
+``llvm.structured.gep`` instruction can be merged if the second one base
+pointer is the result of the first by appending the indices of the second to
+the first.
+Conversely, a single structured GEP instruction can be split in to by
+splitting the indices sequence over multiple instructions.
+
Example:
""""""""
@@ -15378,6 +15386,20 @@ Or:
This is, however, dependent on context that codegen has an insight on. The
fact that `[ i32 x 4 ]` and `%S` are equivalent depends on the target.
+**SGEP split & merge**
+
+A structured GEP can be split, or merged with no observable difference:
+
+.. code-block:: llvm
+
+ %S = type [ 2 x { i32, i32 } ]
+ %a = call ptr @llvm.structured.gep(ptr elementtype(%S) %my_struct, i32 0, i32 1)
+
+ %tmp = call ptr @llvm.structured.gep(ptr elementtype(%S) %my_struct, i32 0)
+ %b = call ptr @llvm.structured.gep(ptr elementtype({ i32, i32 }) %tmp, i32 1)
+
+Here, `%a` and `%b` point to the same object.
+
.. _i_structured_alloca:
>From 7bf798b4fb8c5018d2dbb6dd22709f9fb0904734 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <brioche at google.com>
Date: Wed, 22 Apr 2026 17:11:11 +0200
Subject: [PATCH 17/25] typo nits
---
llvm/docs/LangRef.rst | 12 ++++++------
llvm/lib/Transforms/Scalar/LogicalSROA.cpp | 2 +-
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index f88464222e3d8..e478ba188e572 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -15318,12 +15318,12 @@ If the source pointer is poison, the instruction returns poison.
The resulting pointer belongs to the same address space as ``source``.
This instruction does not dereference the pointer.
-Chaining indices in a single ``llvm.structured.gep`` instruction or spliting
-is sequentialy over multiple has exactly the same behavior. As such, two
-``llvm.structured.gep`` instruction can be merged if the second one base
-pointer is the result of the first by appending the indices of the second to
-the first.
-Conversely, a single structured GEP instruction can be split in to by
+Chaining indices in a single ``llvm.structured.gep`` instruction or splitting
+is sequentially over multiple instructions has exactly the same behavior. As
+such, two ``llvm.structured.gep`` instructions can be merged if the second
+one base pointer is the result of the first by appending the indices of the
+second to the first.
+Conversely, a single structured GEP instruction can be split in two by
splitting the indices sequence over multiple instructions.
Example:
diff --git a/llvm/lib/Transforms/Scalar/LogicalSROA.cpp b/llvm/lib/Transforms/Scalar/LogicalSROA.cpp
index 49c28ae6ca2af..a8e96b02756a1 100644
--- a/llvm/lib/Transforms/Scalar/LogicalSROA.cpp
+++ b/llvm/lib/Transforms/Scalar/LogicalSROA.cpp
@@ -12,7 +12,7 @@
/// promote them to multiple allocas of scalar type.
///
/// FIXME: nested aggregates are not fully optimized (#192619).
-/// FIXME: array are not optimized (#192620).
+/// FIXME: arrays are not optimized (#192620).
///
//===----------------------------------------------------------------------===//
>From 92031746c308163a022c62859280b8142bb92979 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <brioche at google.com>
Date: Thu, 23 Apr 2026 13:20:28 +0200
Subject: [PATCH 18/25] Revert "typo nits"
This reverts commit 7bf798b4fb8c5018d2dbb6dd22709f9fb0904734.
---
llvm/docs/LangRef.rst | 12 ++++++------
llvm/lib/Transforms/Scalar/LogicalSROA.cpp | 2 +-
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index e478ba188e572..f88464222e3d8 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -15318,12 +15318,12 @@ If the source pointer is poison, the instruction returns poison.
The resulting pointer belongs to the same address space as ``source``.
This instruction does not dereference the pointer.
-Chaining indices in a single ``llvm.structured.gep`` instruction or splitting
-is sequentially over multiple instructions has exactly the same behavior. As
-such, two ``llvm.structured.gep`` instructions can be merged if the second
-one base pointer is the result of the first by appending the indices of the
-second to the first.
-Conversely, a single structured GEP instruction can be split in two by
+Chaining indices in a single ``llvm.structured.gep`` instruction or spliting
+is sequentialy over multiple has exactly the same behavior. As such, two
+``llvm.structured.gep`` instruction can be merged if the second one base
+pointer is the result of the first by appending the indices of the second to
+the first.
+Conversely, a single structured GEP instruction can be split in to by
splitting the indices sequence over multiple instructions.
Example:
diff --git a/llvm/lib/Transforms/Scalar/LogicalSROA.cpp b/llvm/lib/Transforms/Scalar/LogicalSROA.cpp
index a8e96b02756a1..49c28ae6ca2af 100644
--- a/llvm/lib/Transforms/Scalar/LogicalSROA.cpp
+++ b/llvm/lib/Transforms/Scalar/LogicalSROA.cpp
@@ -12,7 +12,7 @@
/// promote them to multiple allocas of scalar type.
///
/// FIXME: nested aggregates are not fully optimized (#192619).
-/// FIXME: arrays are not optimized (#192620).
+/// FIXME: array are not optimized (#192620).
///
//===----------------------------------------------------------------------===//
>From 098bbf53135a2440d360e22ba9e168cf82fc5f89 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <brioche at google.com>
Date: Thu, 23 Apr 2026 13:20:34 +0200
Subject: [PATCH 19/25] Revert "langref update"
This reverts commit c1f12be70fc229c6a826227dc8a9cf843f2b1643.
---
llvm/docs/LangRef.rst | 22 ----------------------
1 file changed, 22 deletions(-)
diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index f88464222e3d8..6f34005f3e945 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -15318,14 +15318,6 @@ If the source pointer is poison, the instruction returns poison.
The resulting pointer belongs to the same address space as ``source``.
This instruction does not dereference the pointer.
-Chaining indices in a single ``llvm.structured.gep`` instruction or spliting
-is sequentialy over multiple has exactly the same behavior. As such, two
-``llvm.structured.gep`` instruction can be merged if the second one base
-pointer is the result of the first by appending the indices of the second to
-the first.
-Conversely, a single structured GEP instruction can be split in to by
-splitting the indices sequence over multiple instructions.
-
Example:
""""""""
@@ -15386,20 +15378,6 @@ Or:
This is, however, dependent on context that codegen has an insight on. The
fact that `[ i32 x 4 ]` and `%S` are equivalent depends on the target.
-**SGEP split & merge**
-
-A structured GEP can be split, or merged with no observable difference:
-
-.. code-block:: llvm
-
- %S = type [ 2 x { i32, i32 } ]
- %a = call ptr @llvm.structured.gep(ptr elementtype(%S) %my_struct, i32 0, i32 1)
-
- %tmp = call ptr @llvm.structured.gep(ptr elementtype(%S) %my_struct, i32 0)
- %b = call ptr @llvm.structured.gep(ptr elementtype({ i32, i32 }) %tmp, i32 1)
-
-Here, `%a` and `%b` point to the same object.
-
.. _i_structured_alloca:
>From 1316bcb474fc6ddd5f859d14bdce74a2715a0f08 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <brioche at google.com>
Date: Thu, 23 Apr 2026 16:27:49 +0200
Subject: [PATCH 20/25] limit LSROA scope to full SGEP chain
---
llvm/lib/Transforms/Scalar/LogicalSROA.cpp | 94 +++++++++++++++----
llvm/test/Transforms/LSROA/array.ll | 57 ++++++++++++
llvm/test/Transforms/LSROA/basictest.ll | 52 -----------
llvm/test/Transforms/LSROA/escaping.ll | 100 +++++++++++++++++++++
llvm/test/Transforms/LSROA/phi.ll | 54 +++++++++++
llvm/test/Transforms/LSROA/select.ll | 29 ++++++
6 files changed, 317 insertions(+), 69 deletions(-)
create mode 100644 llvm/test/Transforms/LSROA/escaping.ll
create mode 100644 llvm/test/Transforms/LSROA/phi.ll
create mode 100644 llvm/test/Transforms/LSROA/select.ll
diff --git a/llvm/lib/Transforms/Scalar/LogicalSROA.cpp b/llvm/lib/Transforms/Scalar/LogicalSROA.cpp
index 49c28ae6ca2af..d34b28ffb611b 100644
--- a/llvm/lib/Transforms/Scalar/LogicalSROA.cpp
+++ b/llvm/lib/Transforms/Scalar/LogicalSROA.cpp
@@ -17,6 +17,7 @@
//===----------------------------------------------------------------------===//
#include "llvm/Transforms/Scalar/LogicalSROA.h"
+#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Analysis/DomTreeUpdater.h"
#include "llvm/IR/IRBuilder.h"
@@ -43,31 +44,88 @@ collectLifetimeIntrinsicsUsing(Instruction &I) {
return Output;
}
+// Returns true is all the users and derived users of the alloca
+// allow the alloca to be split.
+static bool isAllocaSplittable(StructuredAllocaInst &SAI) {
+ SmallVector<Value *> WorkList(SAI.users());
+ DenseSet<Value *> Visited;
+
+ // Helper function to enqueue all non-visited users of `I`.
+ auto enqueueAllUsers = [&](Instruction *I) {
+ for (auto *U : I->users()) {
+ if (Visited.contains(U))
+ continue;
+ WorkList.push_back(U);
+ }
+ };
+
+ while (!WorkList.empty()) {
+ Instruction *I = dyn_cast<Instruction>(WorkList.back());
+ WorkList.pop_back();
+
+ // User is not an instruction. Not sure what it it, in
+ // doubt, don't split.
+ if (!I)
+ return false;
+
+ Visited.insert(I);
+
+ // Those allow the alloca split.
+ if (isa<LifetimeIntrinsic>(I))
+ continue;
+
+ // If we load the whole alloca, we cannot split,
+ // otherwise, we can stop looking into derived users.
+ if (auto *LI = dyn_cast<LoadInst>(I)) {
+ if (LI->getPointerOperand() == &SAI)
+ return false;
+ continue;
+ }
+
+ // If we store to whole alloca, we cannot split,
+ // otherwise, we can stop looking into derived users.
+ if (auto *SI = dyn_cast<StoreInst>(I)) {
+ if (SI->getPointerOperand() == &SAI)
+ return false;
+ continue;
+ }
+
+ // PHI and Select instruction are not inherently preventing
+ // the split, but correctly handling those requires more testing,
+ // so postponing this (See #193749)
+ if (isa<PHINode>(I) || isa<SelectInst>(I))
+ return false;
+
+ if (auto *SGEP = dyn_cast<StructuredGEPInst>(I)) {
+ // If the SGEP has no indices and is still there, this probably means the
+ // ptr is escaping or uses as-is. For now, we bail out.
+ if (SGEP->getNumIndices() == 0)
+ return false;
+
+ enqueueAllUsers(SGEP);
+ continue;
+ }
+
+ // Any other users prevents the split (call, escape, etc).
+ return false;
+ }
+
+ return true;
+}
+
// Returns a vector with one element for each field of the struct allocated by
// SAI. Each element is a vector of SGEP instruction referencing this field.
-//
-// If any user of SAI is not an SGEP, or an SGEP referencing the whole struct,
-// this function returns an empty array. This function ignores lifetime
-// intrinsics.
+// This function ignores lifetime intrinsics.
static SmallVector<SmallVector<StructuredGEPInst *>>
collectPerFieldSGEP(StructuredAllocaInst &SAI) {
StructType *ST = cast<StructType>(SAI.getAllocationType());
SmallVector<SmallVector<StructuredGEPInst *>> Output(ST->getNumElements());
for (User *U : SAI.users()) {
- auto II = dyn_cast<IntrinsicInst>(U);
- if (II && II->isLifetimeStartOrEnd())
+ if (dyn_cast<LifetimeIntrinsic>(U))
continue;
- auto *SGEP = dyn_cast<StructuredGEPInst>(U);
- if (!SGEP)
- return {};
-
- // If the SGEP has no indices, this means we have a pointer on the whole
- // struct. For now, we bail out: if it was not used, it would be DCE'd, so
- // there is probably a reference to the whole struct somewhere.
- if (SGEP->getNumIndices() == 0)
- return {};
+ auto *SGEP = cast<StructuredGEPInst>(U);
// IR rule: SGEP on struct can only use constant int as indices.
ConstantInt *Index = cast<ConstantInt>(SGEP->getIndexOperand(0));
@@ -114,10 +172,12 @@ static bool runOnStructuredAlloca(StructuredAllocaInst &SAI) {
if (!ST)
return false;
- auto PerFieldSGEP = collectPerFieldSGEP(SAI);
- if (PerFieldSGEP.empty())
+ if (!isAllocaSplittable(SAI))
return false;
+ auto PerFieldSGEP = collectPerFieldSGEP(SAI);
+ assert(PerFieldSGEP.size() == ST->getNumElements());
+
auto LifetimeIntrinsics = collectLifetimeIntrinsicsUsing(SAI);
IRBuilder B(&SAI);
for (const auto &[FieldIndex, Users] : llvm::enumerate(PerFieldSGEP)) {
diff --git a/llvm/test/Transforms/LSROA/array.ll b/llvm/test/Transforms/LSROA/array.ll
index b808da827f932..3ad35b5b09d28 100644
--- a/llvm/test/Transforms/LSROA/array.ll
+++ b/llvm/test/Transforms/LSROA/array.ll
@@ -23,3 +23,60 @@ entry:
ret i32 %res
}
+
+define i32 @test_simple_array_dynamic_index(i32 %i) {
+; CHECK-LABEL: define i32 @test_simple_array_dynamic_index(
+; CHECK-SAME: i32 [[I:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP:%.*]] = call elementtype([10 x i32]) ptr @llvm.structured.alloca.p0()
+; CHECK-NEXT: [[PTR:%.*]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([10 x i32]) [[TMP]], i32 [[I]])
+; CHECK-NEXT: store i32 0, ptr [[PTR]], align 4
+; CHECK-NEXT: [[RES:%.*]] = load i32, ptr [[PTR]], align 4
+; CHECK-NEXT: ret i32 [[RES]]
+;
+entry:
+ %tmp = call elementtype([10 x i32]) ptr @llvm.structured.alloca.p0()
+ %ptr = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([10 x i32]) %tmp, i32 %i)
+ store i32 0, ptr %ptr
+ %res = load i32, ptr %ptr
+ ret i32 %res
+
+}
+
+define i32 @test_simple_array_dynamic_index_in_child(i32 %i) {
+; CHECK-LABEL: define i32 @test_simple_array_dynamic_index_in_child(
+; CHECK-SAME: i32 [[I:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP0:%.*]] = call elementtype([10 x i32]) ptr @llvm.structured.alloca.p0()
+; CHECK-NEXT: [[PTR1:%.*]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([10 x i32]) [[TMP0]], i32 [[I]])
+; CHECK-NEXT: store i32 0, ptr [[PTR1]], align 4
+; CHECK-NEXT: [[RES:%.*]] = load i32, ptr [[PTR1]], align 4
+; CHECK-NEXT: ret i32 [[RES]]
+;
+entry:
+ %tmp = call elementtype({ i32, [10 x i32]}) ptr @llvm.structured.alloca.p0()
+ %ptr = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, [10 x i32] }) %tmp, i32 1, i32 %i)
+ store i32 0, ptr %ptr
+ %res = load i32, ptr %ptr
+ ret i32 %res
+
+}
+
+define i32 @test_simple_array_dynamic_index_only_child(i32 %i) {
+; CHECK-LABEL: define i32 @test_simple_array_dynamic_index_only_child(
+; CHECK-SAME: i32 [[I:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP0:%.*]] = call elementtype([10 x i32]) ptr @llvm.structured.alloca.p0()
+; CHECK-NEXT: [[PTR1:%.*]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([10 x i32]) [[TMP0]], i32 [[I]])
+; CHECK-NEXT: store i32 0, ptr [[PTR1]], align 4
+; CHECK-NEXT: [[RES:%.*]] = load i32, ptr [[PTR1]], align 4
+; CHECK-NEXT: ret i32 [[RES]]
+;
+entry:
+ %tmp = call elementtype({[10 x i32]}) ptr @llvm.structured.alloca.p0()
+ %ptr = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({[10 x i32]}) %tmp, i32 0, i32 %i)
+ store i32 0, ptr %ptr
+ %res = load i32, ptr %ptr
+ ret i32 %res
+
+}
diff --git a/llvm/test/Transforms/LSROA/basictest.ll b/llvm/test/Transforms/LSROA/basictest.ll
index 3d7f6079e954d..69384c51fbaf8 100644
--- a/llvm/test/Transforms/LSROA/basictest.ll
+++ b/llvm/test/Transforms/LSROA/basictest.ll
@@ -150,55 +150,3 @@ entry:
%c = add i32 %b, %a
ret i32 %c
}
-
-define i32 @test_partial_use_phi_node(i1 %cond) {
-; CHECK-LABEL: define i32 @test_partial_use_phi_node(
-; CHECK-SAME: i1 [[COND:%.*]]) {
-; CHECK-NEXT: [[ENTRY:.*:]]
-; CHECK-NEXT: [[TMP0:%.*]] = call elementtype(i32) ptr @llvm.structured.alloca.p0()
-; CHECK-NEXT: [[TMP1:%.*]] = call elementtype(i32) ptr @llvm.structured.alloca.p0()
-; CHECK-NEXT: br i1 [[COND]], label %[[L1:.*]], label %[[L2:.*]]
-; CHECK: [[L1]]:
-; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[TMP0]])
-; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[TMP1]])
-; CHECK-NEXT: store i32 0, ptr [[TMP0]], align 4
-; CHECK-NEXT: br label %[[L3:.*]]
-; CHECK: [[L2]]:
-; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[TMP0]])
-; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[TMP1]])
-; CHECK-NEXT: store i32 1, ptr [[TMP1]], align 4
-; CHECK-NEXT: br label %[[L3]]
-; CHECK: [[L3]]:
-; CHECK-NEXT: [[PTR:%.*]] = phi ptr [ [[TMP0]], %[[L1]] ], [ [[TMP1]], %[[L2]] ]
-; CHECK-NEXT: [[A:%.*]] = load i32, ptr [[PTR]], align 4
-; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[TMP0]])
-; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[TMP1]])
-; CHECK-NEXT: br label %[[EXIT:.*]]
-; CHECK: [[EXIT]]:
-; CHECK-NEXT: ret i32 [[A]]
-;
-entry:
- %tmp = call elementtype({ i32, i32 }) ptr @llvm.structured.alloca.p0()
- %ptr0 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) %tmp, i32 0)
- %ptr1 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) %tmp, i32 1)
- br i1 %cond, label %l1, label %l2
-
-l1:
- call void @llvm.lifetime.start.p0(ptr %tmp)
- store i32 0, ptr %ptr0
- br label %l3
-
-l2:
- call void @llvm.lifetime.start.p0(ptr %tmp)
- store i32 1, ptr %ptr1
- br label %l3
-
-l3:
- %ptr = phi ptr [ %ptr0, %l1 ], [ %ptr1, %l2 ]
- %a = load i32, ptr %ptr
- call void @llvm.lifetime.end.p0(ptr %tmp)
- br label %exit
-
-exit:
- ret i32 %a
-}
diff --git a/llvm/test/Transforms/LSROA/escaping.ll b/llvm/test/Transforms/LSROA/escaping.ll
new file mode 100644
index 0000000000000..3f90004d3649f
--- /dev/null
+++ b/llvm/test/Transforms/LSROA/escaping.ll
@@ -0,0 +1,100 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt < %s -passes='logical-sroa' -S | FileCheck %s --check-prefixes=CHECK
+
+declare void @llvm.lifetime.start.p0(ptr nocapture)
+declare void @llvm.lifetime.end.p0(ptr nocapture)
+declare ptr @llvm.structured.alloca.p0()
+declare ptr @llvm.structured.gep.p0(ptr, ...)
+declare void @foo(ptr)
+
+; The alloca ptr is escaping, we cannot know if splitting is safe.
+define i32 @test_simple_escape() {
+; CHECK-LABEL: define i32 @test_simple_escape() {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP:%.*]] = call elementtype({ i32, i32 }) ptr @llvm.structured.alloca.p0()
+; CHECK-NEXT: [[PTR0:%.*]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) [[TMP]], i32 0)
+; CHECK-NEXT: [[PTR1:%.*]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) [[TMP]], i32 1)
+; CHECK-NEXT: store i32 0, ptr [[PTR0]], align 4
+; CHECK-NEXT: store i32 1, ptr [[PTR1]], align 4
+; CHECK-NEXT: call void @foo(ptr [[TMP]])
+; CHECK-NEXT: [[A:%.*]] = load i32, ptr [[PTR0]], align 4
+; CHECK-NEXT: [[B:%.*]] = load i32, ptr [[PTR1]], align 4
+; CHECK-NEXT: [[RES:%.*]] = add i32 [[A]], [[B]]
+; CHECK-NEXT: ret i32 [[RES]]
+;
+entry:
+ %tmp = call elementtype({ i32, i32 }) ptr @llvm.structured.alloca.p0()
+
+ %ptr0 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) %tmp, i32 0)
+ %ptr1 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) %tmp, i32 1)
+
+ store i32 0, ptr %ptr0
+ store i32 1, ptr %ptr1
+
+ call void @foo(ptr %tmp)
+
+ %a = load i32, ptr %ptr0
+ %b = load i32, ptr %ptr1
+ %res = add i32 %a, %b
+ ret i32 %res
+}
+
+define i32 @test_derived_escape() {
+; CHECK-LABEL: define i32 @test_derived_escape() {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP:%.*]] = call elementtype({ i32, i32 }) ptr @llvm.structured.alloca.p0()
+; CHECK-NEXT: [[PTR0:%.*]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) [[TMP]], i32 0)
+; CHECK-NEXT: [[PTR1:%.*]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) [[TMP]], i32 1)
+; CHECK-NEXT: store i32 0, ptr [[PTR0]], align 4
+; CHECK-NEXT: store i32 1, ptr [[PTR1]], align 4
+; CHECK-NEXT: call void @foo(ptr [[PTR0]])
+; CHECK-NEXT: [[A:%.*]] = load i32, ptr [[PTR0]], align 4
+; CHECK-NEXT: [[B:%.*]] = load i32, ptr [[PTR1]], align 4
+; CHECK-NEXT: [[RES:%.*]] = add i32 [[A]], [[B]]
+; CHECK-NEXT: ret i32 [[RES]]
+;
+entry:
+ %tmp = call elementtype({ i32, i32 }) ptr @llvm.structured.alloca.p0()
+
+ %ptr0 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) %tmp, i32 0)
+ %ptr1 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) %tmp, i32 1)
+
+ store i32 0, ptr %ptr0
+ store i32 1, ptr %ptr1
+
+ call void @foo(ptr %ptr0)
+
+ %a = load i32, ptr %ptr0
+ %b = load i32, ptr %ptr1
+ %res = add i32 %a, %b
+ ret i32 %res
+}
+
+; A normal ptradd/gep is used, we cannot know if splitting this alloca is safe.
+define i32 @test_normal_gep() {
+; CHECK-LABEL: define i32 @test_normal_gep() {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP:%.*]] = call elementtype({ i32, i32 }) ptr @llvm.structured.alloca.p0()
+; CHECK-NEXT: [[PTR0:%.*]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) [[TMP]], i32 0)
+; CHECK-NEXT: [[PTR1:%.*]] = getelementptr i8, ptr [[PTR0]], i32 4
+; CHECK-NEXT: store i32 0, ptr [[PTR0]], align 4
+; CHECK-NEXT: store i32 1, ptr [[PTR1]], align 4
+; CHECK-NEXT: [[A:%.*]] = load i32, ptr [[PTR0]], align 4
+; CHECK-NEXT: [[B:%.*]] = load i32, ptr [[PTR1]], align 4
+; CHECK-NEXT: [[RES:%.*]] = add i32 [[A]], [[B]]
+; CHECK-NEXT: ret i32 [[RES]]
+;
+entry:
+ %tmp = call elementtype({ i32, i32 }) ptr @llvm.structured.alloca.p0()
+
+ %ptr0 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) %tmp, i32 0)
+ %ptr1 = getelementptr i8, ptr %ptr0, i32 4
+
+ store i32 0, ptr %ptr0
+ store i32 1, ptr %ptr1
+
+ %a = load i32, ptr %ptr0
+ %b = load i32, ptr %ptr1
+ %res = add i32 %a, %b
+ ret i32 %res
+}
diff --git a/llvm/test/Transforms/LSROA/phi.ll b/llvm/test/Transforms/LSROA/phi.ll
new file mode 100644
index 0000000000000..a6a89fbf54f97
--- /dev/null
+++ b/llvm/test/Transforms/LSROA/phi.ll
@@ -0,0 +1,54 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt < %s -passes='logical-sroa' -S | FileCheck %s --check-prefixes=CHECK
+
+; This case is not optimized for now, but we should (see #193749).
+define i32 @test_partial_use_phi_node(i1 %cond) {
+; CHECK-LABEL: define i32 @test_partial_use_phi_node(
+; CHECK-SAME: i1 [[COND:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP1:%.*]] = call elementtype({ i32, i32 }) ptr @llvm.structured.alloca.p0()
+; CHECK-NEXT: [[TMP0:%.*]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) [[TMP1]], i32 0)
+; CHECK-NEXT: [[PTR1:%.*]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) [[TMP1]], i32 1)
+; CHECK-NEXT: br i1 [[COND]], label %[[L1:.*]], label %[[L2:.*]]
+; CHECK: [[L1]]:
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[TMP1]])
+; CHECK-NEXT: store i32 0, ptr [[TMP0]], align 4
+; CHECK-NEXT: br label %[[L3:.*]]
+; CHECK: [[L2]]:
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[TMP1]])
+; CHECK-NEXT: store i32 1, ptr [[PTR1]], align 4
+; CHECK-NEXT: br label %[[L3]]
+; CHECK: [[L3]]:
+; CHECK-NEXT: [[PTR:%.*]] = phi ptr [ [[TMP0]], %[[L1]] ], [ [[PTR1]], %[[L2]] ]
+; CHECK-NEXT: [[A:%.*]] = load i32, ptr [[PTR]], align 4
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[TMP1]])
+; CHECK-NEXT: br label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret i32 [[A]]
+;
+entry:
+ %tmp = call elementtype({ i32, i32 }) ptr @llvm.structured.alloca.p0()
+ %ptr0 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) %tmp, i32 0)
+ %ptr1 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) %tmp, i32 1)
+ br i1 %cond, label %l1, label %l2
+
+l1:
+ call void @llvm.lifetime.start.p0(ptr %tmp)
+ store i32 0, ptr %ptr0
+ br label %l3
+
+l2:
+ call void @llvm.lifetime.start.p0(ptr %tmp)
+ store i32 1, ptr %ptr1
+ br label %l3
+
+l3:
+ %ptr = phi ptr [ %ptr0, %l1 ], [ %ptr1, %l2 ]
+ %a = load i32, ptr %ptr
+ call void @llvm.lifetime.end.p0(ptr %tmp)
+ br label %exit
+
+exit:
+ ret i32 %a
+}
+
diff --git a/llvm/test/Transforms/LSROA/select.ll b/llvm/test/Transforms/LSROA/select.ll
new file mode 100644
index 0000000000000..22503710463b5
--- /dev/null
+++ b/llvm/test/Transforms/LSROA/select.ll
@@ -0,0 +1,29 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt < %s -passes='logical-sroa' -S | FileCheck %s --check-prefixes=CHECK
+
+; This case is not optimized for now, but we should (see #193749).
+define i32 @test_select_basic(i1 %cond) {
+; CHECK-LABEL: define i32 @test_select_basic(
+; CHECK-SAME: i1 [[COND:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP:%.*]] = call elementtype({ i32, i32 }) ptr @llvm.structured.alloca.p0()
+; CHECK-NEXT: [[PTR0:%.*]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) [[TMP]], i32 0)
+; CHECK-NEXT: [[PTR1:%.*]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) [[TMP]], i32 1)
+; CHECK-NEXT: store i32 0, ptr [[PTR0]], align 4
+; CHECK-NEXT: store i32 1, ptr [[PTR1]], align 4
+; CHECK-NEXT: [[PTR:%.*]] = select i1 [[COND]], ptr [[PTR0]], ptr [[PTR1]]
+; CHECK-NEXT: [[A:%.*]] = load i32, ptr [[PTR]], align 4
+; CHECK-NEXT: ret i32 [[A]]
+;
+entry:
+ %tmp = call elementtype({ i32, i32 }) ptr @llvm.structured.alloca.p0()
+ %ptr0 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) %tmp, i32 0)
+ %ptr1 = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype({ i32, i32 }) %tmp, i32 1)
+
+ store i32 0, ptr %ptr0
+ store i32 1, ptr %ptr1
+
+ %ptr = select i1 %cond, ptr %ptr0, ptr %ptr1
+ %a = load i32, ptr %ptr
+ ret i32 %a
+}
>From 11c31342a03587da78f0dba496de72ce7f07249a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <brioche at google.com>
Date: Fri, 24 Apr 2026 13:06:42 +0200
Subject: [PATCH 21/25] pr nits
---
llvm/lib/Transforms/Scalar/LogicalSROA.cpp | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/llvm/lib/Transforms/Scalar/LogicalSROA.cpp b/llvm/lib/Transforms/Scalar/LogicalSROA.cpp
index d34b28ffb611b..c65e6619e6c7a 100644
--- a/llvm/lib/Transforms/Scalar/LogicalSROA.cpp
+++ b/llvm/lib/Transforms/Scalar/LogicalSROA.cpp
@@ -37,15 +37,15 @@ collectLifetimeIntrinsicsUsing(Instruction &I) {
SmallVector<LifetimeIntrinsic *> Output;
for (User *U : I.users()) {
- if (auto LI = dyn_cast<LifetimeIntrinsic>(U))
- Output.push_back(cast<LifetimeIntrinsic>(LI));
+ if (auto *LI = dyn_cast<LifetimeIntrinsic>(U))
+ Output.push_back(LI);
}
return Output;
}
-// Returns true is all the users and derived users of the alloca
-// allow the alloca to be split.
+// Returns true if all direct and indirect users of the alloca
+// allow the split.
static bool isAllocaSplittable(StructuredAllocaInst &SAI) {
SmallVector<Value *> WorkList(SAI.users());
DenseSet<Value *> Visited;
@@ -122,7 +122,7 @@ collectPerFieldSGEP(StructuredAllocaInst &SAI) {
SmallVector<SmallVector<StructuredGEPInst *>> Output(ST->getNumElements());
for (User *U : SAI.users()) {
- if (dyn_cast<LifetimeIntrinsic>(U))
+ if (isa<LifetimeIntrinsic>(U))
continue;
auto *SGEP = cast<StructuredGEPInst>(U);
>From 50744058f61336b1fca42083eb5e8d4548d00b54 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <brioche at google.com>
Date: Thu, 30 Apr 2026 15:24:08 +0200
Subject: [PATCH 22/25] docs: add langref around aliasing
---
llvm/docs/LangRef.rst | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index 6f34005f3e945..170286a38d7d4 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -15318,6 +15318,27 @@ If the source pointer is poison, the instruction returns poison.
The resulting pointer belongs to the same address space as ``source``.
This instruction does not dereference the pointer.
+Aliasing rules:
+"""""""""""""""
+
+Common :ref:`aliasing rules <pointeraliasing>` apply to pointers returned
+by this intrinsic, as well as the following additional rules:
+
+- A pointer returned by `@llvm.structured.gep` for a given element is
+ considered not to alias with pointers the instruction would return for
+ a sibling element.
+
+.. code-block:: llvm
+
+ %S = { i32, i32 }
+ %ptr = call ptr @llvm.structured.gep(ptr elementtype(%S) %src, i32 0)
+ %res = load i64, ptr %ptr
+
+Here, `%res` is not expected to load the second `i32` of the struct `%S`.
+The loaded value is implementation defined and depends on the physical
+layout of `i32`.
+This means it is allowed to consider the second field of `%S` to be unused.
+
Example:
""""""""
>From 9af41108f79ac10f0c73161c86f28ba42a5e960b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <brioche at google.com>
Date: Mon, 4 May 2026 11:44:31 +0200
Subject: [PATCH 23/25] rephrase
---
llvm/docs/LangRef.rst | 20 +++++++++++---------
1 file changed, 11 insertions(+), 9 deletions(-)
diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index 170286a38d7d4..1a63ac9163eff 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -15324,20 +15324,22 @@ Aliasing rules:
Common :ref:`aliasing rules <pointeraliasing>` apply to pointers returned
by this intrinsic, as well as the following additional rules:
-- A pointer returned by `@llvm.structured.gep` for a given element is
- considered not to alias with pointers the instruction would return for
- a sibling element.
+A pointer returned by `@llvm.structured.gep` is considered not to alias a
+pointer returned by another `@llvm.structured.gep` with the same base pointer
+unless the index sequence of one is a prefix of the other.
.. code-block:: llvm
%S = { i32, i32 }
- %ptr = call ptr @llvm.structured.gep(ptr elementtype(%S) %src, i32 0)
- %res = load i64, ptr %ptr
+ %ptr0 = call ptr @llvm.structured.gep(ptr elementtype(%S) %src, i32 0)
+ %ptr1 = call ptr @llvm.structured.gep(ptr elementtype(%S) %src, i32 1)
-Here, `%res` is not expected to load the second `i32` of the struct `%S`.
-The loaded value is implementation defined and depends on the physical
-layout of `i32`.
-This means it is allowed to consider the second field of `%S` to be unused.
+ %field0 = load i64, ptr %ptr0
+ %field1 = load i32, ptr %ptr1
+
+Here, `%field0` is not expected to load the second `i32` of the struct `%S`.
+The loaded value is implementation defined, and optimizations are free
+to assume there is no overlap in the memory loaded by the 2 loads.
Example:
""""""""
>From ddf45f221df9464bc5c53951de57383533976849 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <brioche at google.com>
Date: Fri, 29 May 2026 14:04:08 +0200
Subject: [PATCH 24/25] rephrase the aliasing section
Co-Authored-By: Nikita Popov <npopov at redhat.com>
---
llvm/docs/LangRef.rst | 21 ++++++++++-----------
1 file changed, 10 insertions(+), 11 deletions(-)
diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index 1a63ac9163eff..4ec72bd324d24 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -15324,22 +15324,21 @@ Aliasing rules:
Common :ref:`aliasing rules <pointeraliasing>` apply to pointers returned
by this intrinsic, as well as the following additional rules:
-A pointer returned by `@llvm.structured.gep` is considered not to alias a
-pointer returned by another `@llvm.structured.gep` with the same base pointer
-unless the index sequence of one is a prefix of the other.
+The pointer returned by '``@llvm.structured.gep``' can only be used to access
+memory that is part of the indexed subobject, otherwise the behavior is
+undefined.
.. code-block:: llvm
+ %S = { i32, i32 } ; assuming these are laid out next to each other
+ ; and sizeof(i32) < sizeof(64).
- %S = { i32, i32 }
%ptr0 = call ptr @llvm.structured.gep(ptr elementtype(%S) %src, i32 0)
- %ptr1 = call ptr @llvm.structured.gep(ptr elementtype(%S) %src, i32 1)
-
- %field0 = load i64, ptr %ptr0
- %field1 = load i32, ptr %ptr1
+ %field0 = load i64, ptr %ptr0 ; undefined behavior, because the access
+ ; crosses into the second field.
-Here, `%field0` is not expected to load the second `i32` of the struct `%S`.
-The loaded value is implementation defined, and optimizations are free
-to assume there is no overlap in the memory loaded by the 2 loads.
+This implies that two '``llvm.structured.gep``' calls with the same pointer
+and element type do not alias unless the index sequence of one if a prefix
+of the other.
Example:
""""""""
>From 3413140688321ce0555c1c61e3b2515ae2c368e9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <brioche at google.com>
Date: Thu, 16 Jul 2026 15:36:44 +0200
Subject: [PATCH 25/25] pr-feedback
---
llvm/test/Transforms/LSROA/basictest.ll | 5 -----
1 file changed, 5 deletions(-)
diff --git a/llvm/test/Transforms/LSROA/basictest.ll b/llvm/test/Transforms/LSROA/basictest.ll
index 69384c51fbaf8..2042590b159a4 100644
--- a/llvm/test/Transforms/LSROA/basictest.ll
+++ b/llvm/test/Transforms/LSROA/basictest.ll
@@ -55,8 +55,6 @@ entry:
%res = add i32 %a, %b
ret i32 %res
-
-
}
define i32 @test_simple_struct_aliasing() {
@@ -86,8 +84,6 @@ entry:
%res = add i32 %a, %b
ret i32 %res
-
-
}
define i32 @test_simple_struct_partial_write_read() {
@@ -108,7 +104,6 @@ entry:
%a = load i32, ptr %ptr0
call void @llvm.lifetime.end.p0(ptr %tmp)
-
ret i32 %a
}
More information about the llvm-commits
mailing list