[llvm] [ValueTracking] Support multiple predecessors in willNotFreeBetween (PR #223580)

Vy Nguyen via llvm-commits llvm-commits at lists.llvm.org
Mon Sep 21 09:52:58 PDT 2026


https://github.com/oontvoo updated https://github.com/llvm/llvm-project/pull/223580

>From bdb114f0ad42bf2d373242a3961b8735dbded96a Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Mon, 14 Sep 2026 22:14:37 -0400
Subject: [PATCH 01/11] [ValueTracking] Support multiple predecessors in
 willNotFreeBetween

Previously, `willNotFreeBetween` only walked backward along a linear chain of single-predecessor blocks, bailing out whenever control flow branched and merged (eg., conditional `if-else` blocks before a loop preheader).

This PR  generalises the backward walk to use a worklist over all predecessors from `CtxBB` to `AssumeBB` (bounded by `MaxInstrsToCheckForFree`).
This enables LICM( and possibly other passes) to prove dereferenceability and hoist invariant loads across merging control flow when no path contains a freeing instruction.
---
 llvm/lib/Analysis/ValueTracking.cpp           | 38 +++++++++++--------
 llvm/unittests/Analysis/ValueTrackingTest.cpp | 34 +++++++++++++++++
 2 files changed, 57 insertions(+), 15 deletions(-)

diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index ac49dbc8d76d3a..472582f9b383d4 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -761,22 +761,30 @@ bool llvm::willNotFreeBetween(const Instruction *Assume,
     return hasNoFreeInRange(make_range(Assume->getIterator(), CtxIter));
   }
 
-  // Handle chain of single-predecessor blocks.
-  const BasicBlock *CurBB = CtxBB;
-  while (true) {
-    if (CurBB == AssumeBB)
-      return hasNoFreeInRange(
-          make_range(Assume->getIterator(), AssumeBB->end()));
-
-    const BasicBlock *PredBB = CurBB->getSinglePredecessor();
-    if (!PredBB)
-      return false;
-
-    if (!hasNoFreeInRange(make_range(CurBB->begin(),
-                                     CurBB == CtxBB ? CtxIter : CurBB->end())))
-      return false;
-    CurBB = PredBB;
+  // Multi-predecessor worklist.
+  SmallVector<const BasicBlock *, 8> Worklist;
+  SmallPtrSet<const BasicBlock *, 8> Visited;
+  Worklist.push_back(CtxBB);
+  Visited.insert(CtxBB);
+  while (!Worklist.empty()) {
+      const BasicBlock *CurBB = Worklist.pop_back_val();
+      if (CurBB == AssumeBB) {
+        if (!hasNoFreeInRange(
+			      make_range(Assume->getIterator(), AssumeBB->end())))
+          return false;
+        continue;
+      }
+      if (!hasNoFreeInRange(make_range(
+				       CurBB->begin(), CurBB == CtxBB ? CtxIter : CurBB->end())))
+        return false;
+      if (pred_empty(CurBB))
+        return false;
+      for (const BasicBlock *Pred : predecessors(CurBB)) {
+        if (Visited.insert(Pred).second)
+          Worklist.push_back(Pred);
+      }
   }
+  return true;
 }
 
 // TODO: cmpExcludesZero misses many cases where `RHS` is non-constant but
diff --git a/llvm/unittests/Analysis/ValueTrackingTest.cpp b/llvm/unittests/Analysis/ValueTrackingTest.cpp
index 70731e34185de1..4aa9beca62fe0f 100644
--- a/llvm/unittests/Analysis/ValueTrackingTest.cpp
+++ b/llvm/unittests/Analysis/ValueTrackingTest.cpp
@@ -3412,6 +3412,40 @@ TEST_F(ValueTrackingTest, HaveNoCommonBitsSet) {
   }
 }
 
