[llvm] 2f8c8cb - [IR] Allocas cannot be freed (#202875)

via llvm-commits llvm-commits at lists.llvm.org
Thu Jun 11 00:12:29 PDT 2026


Author: Nikita Popov
Date: 2026-06-11T09:12:24+02:00
New Revision: 2f8c8cb90e0a0e257689313d38bf78a0f95324b6

URL: https://github.com/llvm/llvm-project/commit/2f8c8cb90e0a0e257689313d38bf78a0f95324b6
DIFF: https://github.com/llvm/llvm-project/commit/2f8c8cb90e0a0e257689313d38bf78a0f95324b6.diff

LOG: [IR] Allocas cannot be freed (#202875)

Make canBeFreed() return false for alloca instructions. This matches the
modelling in getPointerDereferenceableBytes(), and as such only affects
the single other caller, which is loop load PRE (resolving the TODO
there).

allocas remain dereferenceable after lifetime.end, in the sense that
it's safe to speculatively load from them. They only become
non-writable, and I don't think this API is responsible for tracking
that (that would be isWritableObject, where unconditionally returning
true for allocas is technically incorrect and it already has a TODO to
that effect). To the best of my knowledge, the only transform that is
affected by lifetime.end making allocas non-writable is scalar promotion
with store speculation in LICM, so a fix to this issue will either be
localized there, or be part of a full lifetime redesign. In any case, it
should not infect the canBeFreed() API.

Added: 
    

Modified: 
    llvm/lib/IR/Value.cpp
    llvm/test/Transforms/GVN/PRE/pre-loop-load.ll

Removed: 
    


################################################################################
diff  --git a/llvm/lib/IR/Value.cpp b/llvm/lib/IR/Value.cpp
index 69f0a9a5a6dec..6f71a5093e4a3 100644
--- a/llvm/lib/IR/Value.cpp
+++ b/llvm/lib/IR/Value.cpp
@@ -832,6 +832,12 @@ bool Value::canBeFreed() const {
   if (isa<Constant>(this))
     return false;
 
+  // Allocas cannot be freed: They remain dereferenceable after lifetime.end,
+  // in the sense that they can be loaded from without UB. They only become
+  // non-writable, which is not tracked by this API.
+  if (isa<AllocaInst>(this))
+    return false;
+
   // Handle byval/byref/sret/inalloca/preallocated arguments.  The storage
   // lifetime is guaranteed to be longer than the callee's lifetime.
   if (auto *A = dyn_cast<Argument>(this)) {

diff  --git a/llvm/test/Transforms/GVN/PRE/pre-loop-load.ll b/llvm/test/Transforms/GVN/PRE/pre-loop-load.ll
index 9cd6274100c2a..8f1b73ad8383d 100644
--- a/llvm/test/Transforms/GVN/PRE/pre-loop-load.ll
+++ b/llvm/test/Transforms/GVN/PRE/pre-loop-load.ll
@@ -204,25 +204,26 @@ exit:
 
 declare void @may_modify_or_free_pointer(ptr %p)
 
-; TODO: Despite the fact that the function may free memory in general, it
-; cannot free memory allocated by alloca.
 define i32 @test_load_on_cold_path_may_free_memory_alloca() {
 ; CHECK-LABEL: @test_load_on_cold_path_may_free_memory_alloca(
 ; CHECK-NEXT:  entry:
 ; CHECK-NEXT:    [[P:%.*]] = alloca i32, align 4
 ; CHECK-NEXT:    call void @may_modify_or_free_pointer(ptr [[P]])
+; CHECK-NEXT:    [[X_PRE1:%.*]] = load i32, ptr [[P]], align 4
 ; CHECK-NEXT:    br label [[LOOP:%.*]]
 ; CHECK:       loop:
-; CHECK-NEXT:    [[IV:%.*]] = phi i32 [ 0, [[ENTRY:%.*]] ], [ [[IV_NEXT:%.*]], [[BACKEDGE:%.*]] ]
-; CHECK-NEXT:    [[X:%.*]] = load i32, ptr [[P]], align 4
+; CHECK-NEXT:    [[X:%.*]] = phi i32 [ [[X_PRE1]], [[ENTRY:%.*]] ], [ [[X2:%.*]], [[BACKEDGE:%.*]] ]
+; CHECK-NEXT:    [[IV:%.*]] = phi i32 [ 0, [[ENTRY]] ], [ [[IV_NEXT:%.*]], [[BACKEDGE]] ]
 ; CHECK-NEXT:    [[COND:%.*]] = icmp ne i32 [[X]], 0
 ; CHECK-NEXT:    br i1 [[COND]], label [[HOT_PATH:%.*]], label [[COLD_PATH:%.*]]
 ; CHECK:       hot_path:
 ; CHECK-NEXT:    br label [[BACKEDGE]]
 ; CHECK:       cold_path:
 ; CHECK-NEXT:    call void @may_modify_or_free_pointer(ptr [[P]])
+; CHECK-NEXT:    [[X_PRE:%.*]] = load i32, ptr [[P]], align 4
 ; CHECK-NEXT:    br label [[BACKEDGE]]
 ; CHECK:       backedge:
+; CHECK-NEXT:    [[X2]] = phi i32 [ [[X_PRE]], [[COLD_PATH]] ], [ [[X]], [[HOT_PATH]] ]
 ; CHECK-NEXT:    [[IV_NEXT]] = add i32 [[IV]], [[X]]
 ; CHECK-NEXT:    [[LOOP_COND:%.*]] = icmp ult i32 [[IV_NEXT]], 1000
 ; CHECK-NEXT:    br i1 [[LOOP_COND]], label [[LOOP]], label [[EXIT:%.*]]
@@ -420,7 +421,7 @@ define i32 @test_load_on_exiting_cold_path_02(ptr %p) gc "statepoint-example" pe
 ; CHECK-NEXT:    br label [[BACKEDGE]]
 ; CHECK:       cold_path:
 ; CHECK-NEXT:    invoke void @side_effect()
-; CHECK-NEXT:    to label [[BACKEDGE]] unwind label [[COLD_EXIT:%.*]]
+; CHECK-NEXT:            to label [[BACKEDGE]] unwind label [[COLD_EXIT:%.*]]
 ; CHECK:       backedge:
 ; CHECK-NEXT:    [[IV_NEXT]] = add i32 [[IV]], [[X]]
 ; CHECK-NEXT:    [[LOOP_COND:%.*]] = icmp ult i32 [[IV_NEXT]], 1000
@@ -429,7 +430,7 @@ define i32 @test_load_on_exiting_cold_path_02(ptr %p) gc "statepoint-example" pe
 ; CHECK-NEXT:    ret i32 [[X]]
 ; CHECK:       cold_exit:
 ; CHECK-NEXT:    [[LANDING_PAD:%.*]] = landingpad token
-; CHECK-NEXT:    cleanup
+; CHECK-NEXT:            cleanup
 ; CHECK-NEXT:    ret i32 -1
 ;
 entry:
@@ -651,7 +652,7 @@ define i32 @test_inner_loop(ptr %p, i1 %arg) {
 ; CHECK-NEXT:    br label [[INNER_LOOP:%.*]]
 ; CHECK:       inner_loop:
 ; CHECK-NEXT:    call void @side_effect() #[[ATTR0]]
-; CHECK-NEXT:    br i1 %arg, label [[INNER_LOOP]], label [[BACKEDGE]]
+; CHECK-NEXT:    br i1 [[ARG:%.*]], label [[INNER_LOOP]], label [[BACKEDGE]]
 ; CHECK:       backedge:
 ; CHECK-NEXT:    [[IV_NEXT]] = add i32 [[IV]], [[X]]
 ; CHECK-NEXT:    [[LOOP_COND:%.*]] = icmp ult i32 [[IV_NEXT]], 1000


        


More information about the llvm-commits mailing list