[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