+TEST_F(ValueTrackingTest, willNotFreeBetweenMultiPredecessor) {
+  StringRef Assembly = R"(
+    declare void @llvm.assume(i1)
+    declare void @may_free()
+    declare void @no_free() nofree
+    define void @test(i1 %cond, ptr %p) {
+    entry:
+      %A = call void @llvm.assume(i1 true) [ "dereferenceable"(ptr %p, i64 16) ]
+      br i1 %cond, label %if.then, label %if.else
+    if.then:
+      call void @no_free()
+      br label %merge
+    if.else:
+      br label %merge
+    merge:
+      %CxtI = load i32, ptr %p
+      br i1 %cond, label %if.then2, label %if.else2
+    if.then2:
+      call void @may_free()
+      br label %merge2
+    if.else2:
+      br label %merge2
+    merge2:
+      %CxtI2 = load i32, ptr %p
+      ret void
+    }
+  )";
+  parseAssembly(Assembly);
+  // Both paths from %A to %CxtI only call nofree functions -> should return true.
+  EXPECT_TRUE(willNotFreeBetween(A, CxtI));
+  // One path from %A to %CxtI2 calls @may_free() -> should return false.
+  EXPECT_FALSE(willNotFreeBetween(A, CxtI2));
+}
+
 class IsBytewiseValueTest : public ValueTrackingTest,
                             public ::testing::WithParamInterface<
                                 std::pair<const char *, const char *>> {

>From 5a20056044c6af328951ae74aae73cd22e3add21 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Mon, 14 Sep 2026 22:35:01 -0400
Subject: [PATCH 02/11] formatting

---
 llvm/lib/Analysis/ValueTracking.cpp           | 29 +++++++++----------
 llvm/unittests/Analysis/ValueTrackingTest.cpp |  3 +-
 2 files changed, 16 insertions(+), 16 deletions(-)

diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index 472582f9b383d4..ae7a29a34cd1eb 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -767,22 +767,21 @@ bool llvm::willNotFreeBetween(const Instruction *Assume,
   Worklist.push_back(CtxBB);
   Visited.insert(CtxBB);
   while (!Worklist.empty()) {
-      const BasicBlock *CurBB = Worklist.pop_back_val();
-      if (CurBB == AssumeBB) {
-        if (!hasNoFreeInRange(
-			      make_range(Assume->getIterator(), AssumeBB->end())))
-          return false;
-        continue;
-      }
-      if (!hasNoFreeInRange(make_range(
-				       CurBB->begin(), CurBB == CtxBB ? CtxIter : CurBB->end())))
+    const BasicBlock *CurBB = Worklist.pop_back_val();
+    if (CurBB == AssumeBB) {
+      if (!hasNoFreeInRange(make_range(Assume->getIterator(), AssumeBB->end())))
         return false;
-      if (pred_empty(CurBB))
-        return false;
-      for (const BasicBlock *Pred : predecessors(CurBB)) {
-        if (Visited.insert(Pred).second)
-          Worklist.push_back(Pred);
-      }
+      continue;
+    }
+    if (!hasNoFreeInRange(make_range(CurBB->begin(),
+                                     CurBB == CtxBB ? CtxIter : CurBB->end())))
+      return false;
+    if (pred_empty(CurBB))
+      return false;
+    for (const BasicBlock *Pred : predecessors(CurBB)) {
+      if (Visited.insert(Pred).second)
+        Worklist.push_back(Pred);
+    }
   }
   return true;
 }
diff --git a/llvm/unittests/Analysis/ValueTrackingTest.cpp b/llvm/unittests/Analysis/ValueTrackingTest.cpp
index 4aa9beca62fe0f..35135d12b22266 100644
--- a/llvm/unittests/Analysis/ValueTrackingTest.cpp
+++ b/llvm/unittests/Analysis/ValueTrackingTest.cpp
@@ -3440,7 +3440,8 @@ TEST_F(ValueTrackingTest, willNotFreeBetweenMultiPredecessor) {
     }
   )";
   parseAssembly(Assembly);
-  // Both paths from %A to %CxtI only call nofree functions -> should return true.
+  // Both paths from %A to %CxtI only call nofree functions -> should return
+  // true.
   EXPECT_TRUE(willNotFreeBetween(A, CxtI));
   // One path from %A to %CxtI2 calls @may_free() -> should return false.
   EXPECT_FALSE(willNotFreeBetween(A, CxtI2));

>From 641281794fd766b386a4f25b1927e6b8ad84a673 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Tue, 15 Sep 2026 17:51:46 -0400
Subject: [PATCH 03/11] address review comments:  - handle loop back edges  -
 replace unit test with lit test  - update other tests

---
 llvm/lib/Analysis/ValueTracking.cpp           | 54 ++++++++++---------
 .../ValueTracking/memory-dereferenceable.ll   | 43 +++++++++++++++
 .../LICM/hoist-speculatable-load.ll           |  2 +-
 ...able-info-from-assumption-constant-size.ll |  6 +--
 llvm/unittests/Analysis/ValueTrackingTest.cpp | 35 ------------
 5 files changed, 77 insertions(+), 63 deletions(-)

diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index ae7a29a34cd1eb..2d4ac35d8bc90b 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -733,49 +733,55 @@ bool llvm::isValidAssumeForContext(const Instruction *Inv,
   return false;
 }
 
