[llvm] [ValueTracking] Set KnownBits from MSB shift idiom (PR #200203)
Marina Taylor via llvm-commits
llvm-commits at lists.llvm.org
Mon Jun 8 06:13:46 PDT 2026
https://github.com/citymarina updated https://github.com/llvm/llvm-project/pull/200203
>From ef78e8d4f52ef9ba6a68a9cb4401bd28dd7a3b1b Mon Sep 17 00:00:00 2001
From: Marina Taylor <marina_taylor at apple.com>
Date: Tue, 26 May 2026 20:18:58 +0100
Subject: [PATCH 1/3] [ValueTracking] Pre-commit test for shl-ctlz-msb idiom
(NFC)
Assisted-by: claude
---
.../ValueTracking/knownbits-shl-ctlz-msb.ll | 167 ++++++++++++++++++
1 file changed, 167 insertions(+)
create mode 100644 llvm/test/Analysis/ValueTracking/knownbits-shl-ctlz-msb.ll
diff --git a/llvm/test/Analysis/ValueTracking/knownbits-shl-ctlz-msb.ll b/llvm/test/Analysis/ValueTracking/knownbits-shl-ctlz-msb.ll
new file mode 100644
index 0000000000000..f6bd3c7c3cc5e
--- /dev/null
+++ b/llvm/test/Analysis/ValueTracking/knownbits-shl-ctlz-msb.ll
@@ -0,0 +1,167 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt -passes=instcombine -S < %s | FileCheck %s
+
+; Pattern: shl X, sub(Y, xor(ctlz(X), BitWidth-1))
+; When BitWidth is a power of 2 and Y < BitWidth-1, the result has its MSB
+; at bit Y: bit Y is set and bits above Y are clear.
+
+declare i16 @llvm.ctlz.i16(i16, i1 immarg)
+declare i31 @llvm.ctlz.i31(i31, i1 immarg)
+declare i32 @llvm.ctlz.i32(i32, i1 immarg)
+declare i64 @llvm.ctlz.i64(i64, i1 immarg)
+
+define i32 @msb_at_14_bit_set(i32 %x) {
+; CHECK-LABEL: @msb_at_14_bit_set(
+; CHECK-NEXT: [[C:%.*]] = call range(i32 0, 33) i32 @llvm.ctlz.i32(i32 [[X:%.*]], i1 true)
+; CHECK-NEXT: [[XORED:%.*]] = xor i32 [[C]], 31
+; CHECK-NEXT: [[SHAMT:%.*]] = sub nsw i32 14, [[XORED]]
+; CHECK-NEXT: [[S:%.*]] = shl i32 [[X]], [[SHAMT]]
+; CHECK-NEXT: [[R:%.*]] = and i32 [[S]], 16384
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %c = call i32 @llvm.ctlz.i32(i32 %x, i1 true)
+ %xored = xor i32 %c, 31
+ %shamt = sub i32 14, %xored
+ %s = shl i32 %x, %shamt
+ %r = and i32 %s, 16384
+ ret i32 %r
+}
+
+define i32 @msb_at_14_high_bits_clear(i32 %x) {
+; CHECK-LABEL: @msb_at_14_high_bits_clear(
+; CHECK-NEXT: [[C:%.*]] = call range(i32 0, 33) i32 @llvm.ctlz.i32(i32 [[X:%.*]], i1 true)
+; CHECK-NEXT: [[XORED:%.*]] = xor i32 [[C]], 31
+; CHECK-NEXT: [[SHAMT:%.*]] = sub nsw i32 14, [[XORED]]
+; CHECK-NEXT: [[S:%.*]] = shl i32 [[X]], [[SHAMT]]
+; CHECK-NEXT: [[R:%.*]] = and i32 [[S]], -32768
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %c = call i32 @llvm.ctlz.i32(i32 %x, i1 true)
+ %xored = xor i32 %c, 31
+ %shamt = sub i32 14, %xored
+ %s = shl i32 %x, %shamt
+ %r = and i32 %s, 4294934528
+ ret i32 %r
+}
+
+define i32 @msb_at_14_xor_commuted(i32 %x) {
+; CHECK-LABEL: @msb_at_14_xor_commuted(
+; CHECK-NEXT: [[C:%.*]] = call range(i32 0, 33) i32 @llvm.ctlz.i32(i32 [[X:%.*]], i1 true)
+; CHECK-NEXT: [[XORED:%.*]] = xor i32 [[C]], 31
+; CHECK-NEXT: [[SHAMT:%.*]] = sub nsw i32 14, [[XORED]]
+; CHECK-NEXT: [[S:%.*]] = shl i32 [[X]], [[SHAMT]]
+; CHECK-NEXT: [[R:%.*]] = and i32 [[S]], -32768
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %c = call i32 @llvm.ctlz.i32(i32 %x, i1 true)
+ %xored = xor i32 31, %c
+ %shamt = sub i32 14, %xored
+ %s = shl i32 %x, %shamt
+ %r = and i32 %s, 4294934528
+ ret i32 %r
+}
+
+define i32 @msb_at_14_zero_not_undef(i32 %x) {
+; CHECK-LABEL: @msb_at_14_zero_not_undef(
+; CHECK-NEXT: [[C:%.*]] = call range(i32 0, 33) i32 @llvm.ctlz.i32(i32 [[X:%.*]], i1 false)
+; CHECK-NEXT: [[XORED:%.*]] = xor i32 [[C]], 31
+; CHECK-NEXT: [[SHAMT:%.*]] = sub nsw i32 14, [[XORED]]
+; CHECK-NEXT: [[S:%.*]] = shl i32 [[X]], [[SHAMT]]
+; CHECK-NEXT: [[R:%.*]] = and i32 [[S]], 16384
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %c = call i32 @llvm.ctlz.i32(i32 %x, i1 false)
+ %xored = xor i32 %c, 31
+ %shamt = sub i32 14, %xored
+ %s = shl i32 %x, %shamt
+ %r = and i32 %s, 16384
+ ret i32 %r
+}
+
+define <2 x i32> @msb_at_14_vec(<2 x i32> %x) {
+; CHECK-LABEL: @msb_at_14_vec(
+; CHECK-NEXT: [[C:%.*]] = call range(i32 0, 33) <2 x i32> @llvm.ctlz.v2i32(<2 x i32> [[X:%.*]], i1 true)
+; CHECK-NEXT: [[XORED:%.*]] = xor <2 x i32> [[C]], splat (i32 31)
+; CHECK-NEXT: [[SHAMT:%.*]] = sub nsw <2 x i32> splat (i32 14), [[XORED]]
+; CHECK-NEXT: [[S:%.*]] = shl <2 x i32> [[X]], [[SHAMT]]
+; CHECK-NEXT: [[R:%.*]] = and <2 x i32> [[S]], splat (i32 16384)
+; CHECK-NEXT: ret <2 x i32> [[R]]
+;
+ %c = call <2 x i32> @llvm.ctlz.v2i32(<2 x i32> %x, i1 true)
+ %xored = xor <2 x i32> %c, <i32 31, i32 31>
+ %shamt = sub <2 x i32> <i32 14, i32 14>, %xored
+ %s = shl <2 x i32> %x, %shamt
+ %r = and <2 x i32> %s, <i32 16384, i32 16384>
+ ret <2 x i32> %r
+}
+
+; Negative: Y is not less than BitWidth - 1
+define i32 @neg_y_too_large(i32 %x) {
+; CHECK-LABEL: @neg_y_too_large(
+; CHECK-NEXT: [[C:%.*]] = call range(i32 0, 33) i32 @llvm.ctlz.i32(i32 [[X:%.*]], i1 true)
+; CHECK-NEXT: [[S:%.*]] = shl i32 [[X]], [[C]]
+; CHECK-NEXT: [[R:%.*]] = and i32 [[S]], -2147483648
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %c = call i32 @llvm.ctlz.i32(i32 %x, i1 true)
+ %xored = xor i32 %c, 31
+ %shamt = sub i32 31, %xored
+ %s = shl i32 %x, %shamt
+ %r = and i32 %s, 2147483648
+ ret i32 %r
+}
+
+; Negative: BitWidth not a power of two
+define i31 @neg_bad_width(i31 %x) {
+; CHECK-LABEL: @neg_bad_width(
+; CHECK-NEXT: [[C:%.*]] = call range(i31 0, 32) i31 @llvm.ctlz.i31(i31 [[X:%.*]], i1 true)
+; CHECK-NEXT: [[XORED:%.*]] = xor i31 [[C]], 30
+; CHECK-NEXT: [[SHAMT:%.*]] = sub nsw i31 14, [[XORED]]
+; CHECK-NEXT: [[S:%.*]] = shl i31 [[X]], [[SHAMT]]
+; CHECK-NEXT: [[R:%.*]] = and i31 [[S]], 16384
+; CHECK-NEXT: ret i31 [[R]]
+;
+ %c = call i31 @llvm.ctlz.i31(i31 %x, i1 true)
+ %xored = xor i31 %c, 30
+ %shamt = sub i31 14, %xored
+ %s = shl i31 %x, %shamt
+ %r = and i31 %s, 16384
+ ret i31 %r
+}
+
+; Negative: xor with a constant other than BitWidth-1 breaks the equivalence
+; ctlz(X) ^ K == (BitWidth-1) - ctlz(X), so the MSB position claim doesn't hold.
+define i32 @neg_wrong_xor_const(i32 %x) {
+; CHECK-LABEL: @neg_wrong_xor_const(
+; CHECK-NEXT: [[C:%.*]] = call range(i32 0, 33) i32 @llvm.ctlz.i32(i32 [[X:%.*]], i1 true)
+; CHECK-NEXT: [[XORED:%.*]] = xor i32 [[C]], 30
+; CHECK-NEXT: [[SHAMT:%.*]] = sub nsw i32 14, [[XORED]]
+; CHECK-NEXT: [[S:%.*]] = shl i32 [[X]], [[SHAMT]]
+; CHECK-NEXT: [[R:%.*]] = and i32 [[S]], 16384
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %c = call i32 @llvm.ctlz.i32(i32 %x, i1 true)
+ %xored = xor i32 %c, 30
+ %shamt = sub i32 14, %xored
+ %s = shl i32 %x, %shamt
+ %r = and i32 %s, 16384
+ ret i32 %r
+}
+
+; Negative: the value being shifted is not the value fed to ctlz.
+define i32 @neg_different_x(i32 %x, i32 %y) {
+; CHECK-LABEL: @neg_different_x(
+; CHECK-NEXT: [[C:%.*]] = call range(i32 0, 33) i32 @llvm.ctlz.i32(i32 [[Y:%.*]], i1 true)
+; CHECK-NEXT: [[XORED:%.*]] = xor i32 [[C]], 31
+; CHECK-NEXT: [[SHAMT:%.*]] = sub nsw i32 14, [[XORED]]
+; CHECK-NEXT: [[S:%.*]] = shl i32 [[X:%.*]], [[SHAMT]]
+; CHECK-NEXT: [[R:%.*]] = and i32 [[S]], 16384
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %c = call i32 @llvm.ctlz.i32(i32 %y, i1 true)
+ %xored = xor i32 %c, 31
+ %shamt = sub i32 14, %xored
+ %s = shl i32 %x, %shamt
+ %r = and i32 %s, 16384
+ ret i32 %r
+}
>From bdc200dd2ce76d28185392e83583affc00f42f5b Mon Sep 17 00:00:00 2001
From: Marina Taylor <marina_taylor at apple.com>
Date: Wed, 27 May 2026 19:58:58 +0100
Subject: [PATCH 2/3] [ValueTracking] Set KnownBits from MSB shift idiom
`shl X, sub(Y, xor(ctlz(X), BitWidth-1))` shifts X so that its MSB lands
at bit Y, when BitWidth is a power of 2.
https://alive2.llvm.org/ce/z/NTVYH7
Assisted-by: claude
---
llvm/lib/Analysis/ValueTracking.cpp | 14 ++++++++
.../ValueTracking/knownbits-shl-ctlz-msb.ll | 35 +++----------------
2 files changed, 19 insertions(+), 30 deletions(-)
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index 3be0db5ad9c7e..a5643a14bf435 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -1633,6 +1633,20 @@ static void computeKnownBitsFromOperator(const Operator *I,
const APInt *C;
if (match(I->getOperand(0), m_APInt(C)))
Known.Zero.setLowBits(C->countr_zero());
+
+ // shl X, sub(Y, xor(ctlz(X), BitWidth-1)) shifts X so that its MSB lands
+ // at bit Y, when BitWidth is a power of 2.
+ const APInt *YC;
+ Value *X = I->getOperand(0);
+ if (isPowerOf2_32(BitWidth) &&
+ match(I->getOperand(1),
+ m_Sub(m_APInt(YC), m_c_Xor(m_Ctlz(m_Specific(X), m_Value()),
+ m_SpecificInt(BitWidth - 1)))) &&
+ YC->ult(BitWidth - 1)) {
+ unsigned Y = YC->getZExtValue();
+ Known.One.setBit(Y);
+ Known.Zero.setBitsFrom(Y + 1);
+ }
break;
}
case Instruction::LShr: {
diff --git a/llvm/test/Analysis/ValueTracking/knownbits-shl-ctlz-msb.ll b/llvm/test/Analysis/ValueTracking/knownbits-shl-ctlz-msb.ll
index f6bd3c7c3cc5e..9a101d945c1ed 100644
--- a/llvm/test/Analysis/ValueTracking/knownbits-shl-ctlz-msb.ll
+++ b/llvm/test/Analysis/ValueTracking/knownbits-shl-ctlz-msb.ll
@@ -12,12 +12,7 @@ declare i64 @llvm.ctlz.i64(i64, i1 immarg)
define i32 @msb_at_14_bit_set(i32 %x) {
; CHECK-LABEL: @msb_at_14_bit_set(
-; CHECK-NEXT: [[C:%.*]] = call range(i32 0, 33) i32 @llvm.ctlz.i32(i32 [[X:%.*]], i1 true)
-; CHECK-NEXT: [[XORED:%.*]] = xor i32 [[C]], 31
-; CHECK-NEXT: [[SHAMT:%.*]] = sub nsw i32 14, [[XORED]]
-; CHECK-NEXT: [[S:%.*]] = shl i32 [[X]], [[SHAMT]]
-; CHECK-NEXT: [[R:%.*]] = and i32 [[S]], 16384
-; CHECK-NEXT: ret i32 [[R]]
+; CHECK-NEXT: ret i32 16384
;
%c = call i32 @llvm.ctlz.i32(i32 %x, i1 true)
%xored = xor i32 %c, 31
@@ -29,12 +24,7 @@ define i32 @msb_at_14_bit_set(i32 %x) {
define i32 @msb_at_14_high_bits_clear(i32 %x) {
; CHECK-LABEL: @msb_at_14_high_bits_clear(
-; CHECK-NEXT: [[C:%.*]] = call range(i32 0, 33) i32 @llvm.ctlz.i32(i32 [[X:%.*]], i1 true)
-; CHECK-NEXT: [[XORED:%.*]] = xor i32 [[C]], 31
-; CHECK-NEXT: [[SHAMT:%.*]] = sub nsw i32 14, [[XORED]]
-; CHECK-NEXT: [[S:%.*]] = shl i32 [[X]], [[SHAMT]]
-; CHECK-NEXT: [[R:%.*]] = and i32 [[S]], -32768
-; CHECK-NEXT: ret i32 [[R]]
+; CHECK-NEXT: ret i32 0
;
%c = call i32 @llvm.ctlz.i32(i32 %x, i1 true)
%xored = xor i32 %c, 31
@@ -46,12 +36,7 @@ define i32 @msb_at_14_high_bits_clear(i32 %x) {
define i32 @msb_at_14_xor_commuted(i32 %x) {
; CHECK-LABEL: @msb_at_14_xor_commuted(
-; CHECK-NEXT: [[C:%.*]] = call range(i32 0, 33) i32 @llvm.ctlz.i32(i32 [[X:%.*]], i1 true)
-; CHECK-NEXT: [[XORED:%.*]] = xor i32 [[C]], 31
-; CHECK-NEXT: [[SHAMT:%.*]] = sub nsw i32 14, [[XORED]]
-; CHECK-NEXT: [[S:%.*]] = shl i32 [[X]], [[SHAMT]]
-; CHECK-NEXT: [[R:%.*]] = and i32 [[S]], -32768
-; CHECK-NEXT: ret i32 [[R]]
+; CHECK-NEXT: ret i32 0
;
%c = call i32 @llvm.ctlz.i32(i32 %x, i1 true)
%xored = xor i32 31, %c
@@ -63,12 +48,7 @@ define i32 @msb_at_14_xor_commuted(i32 %x) {
define i32 @msb_at_14_zero_not_undef(i32 %x) {
; CHECK-LABEL: @msb_at_14_zero_not_undef(
-; CHECK-NEXT: [[C:%.*]] = call range(i32 0, 33) i32 @llvm.ctlz.i32(i32 [[X:%.*]], i1 false)
-; CHECK-NEXT: [[XORED:%.*]] = xor i32 [[C]], 31
-; CHECK-NEXT: [[SHAMT:%.*]] = sub nsw i32 14, [[XORED]]
-; CHECK-NEXT: [[S:%.*]] = shl i32 [[X]], [[SHAMT]]
-; CHECK-NEXT: [[R:%.*]] = and i32 [[S]], 16384
-; CHECK-NEXT: ret i32 [[R]]
+; CHECK-NEXT: ret i32 16384
;
%c = call i32 @llvm.ctlz.i32(i32 %x, i1 false)
%xored = xor i32 %c, 31
@@ -80,12 +60,7 @@ define i32 @msb_at_14_zero_not_undef(i32 %x) {
define <2 x i32> @msb_at_14_vec(<2 x i32> %x) {
; CHECK-LABEL: @msb_at_14_vec(
-; CHECK-NEXT: [[C:%.*]] = call range(i32 0, 33) <2 x i32> @llvm.ctlz.v2i32(<2 x i32> [[X:%.*]], i1 true)
-; CHECK-NEXT: [[XORED:%.*]] = xor <2 x i32> [[C]], splat (i32 31)
-; CHECK-NEXT: [[SHAMT:%.*]] = sub nsw <2 x i32> splat (i32 14), [[XORED]]
-; CHECK-NEXT: [[S:%.*]] = shl <2 x i32> [[X]], [[SHAMT]]
-; CHECK-NEXT: [[R:%.*]] = and <2 x i32> [[S]], splat (i32 16384)
-; CHECK-NEXT: ret <2 x i32> [[R]]
+; CHECK-NEXT: ret <2 x i32> splat (i32 16384)
;
%c = call <2 x i32> @llvm.ctlz.v2i32(<2 x i32> %x, i1 true)
%xored = xor <2 x i32> %c, <i32 31, i32 31>
>From d288b52eb53e4d378b454602f3244be0e2b2d8b7 Mon Sep 17 00:00:00 2001
From: Marina Taylor <marina_taylor at apple.com>
Date: Thu, 28 May 2026 16:52:35 +0100
Subject: [PATCH 3/3] Review fixes
---
llvm/lib/Analysis/ValueTracking.cpp | 8 ++---
.../ValueTracking/knownbits-shl-ctlz-msb.ll | 29 -------------------
2 files changed, 4 insertions(+), 33 deletions(-)
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index a5643a14bf435..82e4108ad9bff 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -1634,14 +1634,14 @@ static void computeKnownBitsFromOperator(const Operator *I,
if (match(I->getOperand(0), m_APInt(C)))
Known.Zero.setLowBits(C->countr_zero());
- // shl X, sub(Y, xor(ctlz(X), BitWidth-1)) shifts X so that its MSB lands
- // at bit Y, when BitWidth is a power of 2.
+ // shl X, sub(Y, xor(ctlz(X, true), BitWidth-1)) shifts X so that its MSB
+ // lands at bit Y, when BitWidth is a power of 2.
const APInt *YC;
Value *X = I->getOperand(0);
if (isPowerOf2_32(BitWidth) &&
match(I->getOperand(1),
- m_Sub(m_APInt(YC), m_c_Xor(m_Ctlz(m_Specific(X), m_Value()),
- m_SpecificInt(BitWidth - 1)))) &&
+ m_Sub(m_APInt(YC), m_Xor(m_Ctlz(m_Specific(X), m_One()),
+ m_SpecificInt(BitWidth - 1)))) &&
YC->ult(BitWidth - 1)) {
unsigned Y = YC->getZExtValue();
Known.One.setBit(Y);
diff --git a/llvm/test/Analysis/ValueTracking/knownbits-shl-ctlz-msb.ll b/llvm/test/Analysis/ValueTracking/knownbits-shl-ctlz-msb.ll
index 9a101d945c1ed..ba71154cc05f8 100644
--- a/llvm/test/Analysis/ValueTracking/knownbits-shl-ctlz-msb.ll
+++ b/llvm/test/Analysis/ValueTracking/knownbits-shl-ctlz-msb.ll
@@ -5,11 +5,6 @@
; When BitWidth is a power of 2 and Y < BitWidth-1, the result has its MSB
; at bit Y: bit Y is set and bits above Y are clear.
-declare i16 @llvm.ctlz.i16(i16, i1 immarg)
-declare i31 @llvm.ctlz.i31(i31, i1 immarg)
-declare i32 @llvm.ctlz.i32(i32, i1 immarg)
-declare i64 @llvm.ctlz.i64(i64, i1 immarg)
-
define i32 @msb_at_14_bit_set(i32 %x) {
; CHECK-LABEL: @msb_at_14_bit_set(
; CHECK-NEXT: ret i32 16384
@@ -34,30 +29,6 @@ define i32 @msb_at_14_high_bits_clear(i32 %x) {
ret i32 %r
}
-define i32 @msb_at_14_xor_commuted(i32 %x) {
-; CHECK-LABEL: @msb_at_14_xor_commuted(
-; CHECK-NEXT: ret i32 0
-;
- %c = call i32 @llvm.ctlz.i32(i32 %x, i1 true)
- %xored = xor i32 31, %c
- %shamt = sub i32 14, %xored
- %s = shl i32 %x, %shamt
- %r = and i32 %s, 4294934528
- ret i32 %r
-}
-
-define i32 @msb_at_14_zero_not_undef(i32 %x) {
-; CHECK-LABEL: @msb_at_14_zero_not_undef(
-; CHECK-NEXT: ret i32 16384
-;
- %c = call i32 @llvm.ctlz.i32(i32 %x, i1 false)
- %xored = xor i32 %c, 31
- %shamt = sub i32 14, %xored
- %s = shl i32 %x, %shamt
- %r = and i32 %s, 16384
- ret i32 %r
-}
-
define <2 x i32> @msb_at_14_vec(<2 x i32> %x) {
; CHECK-LABEL: @msb_at_14_vec(
; CHECK-NEXT: ret <2 x i32> splat (i32 16384)
More information about the llvm-commits
mailing list