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

Sayan Sivakumaran via llvm-commits llvm-commits at lists.llvm.org
Fri Oct 2 06:37:41 PDT 2026


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

>From a43461317ffa1b621bc3e9d35a036db682af89d6 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 haveCommonLowBits helper

---
 llvm/include/llvm/Analysis/ValueTracking.h    |   9 ++
 llvm/lib/Analysis/ValueTracking.cpp           |  41 +++++++
 llvm/unittests/Analysis/ValueTrackingTest.cpp | 110 ++++++++++++++++++
 3 files changed, 160 insertions(+)

diff --git a/llvm/include/llvm/Analysis/ValueTracking.h b/llvm/include/llvm/Analysis/ValueTracking.h
index b3c8a894ef056..8ab859cc8b38c 100644
--- a/llvm/include/llvm/Analysis/ValueTracking.h
+++ b/llvm/include/llvm/Analysis/ValueTracking.h
@@ -136,6 +136,15 @@ LLVM_ABI bool haveNoCommonBitsSet(const WithCache<const Value *> &LHSCache,
                                   const WithCache<const Value *> &RHSCache,
                                   const SimplifyQuery &SQ);
 
+/// Returns true if all of the following are satisfied:
+/// - The first `CommonBitLength` bits of V1 and V2 can be proved equal
+///   whenever V1 and V2 are not undef or poison.
+/// - V1 is poison if and only if V2 is poison
+/// - V1 is undef if and only if V2 is undef
+LLVM_ABI bool haveCommonLowBits(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..1a2f8a2302192 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -277,6 +277,47 @@ bool llvm::haveNoCommonBitsSet(const WithCache<const Value *> &LHSCache,
   return Result == NoCommonBitsSetResult::Known;
 }
 
+bool llvm::haveCommonLowBits(const Value *V1, const Value *V2,
+                             unsigned CommonBitLength,
+                             const SimplifyQuery &SQ) {
+  // Let's not support vectors for now.
+  if (V1->getType()->isVectorTy() || V2->getType()->isVectorTy())
+    return false;
+  if (V1 == V2)
+    return true;
+
+  TypeSize V1Size = V1->getType()->getPrimitiveSizeInBits();
+  TypeSize V2Size = V2->getType()->getPrimitiveSizeInBits();
+  assert(CommonBitLength <= V1Size);
+  assert(CommonBitLength <= V2Size);
+
+  // If A is an extension of B, they certainly share the same lower bits.
+  auto IsExtensionOf = [&SQ](const Value *A, const Value *B) {
+    if (match(A, m_NNegZExt(m_Specific(B)))) {
+      assert(impliesPoison(B, A));
+      return isKnownNonNegative(B, SQ);
+    }
+
+    if (match(A, m_ZExtOrSExt(m_Specific(B)))) {
+      assert(impliesPoison(B, A));
+      assert(impliesPoison(A, B));
+      return true;
+    }
+
+    return false;
+  };
+
+  if (IsExtensionOf(V2, V1) || 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..f995d1bdcfa13 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,108 @@ TEST_F(ValueTrackingTest, HaveNoCommonBitsSet) {
   }
 }
 
+TEST_F(ValueTrackingTest, HaveCommonLowBits) {
+  {
+    // A value trivially shares the same lower 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());
+
+    EXPECT_TRUE(
+        haveCommonLowBits(X, X, X->getType()->getPrimitiveSizeInBits(), SQ));
+  }
+  {
+    // Two different values trivially 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());
+
+    EXPECT_TRUE(haveCommonLowBits(X, Y, 0, SQ));
+    EXPECT_TRUE(haveCommonLowBits(Y, X, 0, SQ));
+    EXPECT_FALSE(haveCommonLowBits(X, Y, 1, SQ));
+    EXPECT_FALSE(haveCommonLowBits(Y, X, 1, SQ));
+  }
+  {
+    // A value has the same lower bits as 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 = &findInstructionByName(F, "Y");
+    auto *Z = &findInstructionByName(F, "Z");
+    AssumptionCache AC(*F);
+    SimplifyQuery SQ(M->getDataLayout());
+
+    auto Size = X->getType()->getPrimitiveSizeInBits();
+    EXPECT_TRUE(haveCommonLowBits(X, Y, Size, SQ));
+    EXPECT_TRUE(haveCommonLowBits(Y, X, Size, SQ));
+    EXPECT_TRUE(haveCommonLowBits(X, Z, Size, SQ));
+    EXPECT_TRUE(haveCommonLowBits(Z, X, Size, SQ));
+  }
+  {
+    // If we zext nneg a value, it only shares the lower bits if it's
+    // non-negative.
+    auto M = parseModule(R"(
+  declare void @llvm.assume(i1)
+
+  define i64 @test(i32 noundef %X, i32 noundef %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 = &findInstructionByName(F, "A");
+    auto *B = &findInstructionByName(F, "B");
+    AssumptionCache AC(*F);
+    SimplifyQuery SQ(M->getDataLayout(), /*DT=*/nullptr, &AC, /*CtxI=*/B);
+
+    auto Size = X->getType()->getPrimitiveSizeInBits();
+    EXPECT_TRUE(haveCommonLowBits(X, A, Size, SQ));
+    EXPECT_TRUE(haveCommonLowBits(A, X, Size, SQ));
+    EXPECT_FALSE(haveCommonLowBits(Y, B, Size, SQ));
+    EXPECT_FALSE(haveCommonLowBits(B, Y, Size, SQ));
+  }
+  {
+    // We can trivially detect common lower bits for constants.
+    auto M = parseModule(R"(
+  define i32 @test() {
+    %X = add i32 1, 0
+    %Y = add i32 1, 2
+    ret i32 %X 
+  })");
+    auto *F = M->getFunction("test");
+    auto *X = &findInstructionByName(F, "X");
+    auto *Y = &findInstructionByName(F, "Y");
+    AssumptionCache AC(*F);
+    SimplifyQuery SQ(M->getDataLayout());
+
+    EXPECT_TRUE(haveCommonLowBits(X, Y, 1, SQ));
+    EXPECT_TRUE(haveCommonLowBits(Y, X, 1, SQ));
+    EXPECT_FALSE(haveCommonLowBits(X, Y, 2, SQ));
+    EXPECT_FALSE(haveCommonLowBits(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