+static bool hasNoFreeInRange(BasicBlock::const_iterator Begin,
+                             BasicBlock::const_iterator End,
+                             unsigned &NumChecked) {
+  for (const Instruction &I : make_range(Begin, End)) {
+    if (NumChecked++ > MaxInstrsToCheckForFree)
+      return false;
+    if (auto *CB = dyn_cast<CallBase>(&I)) {
+      if (!CB->hasFnAttr(Attribute::NoFree))
+        return false;
+    } else if (I.maySynchronize())
+      return false;
+  }
+  return true;
+}
+
 bool llvm::willNotFreeBetween(const Instruction *Assume,
                               const Instruction *CtxI) {
-  // Helper to check if there are any calls in the range that may free memory.
   unsigned NumChecked = 0;
-  auto hasNoFreeInRange = [&NumChecked](auto Range) {
-    for (const Instruction &I : Range) {
-      if (NumChecked++ > MaxInstrsToCheckForFree)
-        return false;
-
-      if (auto *CB = dyn_cast<CallBase>(&I)) {
-        if (!CB->hasFnAttr(Attribute::NoFree))
-          return false;
-      } else if (I.maySynchronize())
-        return false;
-    }
-    return true;
-  };
-
   const BasicBlock *CtxBB = CtxI->getParent();
   const BasicBlock *AssumeBB = Assume->getParent();
   BasicBlock::const_iterator CtxIter = CtxI->getIterator();
   if (CtxBB == AssumeBB) {
-    // Same block case: check that Assume comes before CtxI.
     if (Assume != CtxI && !Assume->comesBefore(CtxI))
       return false;
-    return hasNoFreeInRange(make_range(Assume->getIterator(), CtxIter));
+    return hasNoFreeInRange(Assume->getIterator(), CtxIter, NumChecked);
   }
-
-  // Multi-predecessor worklist.
+  // Check instructions before CtxI in CtxBB.
+  if (!hasNoFreeInRange(CtxBB->begin(), CtxIter, NumChecked))
+    return false;
+  if (pred_empty(CtxBB))
+    return false;
   SmallVector<const BasicBlock *, 8> Worklist;
   SmallPtrSet<const BasicBlock *, 8> Visited;
-  Worklist.push_back(CtxBB);
-  Visited.insert(CtxBB);
+  for (const BasicBlock *Pred : predecessors(CtxBB)) {
+    if (Visited.insert(Pred).second)
+      Worklist.push_back(Pred);
+  }
   while (!Worklist.empty()) {
     const BasicBlock *CurBB = Worklist.pop_back_val();
     if (CurBB == AssumeBB) {
-      if (!hasNoFreeInRange(make_range(Assume->getIterator(), AssumeBB->end())))
+      if (!hasNoFreeInRange(Assume->getIterator(), AssumeBB->end(), NumChecked))
         return false;
       continue;
     }
-    if (!hasNoFreeInRange(make_range(CurBB->begin(),
-                                     CurBB == CtxBB ? CtxIter : CurBB->end())))
+    if (!hasNoFreeInRange(CurBB == CtxBB ? CtxIter : CurBB->begin(),
+                          CurBB->end(), NumChecked))
       return false;
+    if (CurBB == CtxBB)
+      continue;
     if (pred_empty(CurBB))
       return false;
     for (const BasicBlock *Pred : predecessors(CurBB)) {
diff --git a/llvm/test/Analysis/ValueTracking/memory-dereferenceable.ll b/llvm/test/Analysis/ValueTracking/memory-dereferenceable.ll
index 9fbf27c3d7e72f..299e9d5e4496f3 100644
--- a/llvm/test/Analysis/ValueTracking/memory-dereferenceable.ll
+++ b/llvm/test/Analysis/ValueTracking/memory-dereferenceable.ll
@@ -411,6 +411,49 @@ declare ptr addrspace(1) @func1(ptr addrspace(1) returned) nounwind argmemonly
 ; Can free any object accessible in memory
 declare void @mayfree()
 
+; CHECK-LABEL: 'dereferenceable_arg_multi_pred_nofree'
+; CHECK: %a
+define void @dereferenceable_arg_multi_pred_nofree(ptr dereferenceable(16) %a, i1 %cond) {
+entry:
+  br i1 %cond, label %if.then, label %if.else
+if.then:
+  call void @mayfree() nofree
+  br label %merge
+if.else:
+  br label %merge
+merge:
+  %v = load i32, ptr %a
+  ret void
+}
+; CHECK-LABEL: 'dereferenceable_arg_multi_pred_freed'
+; GLOBAL: %a
+; POINT-NOT: %a
+define void @dereferenceable_arg_multi_pred_freed(ptr dereferenceable(16) %a, i1 %cond) {
+entry:
+  br i1 %cond, label %if.then, label %if.else
+if.then:
+  call void @mayfree()
+  br label %merge
+if.else:
+  br label %merge
+merge:
+  %v = load i32, ptr %a
+  ret void
+}
+; CHECK-LABEL: 'dereferenceable_arg_loop_backedge_freed'
+; GLOBAL: %a
+; POINT-NOT: %a
+define void @dereferenceable_arg_loop_backedge_freed(ptr dereferenceable(16) %a, i1 %again) {
+entry:
+  br label %loop
+loop:
+  %v = load i32, ptr %a
+  call void @mayfree()
+  br i1 %again, label %loop, label %exit
+exit:
+  ret void
+}
+
 !0 = !{i64 4}
 !1 = !{i64 2}
 !2 = !{}
diff --git a/llvm/test/Transforms/LICM/hoist-speculatable-load.ll b/llvm/test/Transforms/LICM/hoist-speculatable-load.ll
index ab754a1f9a9d64..05c3982e080e6f 100644
--- a/llvm/test/Transforms/LICM/hoist-speculatable-load.ll
+++ b/llvm/test/Transforms/LICM/hoist-speculatable-load.ll
@@ -66,12 +66,12 @@ define void @f_nofree_nosync(i32 %ptr_i, ptr %ptr2, i1 %cond) nofree nosync {
 ; CHECK-NEXT:    store i32 0, ptr [[PTR2:%.*]], align 4
 ; CHECK-NEXT:    br label [[FOR_BODY_LR_PH]]
 ; CHECK:       for.body.lr.ph:
+; CHECK-NEXT:    [[TMP0:%.*]] = load i32, ptr [[PTR]], align 4
 ; CHECK-NEXT:    br label [[FOR_BODY:%.*]]
 ; CHECK:       for.body:
 ; CHECK-NEXT:    [[I_08:%.*]] = phi i32 [ 0, [[FOR_BODY_LR_PH]] ], [ [[INC:%.*]], [[IF_END:%.*]] ]
 ; CHECK-NEXT:    br i1 [[COND]], label [[IF_END]], label [[IF:%.*]]
 ; CHECK:       if:
-; CHECK-NEXT:    [[TMP0:%.*]] = load i32, ptr [[PTR]], align 4, !invariant.load [[META0]]
 ; CHECK-NEXT:    store i32 [[TMP0]], ptr [[PTR2]], align 4
 ; CHECK-NEXT:    br label [[IF_END]]
 ; CHECK:       if.end:
diff --git a/llvm/test/Transforms/LoopVectorize/dereferenceable-info-from-assumption-constant-size.ll b/llvm/test/Transforms/LoopVectorize/dereferenceable-info-from-assumption-constant-size.ll
index 21a2dd94542b88..20e49af5e4b7ea 100644
--- a/llvm/test/Transforms/LoopVectorize/dereferenceable-info-from-assumption-constant-size.ll
+++ b/llvm/test/Transforms/LoopVectorize/dereferenceable-info-from-assumption-constant-size.ll
@@ -1182,9 +1182,9 @@ define void @deref_assumption_in_header_constant_trip_count_multiple_loop_predec
 ; CHECK-NEXT:    [[TMP0:%.*]] = getelementptr i32, ptr [[A]], i64 [[INDEX]]
 ; CHECK-NEXT:    [[TMP1:%.*]] = getelementptr inbounds i32, ptr [[B]], i64 [[INDEX]]
 ; CHECK-NEXT:    [[WIDE_LOAD:%.*]] = load <2 x i32>, ptr [[TMP1]], align 4
-; CHECK-NEXT:    [[TMP2:%.*]] = icmp slt <2 x i32> [[WIDE_LOAD]], zeroinitializer
-; CHECK-NEXT:    [[WIDE_MASKED_LOAD:%.*]] = call <2 x i32> @llvm.masked.load.v2i32.p0(ptr align 4 [[TMP0]], <2 x i1> [[TMP2]], <2 x i32> poison)
-; CHECK-NEXT:    [[PREDPHI:%.*]] = select <2 x i1> [[TMP2]], <2 x i32> [[WIDE_MASKED_LOAD]], <2 x i32> [[WIDE_LOAD]]
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp sge <2 x i32> [[WIDE_LOAD]], zeroinitializer
+; CHECK-NEXT:    [[WIDE_LOAD1:%.*]] = load <2 x i32>, ptr [[TMP0]], align 4
+; CHECK-NEXT:    [[PREDPHI:%.*]] = select <2 x i1> [[TMP2]], <2 x i32> [[WIDE_LOAD]], <2 x i32> [[WIDE_LOAD1]]
 ; CHECK-NEXT:    [[TMP3:%.*]] = getelementptr inbounds i32, ptr [[C]], i64 [[INDEX]]
 ; CHECK-NEXT:    store <2 x i32> [[PREDPHI]], ptr [[TMP3]], align 4
 ; CHECK-NEXT:    [[INDEX_NEXT]] = add nuw i64 [[INDEX]], 2
diff --git a/llvm/unittests/Analysis/ValueTrackingTest.cpp b/llvm/unittests/Analysis/ValueTrackingTest.cpp
index 35135d12b22266..70731e34185de1 100644
--- a/llvm/unittests/Analysis/ValueTrackingTest.cpp
+++ b/llvm/unittests/Analysis/ValueTrackingTest.cpp
@@ -3412,41 +3412,6 @@ TEST_F(ValueTrackingTest, HaveNoCommonBitsSet) {
   }
 }
 
-TEST_F(ValueTrackingTest, willNotFreeBetweenMultiPredecessor) {
-  StringRef Assembly = R"(
-    declare void @llvm.assume(i1)
-    declare void @may_free()
-    declare void @no_free() nofree
-    define void @test(i1 %cond, ptr %p) {
-    entry:
-      %A = call void @llvm.assume(i1 true) [ "dereferenceable"(ptr %p, i64 16) ]
-      br i1 %cond, label %if.then, label %if.else
-    if.then:
-      call void @no_free()
-      br label %merge
-    if.else:
-      br label %merge
-    merge:
-      %CxtI = load i32, ptr %p
-      br i1 %cond, label %if.then2, label %if.else2
-    if.then2:
-      call void @may_free()
-      br label %merge2
-    if.else2:
-      br label %merge2
-    merge2:
-      %CxtI2 = load i32, ptr %p
-      ret void
-    }
-  )";
-  parseAssembly(Assembly);
-  // Both paths from %A to %CxtI only call nofree functions -> should return
-  // true.
-  EXPECT_TRUE(willNotFreeBetween(A, CxtI));
-  // One path from %A to %CxtI2 calls @may_free() -> should return false.
-  EXPECT_FALSE(willNotFreeBetween(A, CxtI2));
-}
-
 class IsBytewiseValueTest : public ValueTrackingTest,
                             public ::testing::WithParamInterface<
                                 std::pair<const char *, const char *>> {

>From 4638c094595722c7d4eec41ace094fd919789575 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Thu, 17 Sep 2026 09:05:59 -0400
Subject: [PATCH 04/11] formatting

---
 llvm/lib/Analysis/ValueTracking.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index 2d4ac35d8bc90b..bbeb73946d7cb3 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -742,8 +742,9 @@ static bool hasNoFreeInRange(BasicBlock::const_iterator Begin,
     if (auto *CB = dyn_cast<CallBase>(&I)) {
       if (!CB->hasFnAttr(Attribute::NoFree))
         return false;
-    } else if (I.maySynchronize())
+    } else if (I.maySynchronize()) {
       return false;
+    }
   }
   return true;
 }

>From b4dfeb4736c3c90ba85b8dced13adf7cfccd8013 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Thu, 17 Sep 2026 10:30:52 -0400
Subject: [PATCH 05/11] also pass the DT to willNotFreeBetween(): helps prune
 backwards search, fixes loop backedge handling

---
 llvm/include/llvm/Analysis/ValueTracking.h |  3 +-
 llvm/lib/Analysis/Loads.cpp                |  4 +--
 llvm/lib/Analysis/ValueTracking.cpp        | 39 +++++++++++++++++-----
 3 files changed, 34 insertions(+), 12 deletions(-)

diff --git a/llvm/include/llvm/Analysis/ValueTracking.h b/llvm/include/llvm/Analysis/ValueTracking.h
index 9a1fdd0ca05a95..99e0121adb6d79 100644
--- a/llvm/include/llvm/Analysis/ValueTracking.h
+++ b/llvm/include/llvm/Analysis/ValueTracking.h
@@ -660,7 +660,8 @@ inline bool isValidAssumeForContext(const Instruction *I,
 /// Returns true, if no instruction between \p Assume and \p CtxI may free
 /// (including through synchronization).
 LLVM_ABI bool willNotFreeBetween(const Instruction *Assume,
-                                 const Instruction *CtxI);
+                                 const Instruction *CtxI,
+                                 const DominatorTree *DT = nullptr);
 
 enum class OverflowResult {
   /// Always overflows in the direction of signed/unsigned min value.
diff --git a/llvm/lib/Analysis/Loads.cpp b/llvm/lib/Analysis/Loads.cpp
index de9022c540d42c..4a75a7841fcb9c 100644
--- a/llvm/lib/Analysis/Loads.cpp
+++ b/llvm/lib/Analysis/Loads.cpp
@@ -54,7 +54,7 @@ static bool isDereferenceableAndAlignedPointerViaAssumption(
           // Dereferenceable information from assumptions is only valid if the
           // value cannot be freed between the assumption and use.
           if (!IsDerefable &&
-              (!PtrCanBeFreed || willNotFreeBetween(Assume, SQ.CxtI)) &&
+              (!PtrCanBeFreed || willNotFreeBetween(Assume, SQ.CxtI, SQ.DT)) &&
               CheckSize(RK))
             IsDerefable = true;
         }
@@ -149,7 +149,7 @@ static bool isDereferenceableAndAlignedPointer(
         DefI = &cast<Argument>(V)->getParent()->getEntryBlock().front();
       }
 
-      if (!SQ.CxtI || !willNotFreeBetween(DefI, SQ.CxtI))
+      if (!SQ.CxtI || !willNotFreeBetween(DefI, SQ.CxtI, SQ.DT))
         return false;
     }
 
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index bbeb73946d7cb3..d27f25c1911281 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -750,41 +750,62 @@ static bool hasNoFreeInRange(BasicBlock::const_iterator Begin,
 }
 
 bool llvm::willNotFreeBetween(const Instruction *Assume,
-                              const Instruction *CtxI) {
-  unsigned NumChecked = 0;
+                              const Instruction *CtxI,
+                              const DominatorTree *DT) {
+
   const BasicBlock *CtxBB = CtxI->getParent();
   const BasicBlock *AssumeBB = Assume->getParent();
+  unsigned NumChecked = 0;
   BasicBlock::const_iterator CtxIter = CtxI->getIterator();
+
   if (CtxBB == AssumeBB) {
     if (Assume != CtxI && !Assume->comesBefore(CtxI))
       return false;
     return hasNoFreeInRange(Assume->getIterator(), CtxIter, NumChecked);
   }
-  // Check instructions before CtxI in CtxBB.
+
+  if (DT && !DT->dominates(Assume, CtxI))
+    return false;
+
   if (!hasNoFreeInRange(CtxBB->begin(), CtxIter, NumChecked))
     return false;
   if (pred_empty(CtxBB))
     return false;
-  SmallVector<const BasicBlock *, 8> Worklist;
-  SmallPtrSet<const BasicBlock *, 8> Visited;
+
+  SmallVector<const BasicBlock *, 16> Worklist;
+  SmallPtrSet<const BasicBlock *, 16> Visited;
+  // Enqueue all predecessors of CtxBB.
+  // Note: CtxBB is NOT pre-inserted to ensure that loop
+  // backedges returning to CtxBB are enqueued and checked correclty.
   for (const BasicBlock *Pred : predecessors(CtxBB)) {
     if (Visited.insert(Pred).second)
       Worklist.push_back(Pred);
   }
   while (!Worklist.empty()) {
     const BasicBlock *CurBB = Worklist.pop_back_val();
+
     if (CurBB == AssumeBB) {
       if (!hasNoFreeInRange(Assume->getIterator(), AssumeBB->end(), NumChecked))
         return false;
       continue;
     }
-    if (!hasNoFreeInRange(CurBB == CtxBB ? CtxIter : CurBB->begin(),
-                          CurBB->end(), NumChecked))
+
+    if (DT && !DT->dominates(AssumeBB, CurBB))
       return false;
-    if (CurBB == CtxBB)
-      continue;
+
     if (pred_empty(CurBB))
       return false;
+    // If CurBB == CtxBB (due to a loop backedge targeting CtxBB), check
+    // instructions from CtxIter to the end of CtxBB (instructions before
+    // CtxIter were checked above). Otherwise, check the entire block.
+    BasicBlock::const_iterator Start =
+        (CurBB == CtxBB) ? CtxIter : CurBB->begin();
+    if (!hasNoFreeInRange(Start, CurBB->end(), NumChecked))
+      return false;
+    // If we reached CtxBB via a backedge, do not re-expand its predecessors
+    // as they were already enqueued at initialization.
+    if (CurBB == CtxBB)
+      continue;
     for (const BasicBlock *Pred : predecessors(CurBB)) {
       if (Visited.insert(Pred).second)
         Worklist.push_back(Pred);

>From 2f889af4f2e6ed8f86e410d583b596647c12f0fb Mon Sep 17 00:00:00 2001
From: Vy Nguyen <1762963+oontvoo at users.noreply.github.com>
Date: Thu, 17 Sep 2026 12:18:19 -0400
Subject: [PATCH 06/11] added extra space

Co-authored-by: Florian Hahn <flo at fhahn.com>
---
 llvm/test/Analysis/ValueTracking/memory-dereferenceable.ll | 1 +
 1 file changed, 1 insertion(+)

diff --git a/llvm/test/Analysis/ValueTracking/memory-dereferenceable.ll b/llvm/test/Analysis/ValueTracking/memory-dereferenceable.ll
index 299e9d5e4496f3..b632de12f8abf4 100644
--- a/llvm/test/Analysis/ValueTracking/memory-dereferenceable.ll
+++ b/llvm/test/Analysis/ValueTracking/memory-dereferenceable.ll
@@ -416,6 +416,7 @@ declare void @mayfree()
 define void @dereferenceable_arg_multi_pred_nofree(ptr dereferenceable(16) %a, i1 %cond) {
 entry:
   br i1 %cond, label %if.then, label %if.else
+  
 if.then:
   call void @mayfree() nofree
   br label %merge

>From 970f58c6ea95d306fa61b6adbfe02b4989e3fcb1 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <1762963+oontvoo at users.noreply.github.com>
Date: Fri, 18 Sep 2026 09:29:55 -0400
Subject: [PATCH 07/11] Update llvm/lib/Analysis/ValueTracking.cpp

Co-authored-by: Antonio Frighetto <me at antoniofrighetto.com>
---
 llvm/lib/Analysis/ValueTracking.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index d27f25c1911281..b4bd342a293c7e 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -776,7 +776,7 @@ bool llvm::willNotFreeBetween(const Instruction *Assume,
   SmallPtrSet<const BasicBlock *, 16> Visited;
   // Enqueue all predecessors of CtxBB.
   // Note: CtxBB is NOT pre-inserted to ensure that loop
-  // backedges returning to CtxBB are enqueued and checked correclty.
+  // backedges returning to CtxBB are enqueued and checked correctly.
   for (const BasicBlock *Pred : predecessors(CtxBB)) {
     if (Visited.insert(Pred).second)
       Worklist.push_back(Pred);

>From 03b9697339092aafed7ebc183a7ad8f886645e97 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Fri, 18 Sep 2026 12:39:58 -0400
Subject: [PATCH 08/11] remove redundant dominate check

---
 llvm/lib/Analysis/ValueTracking.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index b4bd342a293c7e..1251c40c9ece61 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -790,8 +790,8 @@ bool llvm::willNotFreeBetween(const Instruction *Assume,
       continue;
     }
 
-    if (DT && !DT->dominates(AssumeBB, CurBB))
-      return false;
+    assert((!DT || DT->dominates(AssumeBB, CurBB)) &&
+           "Blocks between Assume and CtxI must be dominated by AssumeBB");
 
     if (pred_empty(CurBB))
       return false;

>From 4027c71b1a33b80851c7cf3415039b478236eb42 Mon Sep 17 00:00:00 2001
From: Vy Nguyen <1762963+oontvoo at users.noreply.github.com>
Date: Fri, 18 Sep 2026 22:44:26 -0400
Subject: [PATCH 09/11] Update llvm/lib/Analysis/ValueTracking.cpp

Co-authored-by: Antonio Frighetto <me at antoniofrighetto.com>
---
 llvm/lib/Analysis/ValueTracking.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index 1251c40c9ece61..7990b6d97863f3 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -798,8 +798,7 @@ bool llvm::willNotFreeBetween(const Instruction *Assume,
     // If CurBB == CtxBB (due to a loop backedge targeting CtxBB), check
     // instructions from CtxIter to the end of CtxBB (instructions before
     // CtxIter were checked above). Otherwise, check the entire block.
-    BasicBlock::const_iterator Start =
-        (CurBB == CtxBB) ? CtxIter : CurBB->begin();
+    auto StartIt = (CurBB == CtxBB) ? CtxIter : CurBB->begin();
     if (!hasNoFreeInRange(Start, CurBB->end(), NumChecked))
       return false;
     // If we reached CtxBB via a backedge, do not re-expand its predecessors

>From 1dbfe2a52abf1df6865144c002adea758199ddda Mon Sep 17 00:00:00 2001
From: Vy Nguyen <1762963+oontvoo at users.noreply.github.com>
Date: Fri, 18 Sep 2026 23:20:38 -0400
Subject: [PATCH 10/11] s/StartIt/Start

---
 llvm/lib/Analysis/ValueTracking.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index 7990b6d97863f3..27c0dee9eb0faa 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -798,7 +798,7 @@ bool llvm::willNotFreeBetween(const Instruction *Assume,
     // If CurBB == CtxBB (due to a loop backedge targeting CtxBB), check
     // instructions from CtxIter to the end of CtxBB (instructions before
     // CtxIter were checked above). Otherwise, check the entire block.
-    auto StartIt = (CurBB == CtxBB) ? CtxIter : CurBB->begin();
+    auto Start = (CurBB == CtxBB) ? CtxIter : CurBB->begin();
     if (!hasNoFreeInRange(Start, CurBB->end(), NumChecked))
       return false;
     // If we reached CtxBB via a backedge, do not re-expand its predecessors

>From a6550a5d8e8f6eaa00e1e9d4685bf6c976de07de Mon Sep 17 00:00:00 2001
From: Vy Nguyen <vyng at google.com>
Date: Mon, 21 Sep 2026 12:52:36 -0400
Subject: [PATCH 11/11] address review comment, simplify worklist

---
 llvm/lib/Analysis/ValueTracking.cpp | 34 +++++++----------------------
 1 file changed, 8 insertions(+), 26 deletions(-)

diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index 930af829f5ae1d..d18a95610d20c2 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -756,63 +756,45 @@ static bool hasNoFreeInRange(BasicBlock::const_iterator Begin,
 bool llvm::willNotFreeBetween(const Instruction *Assume,
                               const Instruction *CtxI,
                               const DominatorTree *DT) {
-
   const BasicBlock *CtxBB = CtxI->getParent();
   const BasicBlock *AssumeBB = Assume->getParent();
   unsigned NumChecked = 0;
   BasicBlock::const_iterator CtxIter = CtxI->getIterator();
-
   if (CtxBB == AssumeBB) {
     if (Assume != CtxI && !Assume->comesBefore(CtxI))
       return false;
     return hasNoFreeInRange(Assume->getIterator(), CtxIter, NumChecked);
   }
-
   if (DT && !DT->dominates(Assume, CtxI))
     return false;
-
   if (!hasNoFreeInRange(CtxBB->begin(), CtxIter, NumChecked))
     return false;
   if (pred_empty(CtxBB))
     return false;
-
-  SmallVector<const BasicBlock *, 16> Worklist;
-  SmallPtrSet<const BasicBlock *, 16> Visited;
-  // Enqueue all predecessors of CtxBB.
-  // Note: CtxBB is NOT pre-inserted to ensure that loop
+  // Note: CtxBB is NOT pre-inserted into Visited to ensure that loop
   // backedges returning to CtxBB are enqueued and checked correctly.
-  for (const BasicBlock *Pred : predecessors(CtxBB)) {
-    if (Visited.insert(Pred).second)
-      Worklist.push_back(Pred);
-  }
+  SmallVector<const BasicBlock *, 16> Worklist(predecessors(CtxBB));
+  SmallPtrSet<const BasicBlock *, 16> Visited;
   while (!Worklist.empty()) {
     const BasicBlock *CurBB = Worklist.pop_back_val();
-
+    if (!Visited.insert(CurBB).second)
+      continue;
     if (CurBB == AssumeBB) {
       if (!hasNoFreeInRange(Assume->getIterator(), AssumeBB->end(), NumChecked))
         return false;
       continue;
     }
-
     assert((!DT || DT->dominates(AssumeBB, CurBB)) &&
            "Blocks between Assume and CtxI must be dominated by AssumeBB");
-
     if (pred_empty(CurBB))
       return false;
     // If CurBB == CtxBB (due to a loop backedge targeting CtxBB), check
     // instructions from CtxIter to the end of CtxBB (instructions before
     // CtxIter were checked above). Otherwise, check the entire block.
-    auto Start = (CurBB == CtxBB) ? CtxIter : CurBB->begin();
-    if (!hasNoFreeInRange(Start, CurBB->end(), NumChecked))
+    auto StartIt = (CurBB == CtxBB) ? CtxIter : CurBB->begin();
+    if (!hasNoFreeInRange(StartIt, CurBB->end(), NumChecked))
       return false;
-    // If we reached CtxBB via a backedge, do not re-expand its predecessors
-    // as they were already enqueued at initialization.
-    if (CurBB == CtxBB)
-      continue;
-    for (const BasicBlock *Pred : predecessors(CurBB)) {
-      if (Visited.insert(Pred).second)
-        Worklist.push_back(Pred);
-    }
+    append_range(Worklist, predecessors(CurBB));
   }
   return true;
 }



More information about the llvm-commits mailing list