[llvm] [ValueTracking][NFC] Add haveCommonInitialBits helper (PR #228168)

Sayan Sivakumaran via llvm-commits llvm-commits at lists.llvm.org
Thu Oct 1 10:49:46 PDT 2026


https://github.com/sivakusayan created https://github.com/llvm/llvm-project/pull/228168

Related to https://github.com/llvm/llvm-project/issues/138678.

It can be useful to prove that two values start with the same bits, up to some length. For example, this can be used as one necessary condition for a store to preserve a memory location. Consider this snippet derived from the linked issue:
```
  %2 = load i8, ptr %0, align 8
  %3 = zext i8 %2 to i32
  store i32 %3, ptr @g1, align 4
  
  ; %5 is redundant, since an overlap of the store and load 
  ; implies @g1 and %0 share the same base
  %5 = load i8, ptr %0, align 8    
```
This is preliminary work so that the above optimization can be made in GVN.

>From e28870bc7d62e94ca0c19cce7d5aedbfc49c6c67 Mon Sep 17 00:00:00 2001
From: Sayan Sivakumaran <sivakusayan at gmail.com>
Date: Thu, 1 Oct 2026 12:39:27 -0500
Subject: [PATCH] [ValueTracking][NFC] Add haveCommonInitialBits helper

---
 llvm/include/llvm/Analysis/ValueTracking.h    |   6 +
 llvm/lib/Analysis/ValueTracking.cpp           |  33 ++++++
 llvm/unittests/Analysis/ValueTrackingTest.cpp | 112 ++++++++++++++++++
 3 files changed, 151 insertions(+)

diff --git a/llvm/include/llvm/Analysis/ValueTracking.h b/llvm/include/llvm/Analysis/ValueTracking.h
index b3c8a894ef056..58294b6b1ddb6 100644
--- a/llvm/include/llvm/Analysis/ValueTracking.h
+++ b/llvm/include/llvm/Analysis/ValueTracking.h
@@ -136,6 +136,12 @@ LLVM_ABI bool haveNoCommonBitsSet(const WithCache<const Value *> &LHSCache,
                                   const WithCache<const Value *> &RHSCache,
                                   const SimplifyQuery &SQ);
 
+/// Return true if the first `CommonBitLength` bits of V1 and V2 can be proved
+/// equal.
+LLVM_ABI bool haveCommonInitialBits(const Value *V1, const Value *V2,
+                                    unsigned CommonBitLength,
+                                    const SimplifyQuery &SQ);
+
 /// Return true if the given value is known to have exactly one bit set when
 /// defined. For vectors return true if every element is known to be a power
 /// of two when defined. Supports values with integer or pointer type and
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index e9c1b9fa95376..c373bb03a025d 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -277,6 +277,39 @@ bool llvm::haveNoCommonBitsSet(const WithCache<const Value *> &LHSCache,
   return Result == NoCommonBitsSetResult::Known;
 }
 
+bool llvm::haveCommonInitialBits(const Value *V1, const Value *V2,
+                                 unsigned CommonBitLength,
+                                 const SimplifyQuery &SQ) {
+  TypeSize V1Size = V1->getType()->getPrimitiveSizeInBits();
+  TypeSize V2Size = V2->getType()->getPrimitiveSizeInBits();
+  assert(CommonBitLength <= V1Size);
+  assert(CommonBitLength <= V2Size);
+
+  if (V1 == V2)
+    return true;
+
+  // If the number of matching bits we are checking for is
+  // the same as the size of one of the values, it suffices
+  // to check that the other is a sign or zero extension.
+  auto IsExtensionOf = [SQ](const Value *A, const Value *B) {
+    if (match(A, m_NNegZExt(m_Specific(B))))
+      return isKnownNonNegative(B, SQ);
+    if (match(A, m_ZExtOrSExt(m_Specific(B))))
+      return true;
+    return false;
+  };
+  if (V1Size == CommonBitLength && IsExtensionOf(V2, V1))
+    return true;
+  if (V2Size == CommonBitLength && IsExtensionOf(V1, V2))
+    return true;
+
+  // We couldn't find a special case to prove this, so fallback to
+  // KnownBit analysis.
+  KnownBits V1Known = computeKnownBits(V1, SQ).trunc(CommonBitLength);
+  KnownBits V2Known = computeKnownBits(V2, SQ).trunc(CommonBitLength);
+  return (V1Known ^ V2Known).isZero();
+}
+
 bool llvm::isOnlyUsedInZeroComparison(const Instruction *I) {
   return !I->user_empty() &&
          all_of(I->users(), match_fn(m_ICmp(m_Value(), m_Zero())));
diff --git a/llvm/unittests/Analysis/ValueTrackingTest.cpp b/llvm/unittests/Analysis/ValueTrackingTest.cpp
index 75cb0f52b15d5..2519bdbd0def1 100644
--- a/llvm/unittests/Analysis/ValueTrackingTest.cpp
+++ b/llvm/unittests/Analysis/ValueTrackingTest.cpp
@@ -47,6 +47,14 @@ static Instruction &findInstructionByName(Function *F, StringRef Name) {
   llvm_unreachable("Expected value not found");
 }
 
+static Value *findArgumentByName(Function *F, StringRef Name) {
+  for (Value &V : F->args())
+    if (V.getName() == Name)
+      return &V;
+
+  llvm_unreachable("Expected argument not found");
+}
+
 class ValueTrackingTest : public testing::Test {
 protected:
   std::unique_ptr<Module> parseModule(StringRef Assembly) {
@@ -3412,6 +3420,110 @@ TEST_F(ValueTrackingTest, HaveNoCommonBitsSet) {
   }
 }
 
+TEST_F(ValueTrackingTest, HaveCommonInitialBits) {
+  {
+    // A value vacuously has common initial bits with itself.
+    auto M = parseModule(R"(
+  define i32 @test(i32 %X) {
+    ret i32 %X 
+  })");
+    auto *F = M->getFunction("test");
+    auto *X = findArgumentByName(F, "X");
+    AssumptionCache AC(*F);
+    SimplifyQuery SQ(M->getDataLayout(), /*DT=*/nullptr, &AC);
+
+    EXPECT_TRUE(haveCommonInitialBits(
+        X, X, X->getType()->getPrimitiveSizeInBits(), SQ));
+  }
+  {
+    // Two different values vacuously have zero bits in common.
+    auto M = parseModule(R"(
+  define i32 @test(i32 %X, i32 %Y) {
+    ret i32 %X 
+  })");
+    auto *F = M->getFunction("test");
+    auto *X = findArgumentByName(F, "X");
+    auto *Y = findArgumentByName(F, "Y");
+    AssumptionCache AC(*F);
+    SimplifyQuery SQ(M->getDataLayout(), /*DT=*/nullptr, &AC);
+
+    EXPECT_TRUE(haveCommonInitialBits(X, Y, 0, SQ));
+    EXPECT_TRUE(haveCommonInitialBits(Y, X, 0, SQ));
+    EXPECT_FALSE(haveCommonInitialBits(Y, X, 1, SQ));
+    EXPECT_FALSE(haveCommonInitialBits(Y, X, 1, SQ));
+  }
+  {
+    // A value shares common initial bits with its extension.
+    auto M = parseModule(R"(
+  define i64 @test(i32 %X) {
+    %Y = zext i32 %X to i64
+    %Z = sext i32 %X to i64
+    ret i64 %Y 
+  })");
+    auto *F = M->getFunction("test");
+    auto *X = findArgumentByName(F, "X");
+    auto *Y = findInstructionByNameOrNull(F, "Y");
+    auto *Z = findInstructionByNameOrNull(F, "Z");
+    AssumptionCache AC(*F);
+    SimplifyQuery SQ(M->getDataLayout(), /*DT=*/nullptr, &AC);
+
+    auto Size = X->getType()->getPrimitiveSizeInBits();
+    EXPECT_TRUE(haveCommonInitialBits(X, Y, Size, SQ));
+    EXPECT_TRUE(haveCommonInitialBits(Y, X, Size, SQ));
+    EXPECT_TRUE(haveCommonInitialBits(X, Z, Size, SQ));
+    EXPECT_TRUE(haveCommonInitialBits(Z, X, Size, SQ));
+  }
+  {
+    // If we zext nneg a value, it only shares common initial bits if it's
+    // non-negative (otherwise the result is poison).
+    auto M = parseModule(R"(
+  declare void @llvm.assume(i1)
+
+  define i64 @test(i32 %X, i32 %Y) {
+    %res = icmp sge i32 %X, 0
+    call void @llvm.assume(i1 %res)
+
+    %A = zext nneg i32 %X to i64
+    %B = zext nneg i32 %Y to i64
+    ret i64 %A 
+  })");
+    auto *F = M->getFunction("test");
+    auto *X = findArgumentByName(F, "X");
+    auto *Y = findArgumentByName(F, "Y");
+    auto *A = findInstructionByNameOrNull(F, "A");
+    auto *B = findInstructionByNameOrNull(F, "B");
+    AssumptionCache AC(*F);
+    SimplifyQuery SQ(M->getDataLayout(), /*DT=*/nullptr, &AC, /*CtxI=*/B);
+
+    auto Size = X->getType()->getPrimitiveSizeInBits();
+    EXPECT_TRUE(haveCommonInitialBits(X, A, Size, SQ));
+    EXPECT_TRUE(haveCommonInitialBits(A, X, Size, SQ));
+    EXPECT_FALSE(haveCommonInitialBits(Y, B, Size, SQ));
+    EXPECT_FALSE(haveCommonInitialBits(B, Y, Size, SQ));
+  }
+  {
+    // We can trivially detect common bits for constants.
+    auto M = parseModule(R"(
+  declare void @llvm.assume(i1)
+
+  define i32 @test() {
+    %X = add i32 1, 0
+    %Y = add i32 1, 2
+    ret i32 %X 
+  })");
+    auto *F = M->getFunction("test");
+    auto *X = findInstructionByNameOrNull(F, "X");
+    auto *Y = findInstructionByNameOrNull(F, "Y");
+    AssumptionCache AC(*F);
+    SimplifyQuery SQ(M->getDataLayout(), /*DT=*/nullptr, &AC);
+
+    EXPECT_TRUE(haveCommonInitialBits(X, Y, 1, SQ));
+    EXPECT_TRUE(haveCommonInitialBits(Y, X, 1, SQ));
+    EXPECT_FALSE(haveCommonInitialBits(X, Y, 2, SQ));
+    EXPECT_FALSE(haveCommonInitialBits(Y, X, 2, SQ));
+  }
+}
+
 class IsBytewiseValueTest : public ValueTrackingTest,
                             public ::testing::WithParamInterface<
                                 std::pair<const char *, const char *>> {



More information about the llvm-commits mailing list