[llvm] [IR] Add logical SROA pass (PR #192058)

Nathan Gauër via llvm-commits llvm-commits at lists.llvm.org
Wed Apr 15 07:57:33 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 1/7] [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 2/7] 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 3/7] 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 4/7] 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 5/7] 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 6/7] 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 7/7] 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



More information about the llvm-commits mailing list