[llvm] 03b5c52 - [LSROA] Add logical SROA pass (#192058)
via llvm-commits
llvm-commits at lists.llvm.org
Mon Jul 20 01:42:39 PDT 2026
Author: Nathan Gauër
Date: 2026-07-20T10:42:34+02:00
New Revision: 03b5c5285eaf0acdc1dc30c5be6e0a7cefeab16f
URL: https://github.com/llvm/llvm-project/commit/03b5c5285eaf0acdc1dc30c5be6e0a7cefeab16f
DIFF: https://github.com/llvm/llvm-project/commit/03b5c5285eaf0acdc1dc30c5be6e0a7cefeab16f.diff
LOG: [LSROA] Add logical SROA pass (#192058)
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.
---------
Co-authored-by: Nikita Popov <npopov at redhat.com>
Added:
llvm/include/llvm/Transforms/Scalar/LogicalSROA.h
llvm/lib/Transforms/Scalar/LogicalSROA.cpp
llvm/test/Transforms/LSROA/array.ll
llvm/test/Transforms/LSROA/basictest.ll
llvm/test/Transforms/LSROA/escaping.ll
llvm/test/Transforms/LSROA/nesting.ll
llvm/test/Transforms/LSROA/normal-gep.ll
llvm/test/Transforms/LSROA/phi.ll
llvm/test/Transforms/LSROA/select.ll
Modified:
llvm/docs/LangRef.md
llvm/include/llvm/IR/IntrinsicInst.h
llvm/include/llvm/InitializePasses.h
llvm/lib/Passes/PassBuilder.cpp
llvm/lib/Passes/PassRegistry.def
llvm/lib/Transforms/Scalar/CMakeLists.txt
Removed:
################################################################################
diff --git a/llvm/docs/LangRef.md b/llvm/docs/LangRef.md
index 9526cd020d724..5275cf87d7cad 100644
--- a/llvm/docs/LangRef.md
+++ b/llvm/docs/LangRef.md
@@ -14994,6 +14994,28 @@ 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:
+
+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.
+
+```llvm
+ %S = { i32, i32 } ; assuming these are laid out next to each other
+ ; and sizeof(i32) < sizeof(64).
+
+ %ptr0 = call ptr @llvm.structured.gep(ptr elementtype(%S) %src, i32 0)
+ %field0 = load i64, ptr %ptr0 ; undefined behavior, because the access
+ ; crosses into the second field.
+```
+
+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:
**Simple case: logical access of a struct field**
diff --git a/llvm/include/llvm/IR/IntrinsicInst.h b/llvm/include/llvm/IR/IntrinsicInst.h
index 214b0f96afbf9..6d83f9993f472 100644
--- a/llvm/include/llvm/IR/IntrinsicInst.h
+++ b/llvm/include/llvm/IR/IntrinsicInst.h
@@ -1869,6 +1869,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/InitializePasses.h b/llvm/include/llvm/InitializePasses.h
index 4280d35e53405..f913937f259af 100644
--- a/llvm/include/llvm/InitializePasses.h
+++ b/llvm/include/llvm/InitializePasses.h
@@ -173,6 +173,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 &);
diff --git a/llvm/include/llvm/Transforms/Scalar/LogicalSROA.h b/llvm/include/llvm/Transforms/Scalar/LogicalSROA.h
new file mode 100644
index 0000000000000..786d8911e639f
--- /dev/null
+++ b/llvm/include/llvm/Transforms/Scalar/LogicalSROA.h
@@ -0,0 +1,35 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_LOGICALSROA_H
+#define LLVM_TRANSFORMS_SCALAR_LOGICALSROA_H
+
+#include "llvm/IR/PassManager.h"
+
+namespace llvm {
+
+class Function;
+
+class LogicalSROAPass : public PassInfoMixin<LogicalSROAPass> {
+public:
+ LogicalSROAPass();
+
+ /// Run the pass over the function.
+ PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
+};
+
+} // end namespace llvm
+
+#endif // LLVM_TRANSFORMS_SCALAR_LOGICALSROA_H
diff --git a/llvm/lib/Passes/PassBuilder.cpp b/llvm/lib/Passes/PassBuilder.cpp
index 48a5108355c32..9ff5e890674d2 100644
--- a/llvm/lib/Passes/PassBuilder.cpp
+++ b/llvm/lib/Passes/PassBuilder.cpp
@@ -309,6 +309,7 @@
#include "llvm/Transforms/Scalar/JumpTableToSwitch.h"
#include "llvm/Transforms/Scalar/JumpThreading.h"
#include "llvm/Transforms/Scalar/LICM.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 ec5657d3208e9..6a2992a768cde 100644
--- a/llvm/lib/Passes/PassRegistry.def
+++ b/llvm/lib/Passes/PassRegistry.def
@@ -479,6 +479,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", 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 4f6b208c2e729..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
diff --git a/llvm/lib/Transforms/Scalar/LogicalSROA.cpp b/llvm/lib/Transforms/Scalar/LogicalSROA.cpp
new file mode 100644
index 0000000000000..a0ed5cd0681d7
--- /dev/null
+++ b/llvm/lib/Transforms/Scalar/LogicalSROA.cpp
@@ -0,0 +1,228 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 but for logical pointers.
+/// It tries to identify promotable elements of an aggregate alloca, and
+/// promote them to multiple allocas of scalar type.
+///
+/// FIXME: nested aggregates are not fully optimized (#192619).
+/// FIXME: array are not optimized (#192620).
+///
+//===----------------------------------------------------------------------===//
+
+#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"
+#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 "logical-sroa"
+
+// Return all lifetime intrinsics with the instruction I as operand.
+static SmallVector<LifetimeIntrinsic *>
+collectLifetimeIntrinsicsUsing(Instruction &I) {
+ SmallVector<LifetimeIntrinsic *> Output;
+
+ for (User *U : I.users()) {
+ if (auto *LI = dyn_cast<LifetimeIntrinsic>(U))
+ Output.push_back(LI);
+ }
+
+ return Output;
+}
+
+// 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;
+
+ // 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.
+// 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()) {
+ if (isa<LifetimeIntrinsic>(U))
+ continue;
+
+ auto *SGEP = cast<StructuredGEPInst>(U);
+
+ // IR rule: SGEP on struct can only use constant int as indices.
+ ConstantInt *Index = cast<ConstantInt>(SGEP->getIndexOperand(0));
+ assert(Index->getZExtValue() < Output.size());
+ Output[Index->getZExtValue()].push_back(SGEP);
+ }
+
+ return Output;
+}
+
+// For each lifetime intrinsic in LifetimeIntrinsics, creates a new one, but
+// uses V as operand.
+static void copyLifetimeIntrinsicFor(IRBuilder<> &B, LifetimeIntrinsic *II,
+ Value *V) {
+ B.SetInsertPoint(II);
+
+ if (II->getIntrinsicID() == Intrinsic::lifetime_start) {
+ B.CreateLifetimeStart(V);
+ } else if (II->getIntrinsicID() == Intrinsic::lifetime_end) {
+ B.CreateLifetimeEnd(V);
+ } else
+ llvm_unreachable("invalid argument: expected a lifetime intrinsic");
+}
+
+static void rewriteSGEPChain(IRBuilder<> &B, StructuredGEPInst *SGEP,
+ StructuredAllocaInst *FieldAlloca) {
+ if (SGEP->getNumIndices() == 1) {
+ SGEP->replaceAllUsesWith(FieldAlloca);
+ SGEP->eraseFromParent();
+ return;
+ }
+
+ SmallVector<Value *, 4> Indices(llvm::drop_begin(SGEP->indices()));
+ B.SetInsertPoint(SGEP);
+ auto *I = B.CreateStructuredGEP(FieldAlloca->getAllocationType(), FieldAlloca,
+ Indices, SGEP->getName());
+ SGEP->replaceAllUsesWith(I);
+ SGEP->eraseFromParent();
+}
+
+static bool runOnStructuredAlloca(StructuredAllocaInst &SAI) {
+ // For now, LogicalSROA only handles SGEP on structs.
+ StructType *ST = dyn_cast<StructType>(SAI.getAllocationType());
+ if (!ST)
+ return false;
+
+ 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)) {
+ if (Users.empty())
+ continue;
+
+ B.SetInsertPoint(&SAI);
+ auto *FieldAlloca = cast<StructuredAllocaInst>(
+ B.CreateStructuredAlloca(ST->getElementType(FieldIndex)));
+
+ for (auto II : LifetimeIntrinsics)
+ copyLifetimeIntrinsicFor(B, II, FieldAlloca);
+
+ for (StructuredGEPInst *SGEP : Users)
+ rewriteSGEPChain(B, SGEP, FieldAlloca);
+ }
+
+ for (auto *II : LifetimeIntrinsics)
+ II->eraseFromParent();
+ SAI.eraseFromParent();
+ return true;
+}
+
+static bool runLogicalSROA(Function &F) {
+ SmallVector<StructuredAllocaInst *> Worklist;
+ BasicBlock &EntryBB = F.getEntryBlock();
+ for (Instruction &I : EntryBB) {
+ if (StructuredAllocaInst *SAI = dyn_cast<StructuredAllocaInst>(&I))
+ Worklist.push_back(SAI);
+ }
+
+ bool Changed = false;
+ for (StructuredAllocaInst *SAI : Worklist)
+ Changed |= runOnStructuredAlloca(*SAI);
+ return Changed;
+}
+
+PreservedAnalyses LogicalSROAPass::run(Function &F,
+ FunctionAnalysisManager &AM) {
+ if (!runLogicalSROA(F))
+ return PreservedAnalyses::all();
+
+ PreservedAnalyses PA;
+ PA.preserveSet<CFGAnalyses>();
+ return PA;
+}
+
+LogicalSROAPass::LogicalSROAPass() {}
diff --git a/llvm/test/Transforms/LSROA/array.ll b/llvm/test/Transforms/LSROA/array.ll
new file mode 100644
index 0000000000000..d488d90a38424
--- /dev/null
+++ b/llvm/test/Transforms/LSROA/array.ll
@@ -0,0 +1,78 @@
+; 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, ...)
+
+define i32 @test_simple_array() {
+; 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)
+ store i32 0, ptr %ptr
+ %res = load i32, ptr %ptr
+ 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
new file mode 100644
index 0000000000000..2042590b159a4
--- /dev/null
+++ b/llvm/test/Transforms/LSROA/basictest.ll
@@ -0,0 +1,147 @@
+; 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, ...)
+
+define i32 @test_simple_scalar() {
+; 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)
+ store i32 0, ptr %tmp
+ %res = load i32, ptr %tmp
+ call void @llvm.lifetime.end.p0(ptr %tmp)
+ ret i32 %res
+}
+
+define i32 @test_simple_struct_entire_write_read() {
+; 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)
+ %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
+}
+
+define i32 @test_simple_struct_aliasing() {
+; 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)
+ %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
+}
+
+define i32 @test_simple_struct_partial_write_read() {
+; 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)
+ %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)
+
+ ret i32 %a
+}
+
+define i32 @test_struct_use_across_lifetime() {
+; 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)
+
+ 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)
+
+ 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)
+
+ %c = add i32 %b, %a
+ ret i32 %c
+}
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/nesting.ll b/llvm/test/Transforms/LSROA/nesting.ll
new file mode 100644
index 0000000000000..5291cfe4ebee0
--- /dev/null
+++ b/llvm/test/Transforms/LSROA/nesting.ll
@@ -0,0 +1,39 @@
+; 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, ...)
+
+%S = type { i32, { i32, i32 } }
+
+; TODO: It is fine to split the nested struct and discard the second element.
+; simply not implemented yet.
+define i32 @test_nested_struct() {
+; 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)
+
+
+ store i32 0, ptr %0
+ store i32 1, ptr %1
+ %a = load i32, ptr %0
+ %b = load i32, ptr %1
+
+ %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..4358d9790c2bd
--- /dev/null
+++ b/llvm/test/Transforms/LSROA/normal-gep.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
+
+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: [[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)
+
+ store i32 0, ptr %0
+ store i32 1, ptr %1
+ %a = load i32, ptr %0
+ %b = load i32, ptr %1
+
+ %c = add i32 %a, %b
+ ret i32 %c
+}
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
+}
More information about the llvm-commits
mailing list