[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