[llvm] [Mem2Reg] Remove extraneous getAllocatedType() check (PR #177438)
Jameson Nash via llvm-commits
llvm-commits at lists.llvm.org
Thu Jan 22 11:04:10 PST 2026
https://github.com/vtjnash created https://github.com/llvm/llvm-project/pull/177438
Replace uses of getAllocatedType() in PromoteMemoryToRegister.cpp with type tracking from actual loads and stores. This makes the promotion logic more semantic - it now checks that all loads/stores use a consistent type rather than requiring them to match the alloca's declared type.
Changes:
- isAllocaPromotable() now tracks the first load/store type seen and ensures all subsequent accesses use the same type
- AllocaInfo gains a ValueType field populated during AnalyzeAlloca()
- PromoteMem2Reg tracks AllocaValueTypes alongside other per-alloca info
- PHI nodes and UndefValues are created using the tracked type
This is semantically more permissive - an alloca declared as i64 but only accessed as i32 is now promotable. This is correct because the alloca is just a blob of memory; what matters for Mem2Reg is consistent access patterns.
Test changes:
- asan-stack-safety.ll: Changed loads/stores to volatile to prevent promotion while preserving ASAN stack safety analysis behavior
- SPIRV pointer tests (array-skips-gep.ll, load-struct.ll, store-struct.ll, store-to-array-first-element.ll): Added escape calls to prevent alloca promotion, as these tests verify SPIRV backend handling of Function-storage-class pointers
>From e220e42829c7a59c0f1f29f2cce7a2da04156a94 Mon Sep 17 00:00:00 2001
From: Jameson Nash <vtjnash+github at gmail.com>
Date: Wed, 21 Jan 2026 18:54:41 +0000
Subject: [PATCH] [Mem2Reg] Remove extraneous getAllocatedType() check
Replace uses of getAllocatedType() in PromoteMemoryToRegister.cpp with
type tracking from actual loads and stores. This makes the promotion
logic more semantic - it now checks that all loads/stores use a
consistent type rather than requiring them to match the alloca's
declared type.
Changes:
- isAllocaPromotable() now tracks the first load/store type seen and
ensures all subsequent accesses use the same type
- AllocaInfo gains a ValueType field populated during AnalyzeAlloca()
- PromoteMem2Reg tracks AllocaValueTypes alongside other per-alloca info
- PHI nodes and UndefValues are created using the tracked type
This is semantically more permissive - an alloca declared as i64 but
only accessed as i32 is now promotable. This is correct because the
alloca is just a blob of memory; what matters is consistent access
patterns.
Test fixes:
- asan-stack-safety.ll: Changed loads/stores to volatile to prevent
promotion while preserving ASAN stack safety analysis behavior
- SPIRV pointer tests (array-skips-gep.ll, load-struct.ll,
store-struct.ll, store-to-array-first-element.ll): Added escape
calls to prevent alloca promotion, as these tests verify SPIRV
backend handling of Function-storage-class pointers
Co-Authored-By: Claude Opus 4.5 <noreply at anthropic.com>
---
.../Utils/PromoteMemoryToRegister.cpp | 38 +++++++++++++++----
.../CodeGen/SPIRV/pointers/array-skips-gep.ll | 3 ++
.../CodeGen/SPIRV/pointers/load-struct.ll | 5 +++
.../CodeGen/SPIRV/pointers/store-struct.ll | 8 ++++
.../pointers/store-to-array-first-element.ll | 3 ++
.../AddressSanitizer/asan-stack-safety.ll | 4 +-
6 files changed, 52 insertions(+), 9 deletions(-)
diff --git a/llvm/lib/Transforms/Utils/PromoteMemoryToRegister.cpp b/llvm/lib/Transforms/Utils/PromoteMemoryToRegister.cpp
index d93a4d87f30f4..223f94f2037ee 100644
--- a/llvm/lib/Transforms/Utils/PromoteMemoryToRegister.cpp
+++ b/llvm/lib/Transforms/Utils/PromoteMemoryToRegister.cpp
@@ -65,20 +65,31 @@ STATISTIC(NumPHIInsert, "Number of PHI nodes inserted");
bool llvm::isAllocaPromotable(const AllocaInst *AI) {
// Only allow direct and non-volatile loads and stores...
+ // All loads and stores must use the same type (determined by the first one
+ // seen). We don't require the type to match the alloca's declared type.
+ Type *ExpectedType = nullptr;
for (const User *U : AI->users()) {
if (const LoadInst *LI = dyn_cast<LoadInst>(U)) {
// Note that atomic loads can be transformed; atomic semantics do
// not have any meaning for a local alloca.
- if (LI->isVolatile() || LI->getType() != AI->getAllocatedType())
+ if (LI->isVolatile())
+ return false;
+ if (!ExpectedType)
+ ExpectedType = LI->getType();
+ else if (LI->getType() != ExpectedType)
return false;
} else if (const StoreInst *SI = dyn_cast<StoreInst>(U)) {
- if (SI->getValueOperand() == AI ||
- SI->getValueOperand()->getType() != AI->getAllocatedType())
+ if (SI->getValueOperand() == AI)
return false; // Don't allow a store OF the AI, only INTO the AI.
// Note that atomic stores can be transformed; atomic semantics do
// not have any meaning for a local alloca.
if (SI->isVolatile())
return false;
+ Type *StoreType = SI->getValueOperand()->getType();
+ if (!ExpectedType)
+ ExpectedType = StoreType;
+ else if (StoreType != ExpectedType)
+ return false;
} else if (const IntrinsicInst *II = dyn_cast<IntrinsicInst>(U)) {
if (!II->isLifetimeStartOrEnd() && !II->isDroppable() &&
II->getIntrinsicID() != Intrinsic::fake_use)
@@ -199,6 +210,10 @@ struct AllocaInfo {
BasicBlock *OnlyBlock;
bool OnlyUsedInOneBlock;
+ /// The type used by all loads/stores of this alloca. This may differ from
+ /// the alloca's declared type if all accesses use a different type.
+ Type *ValueType;
+
/// Debug users of the alloca - does not include dbg.assign intrinsics.
DPUserVec DPUsers;
/// Helper to update assignment tracking debug info.
@@ -210,6 +225,7 @@ struct AllocaInfo {
OnlyStore = nullptr;
OnlyBlock = nullptr;
OnlyUsedInOneBlock = true;
+ ValueType = nullptr;
DPUsers.clear();
AssignmentTracking.clear();
}
@@ -229,11 +245,15 @@ struct AllocaInfo {
// Remember the basic blocks which define new values for the alloca
DefiningBlocks.push_back(SI->getParent());
OnlyStore = SI;
+ if (!ValueType)
+ ValueType = SI->getValueOperand()->getType();
} else {
LoadInst *LI = cast<LoadInst>(User);
// Otherwise it must be a load instruction, keep track of variable
// reads.
UsingBlocks.push_back(LI->getParent());
+ if (!ValueType)
+ ValueType = LI->getType();
}
if (OnlyUsedInOneBlock) {
@@ -380,6 +400,8 @@ struct PromoteMem2Reg {
/// For each alloca, keep an instance of a helper class that gives us an easy
/// way to update assignment tracking debug info if the alloca is promoted.
SmallVector<AssignmentTrackingInfo, 8> AllocaATInfo;
+ /// For each alloca, the type used by all loads/stores of this alloca.
+ SmallVector<Type *, 8> AllocaValueTypes;
/// A set of dbg.assigns to delete because they've been demoted to
/// dbg.values. Call cleanUpDbgAssigns to delete them.
SmallPtrSet<DbgVariableRecord *, 8> DVRAssignsToDelete;
@@ -745,6 +767,7 @@ void PromoteMem2Reg::run() {
AllocaATInfo.resize(Allocas.size());
AllocaDPUsers.resize(Allocas.size());
+ AllocaValueTypes.resize(Allocas.size());
AllocaInfo Info;
LargeBlockInfo LBI;
@@ -806,6 +829,7 @@ void PromoteMem2Reg::run() {
AllocaATInfo[AllocaNum] = Info.AssignmentTracking;
if (!Info.DPUsers.empty())
AllocaDPUsers[AllocaNum] = Info.DPUsers;
+ AllocaValueTypes[AllocaNum] = Info.ValueType;
// Keep the reverse mapping of the 'Allocas' array for the rename pass.
AllocaLookup[Allocas[AllocaNum]] = AllocaNum;
@@ -847,7 +871,7 @@ void PromoteMem2Reg::run() {
// been stored yet. In this case, it will get this null value.
IncomingVals.resize(Allocas.size());
for (unsigned i = 0, e = Allocas.size(); i != e; ++i)
- IncomingVals.init(i, UndefValue::get(Allocas[i]->getAllocatedType()));
+ IncomingVals.init(i, UndefValue::get(AllocaValueTypes[i]));
// When handling debug info, treat all incoming values as if they have
// compiler-generated (empty) locations, representing the uninitialized
@@ -1061,9 +1085,9 @@ bool PromoteMem2Reg::QueuePhiNode(BasicBlock *BB, unsigned AllocaNo,
if (PN)
return false;
- // Create a PhiNode using the dereferenced type... and add the phi-node to the
- // BasicBlock.
- PN = PHINode::Create(Allocas[AllocaNo]->getAllocatedType(), getNumPreds(BB),
+ // Create a PhiNode using the type from loads/stores... and add the phi-node
+ // to the BasicBlock.
+ PN = PHINode::Create(AllocaValueTypes[AllocaNo], getNumPreds(BB),
Allocas[AllocaNo]->getName() + "." + Twine(Version++));
PN->insertBefore(BB->begin());
++NumPHIInsert;
diff --git a/llvm/test/CodeGen/SPIRV/pointers/array-skips-gep.ll b/llvm/test/CodeGen/SPIRV/pointers/array-skips-gep.ll
index 3bc07bc137ac6..b2b83f650ecf9 100644
--- a/llvm/test/CodeGen/SPIRV/pointers/array-skips-gep.ll
+++ b/llvm/test/CodeGen/SPIRV/pointers/array-skips-gep.ll
@@ -16,6 +16,7 @@
define internal spir_func i32 @foo() {
%array = alloca [10 x i32], align 4
; CHECK: %[[#array:]] = OpVariable %[[#array_fp:]] Function
+ call void @escape(ptr %array)
; Direct load from the pointer index. This requires an OpAccessChain
%1 = load i32, ptr %array, align 4
@@ -26,6 +27,8 @@ define internal spir_func i32 @foo() {
; CHECK: OpReturnValue %[[#val]]
}
+declare void @escape(ptr)
+
define internal spir_func i32 @bar() {
; Direct load from the pointer index. This requires an OpAccessChain
%1 = load i32, ptr addrspace(10) @gv
diff --git a/llvm/test/CodeGen/SPIRV/pointers/load-struct.ll b/llvm/test/CodeGen/SPIRV/pointers/load-struct.ll
index 280a7e4f9053b..226a80e23f333 100644
--- a/llvm/test/CodeGen/SPIRV/pointers/load-struct.ll
+++ b/llvm/test/CodeGen/SPIRV/pointers/load-struct.ll
@@ -25,6 +25,7 @@
define internal spir_func float @foo() {
%1 = alloca %struct.SF, align 4
; CHECK: %[[#var:]] = OpVariable %[[#sf_fp]] Function
+ call void @escape(ptr %1)
%2 = load float, ptr %1, align 4
; CHECK: %[[#tmp:]] = OpAccessChain %[[#float_fp]] %[[#var]] %[[#uint_0]]
@@ -36,6 +37,7 @@ define internal spir_func float @foo() {
define internal spir_func i32 @bar() {
%1 = alloca %struct.SU, align 4
; CHECK: %[[#var:]] = OpVariable %[[#su_fp]] Function
+ call void @escape(ptr %1)
%2 = load i32, ptr %1, align 4
; CHECK: %[[#tmp:]] = OpAccessChain %[[#uint_fp]] %[[#var]] %[[#uint_0]]
@@ -47,6 +49,7 @@ define internal spir_func i32 @bar() {
define internal spir_func float @baz() {
%1 = alloca %struct.SFUF, align 4
; CHECK: %[[#var:]] = OpVariable %[[#sfuf_fp]] Function
+ call void @escape(ptr %1)
%2 = load float, ptr %1, align 4
; CHECK: %[[#tmp:]] = OpAccessChain %[[#float_fp]] %[[#var]] %[[#uint_0]]
@@ -62,3 +65,5 @@ define internal spir_func float @biz() {
ret float %2
}
+
+declare void @escape(ptr)
diff --git a/llvm/test/CodeGen/SPIRV/pointers/store-struct.ll b/llvm/test/CodeGen/SPIRV/pointers/store-struct.ll
index 1ff53614990d5..effe372b93f83 100644
--- a/llvm/test/CodeGen/SPIRV/pointers/store-struct.ll
+++ b/llvm/test/CodeGen/SPIRV/pointers/store-struct.ll
@@ -42,6 +42,7 @@
define internal spir_func void @foo() {
%1 = alloca %struct.SF, align 4
; CHECK: %[[#var:]] = OpVariable %[[#sf_fp]] Function
+ call void @escape(ptr %1)
store float 0.0, ptr %1, align 4
; CHECK: %[[#tmp:]] = OpInBoundsAccessChain %[[#float_fp]] %[[#var]] %[[#uint_0]]
@@ -53,6 +54,7 @@ define internal spir_func void @foo() {
define internal spir_func void @bar() {
%1 = alloca %struct.SU, align 4
; CHECK: %[[#var:]] = OpVariable %[[#su_fp]] Function
+ call void @escape(ptr %1)
store i32 0, ptr %1, align 4
; CHECK: %[[#tmp:]] = OpInBoundsAccessChain %[[#uint_fp]] %[[#var]] %[[#uint_0]]
@@ -64,6 +66,7 @@ define internal spir_func void @bar() {
define internal spir_func void @baz() {
%1 = alloca %struct.SFUF, align 4
; CHECK: %[[#var:]] = OpVariable %[[#sfuf_fp]] Function
+ call void @escape(ptr %1)
store float 0.0, ptr %1, align 4
; CHECK: %[[#tmp:]] = OpInBoundsAccessChain %[[#float_fp]] %[[#var]] %[[#uint_0]]
@@ -83,6 +86,7 @@ define internal spir_func void @biz() {
define internal spir_func void @nested_store() {
%1 = alloca %struct.SSU, align 4
; CHECK: %[[#var:]] = OpVariable %[[#ssu_fp]] Function
+ call void @escape(ptr %1)
store i32 0, ptr %1, align 4
; CHECK: %[[#tmp:]] = OpInBoundsAccessChain %[[#uint_fp]] %[[#var]] %[[#uint_0]] %[[#uint_0]]
@@ -94,6 +98,7 @@ define internal spir_func void @nested_store() {
define internal spir_func void @nested_store_vector() {
%1 = alloca %struct.SSV, align 4
; CHECK: %[[#var:]] = OpVariable %[[#ssv_fp]] Function
+ call void @escape(ptr %1)
store i32 0, ptr %1, align 4
; CHECK: %[[#tmp:]] = OpInBoundsAccessChain %[[#uint_fp]] %[[#var]] %[[#uint_0]] %[[#uint_0]] %[[#uint_0]]
@@ -105,6 +110,7 @@ define internal spir_func void @nested_store_vector() {
define internal spir_func void @nested_array_vector() {
%1 = alloca %struct.SSASSV, align 4
; CHECK: %[[#var:]] = OpVariable %[[#ssassv_fp]] Function
+ call void @escape(ptr %1)
store i32 0, ptr %1, align 4
; CHECK: %[[#tmp:]] = OpInBoundsAccessChain %[[#uint_fp]] %[[#var]] %[[#uint_0]] %[[#uint_0]] %[[#uint_0]] %[[#uint_0]] %[[#uint_0]] %[[#uint_0]]
@@ -112,3 +118,5 @@ define internal spir_func void @nested_array_vector() {
ret void
}
+
+declare void @escape(ptr)
diff --git a/llvm/test/CodeGen/SPIRV/pointers/store-to-array-first-element.ll b/llvm/test/CodeGen/SPIRV/pointers/store-to-array-first-element.ll
index 2369c51799993..8ec7fa9c94da7 100644
--- a/llvm/test/CodeGen/SPIRV/pointers/store-to-array-first-element.ll
+++ b/llvm/test/CodeGen/SPIRV/pointers/store-to-array-first-element.ll
@@ -15,6 +15,9 @@
define spir_func void @test_array_store() {
entry:
%var = alloca [4 x i32]
+ call void @escape(ptr %var)
store i32 123, ptr %var
ret void
}
+
+declare void @escape(ptr)
diff --git a/llvm/test/Instrumentation/AddressSanitizer/asan-stack-safety.ll b/llvm/test/Instrumentation/AddressSanitizer/asan-stack-safety.ll
index 8210970aaaeb4..f94f3d4e6fb4e 100644
--- a/llvm/test/Instrumentation/AddressSanitizer/asan-stack-safety.ll
+++ b/llvm/test/Instrumentation/AddressSanitizer/asan-stack-safety.ll
@@ -7,7 +7,7 @@
define i32 @load() sanitize_address {
%buf = alloca [10 x i8], align 1
; NOSAFETY: call i64 @__asan_stack_malloc
- %1 = load i8, ptr %buf, align 1
+ %1 = load volatile i8, ptr %buf, align 1
; NOSAFETY: call void @__asan_load1
ret i32 0
}
@@ -16,7 +16,7 @@ define i32 @load() sanitize_address {
define i32 @store() sanitize_address {
%buf = alloca [10 x i8], align 1
; NOSAFETY: call i64 @__asan_stack_malloc
- store i8 0, ptr %buf
+ store volatile i8 0, ptr %buf
; NOSAFETY: call void @__asan_store1
ret i32 0
}
More information about the llvm-commits
mailing list