[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