[llvm] [InstCombine] Fold cttz(mul X, OddC) -> cttz(X) (PR #214376)
via llvm-commits
llvm-commits at lists.llvm.org
Fri Aug 7 08:52:24 PDT 2026
https://github.com/Bagodiya updated https://github.com/llvm/llvm-project/pull/214376
>From 711b44508f9ce65644748aacc93ce6b10f253a33 Mon Sep 17 00:00:00 2001
From: Sofiya Bagodiya <sofiyabagodiya at gmail.com>
Date: Thu, 6 Aug 2026 21:09:07 -0400
Subject: [PATCH] [InstCombine] Fold cttz(mul X, OddC) -> cttz(X)
Multiplying by an odd constant preserves the trailing-zero count, so
cttz(X * OddC) simplifies to cttz(X). This is cttz-only; the identity
does not hold for ctlz.
Fixes #213877
---
.../InstCombine/InstCombineCalls.cpp | 5 +
llvm/test/Transforms/InstCombine/cttz.ll | 64 +++++++
.../Transforms/InstCombine/known-non-zero.ll | 26 +++
.../InstCombine/select-cmp-cttz-ctlz.ll | 174 ++++++++++++++++++
4 files changed, 269 insertions(+)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
index 5ee5009bd0262..7fee08ad21d28 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
@@ -522,6 +522,11 @@ static Instruction *foldCttzCtlz(IntrinsicInst &II, InstCombinerImpl &IC) {
if (match(Op0, m_c_And(m_Neg(m_Value(X)), m_Deferred(X))))
return CallInst::Create(II.getCalledFunction(), {X, Op1});
+ // cttz(mul(X, OddC)) -> cttz(X)
+ if (match(Op0, m_Mul(m_Value(X),
+ m_CheckedInt([](const APInt &C) { return C[0]; }))))
+ return CallInst::Create(II.getCalledFunction(), {X, Op1});
+
// cttz(sext(x)) -> cttz(zext(x))
if (match(Op0, m_OneUse(m_SExt(m_Value(X))))) {
auto *Zext = IC.Builder.CreateZExt(X, II.getType());
diff --git a/llvm/test/Transforms/InstCombine/cttz.ll b/llvm/test/Transforms/InstCombine/cttz.ll
index 7af67faab1d47..cd9e9881edc90 100644
--- a/llvm/test/Transforms/InstCombine/cttz.ll
+++ b/llvm/test/Transforms/InstCombine/cttz.ll
@@ -392,6 +392,70 @@ define i9 @fold_clz_log2_i9(i9 %x) {
%r = call i9 @llvm.ctlz(i9 %v, i1 true)
ret i9 %r
}
+
+define i32 @cttz_odd_mul(i32 %x) {
+; CHECK-LABEL: @cttz_odd_mul(
+; CHECK-NEXT: [[R:%.*]] = call range(i32 0, 33) i32 @llvm.cttz.i32(i32 [[X:%.*]], i1 false)
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %m = mul i32 %x, 3
+ %r = call i32 @llvm.cttz.i32(i32 %m, i1 false)
+ ret i32 %r
+}
+
+define i32 @cttz_odd_mul_zero_poison(i32 %x) {
+; CHECK-LABEL: @cttz_odd_mul_zero_poison(
+; CHECK-NEXT: [[R:%.*]] = call range(i32 0, 33) i32 @llvm.cttz.i32(i32 [[X:%.*]], i1 true)
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %m = mul i32 %x, 5
+ %r = call i32 @llvm.cttz.i32(i32 %m, i1 true)
+ ret i32 %r
+}
+
+define <2 x i64> @cttz_odd_mul_splat(<2 x i64> %x) {
+; CHECK-LABEL: @cttz_odd_mul_splat(
+; CHECK-NEXT: [[R:%.*]] = call range(i64 0, 65) <2 x i64> @llvm.cttz.v2i64(<2 x i64> [[X:%.*]], i1 false)
+; CHECK-NEXT: ret <2 x i64> [[R]]
+;
+ %m = mul <2 x i64> %x, splat (i64 3)
+ %r = call <2 x i64> @llvm.cttz.v2i64(<2 x i64> %m, i1 false)
+ ret <2 x i64> %r
+}
+
+define i32 @cttz_even_mul(i32 %x) {
+; CHECK-LABEL: @cttz_even_mul(
+; CHECK-NEXT: [[M:%.*]] = mul i32 [[X:%.*]], 6
+; CHECK-NEXT: [[R:%.*]] = call range(i32 1, 33) i32 @llvm.cttz.i32(i32 [[M]], i1 false)
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %m = mul i32 %x, 6
+ %r = call i32 @llvm.cttz.i32(i32 %m, i1 false)
+ ret i32 %r
+}
+
+define i32 @cttz_odd_mul_multiuse(i32 %x) {
+; CHECK-LABEL: @cttz_odd_mul_multiuse(
+; CHECK-NEXT: [[M:%.*]] = mul i32 [[X:%.*]], 3
+; CHECK-NEXT: call void @use(i32 [[M]])
+; CHECK-NEXT: [[R:%.*]] = call range(i32 0, 33) i32 @llvm.cttz.i32(i32 [[X]], i1 false)
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %m = mul i32 %x, 3
+ call void @use(i32 %m)
+ %r = call i32 @llvm.cttz.i32(i32 %m, i1 false)
+ ret i32 %r
+}
+
+define i32 @cttz_odd_mul_nsw(i32 %x) {
+; CHECK-LABEL: @cttz_odd_mul_nsw(
+; CHECK-NEXT: [[R:%.*]] = call range(i32 0, 33) i32 @llvm.cttz.i32(i32 [[X:%.*]], i1 false)
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %m = mul nsw i32 %x, 3
+ %r = call i32 @llvm.cttz.i32(i32 %m, i1 false)
+ ret i32 %r
+}
;.
; CHECK: [[PROF0]] = !{!"branch_weights", i32 1, i32 2}
;.
diff --git a/llvm/test/Transforms/InstCombine/known-non-zero.ll b/llvm/test/Transforms/InstCombine/known-non-zero.ll
index 9bfec0d7289dd..ead43b2ea29a8 100644
--- a/llvm/test/Transforms/InstCombine/known-non-zero.ll
+++ b/llvm/test/Transforms/InstCombine/known-non-zero.ll
@@ -315,3 +315,29 @@ non_zero:
exit:
ret i64 -1
}
+
+define i32 @test0_odd_mul(i64 %x) {
+; CHECK-LABEL: @test0_odd_mul(
+; CHECK-NEXT: start:
+; CHECK-NEXT: [[C:%.*]] = icmp eq i64 [[X:%.*]], 0
+; CHECK-NEXT: br i1 [[C]], label [[EXIT:%.*]], label [[NON_ZERO:%.*]]
+; CHECK: non_zero:
+; CHECK-NEXT: [[CTZ:%.*]] = call range(i64 0, 65) i64 @llvm.cttz.i64(i64 [[X]], i1 true)
+; CHECK-NEXT: [[CTZ32:%.*]] = trunc nuw nsw i64 [[CTZ]] to i32
+; CHECK-NEXT: br label [[EXIT]]
+; CHECK: exit:
+; CHECK-NEXT: [[RES:%.*]] = phi i32 [ [[CTZ32]], [[NON_ZERO]] ], [ 0, [[START:%.*]] ]
+; CHECK-NEXT: ret i32 [[RES]]
+;
+start:
+ %c = icmp eq i64 %x, 0
+ br i1 %c, label %exit, label %non_zero
+non_zero:
+ %m = mul i64 %x, 3
+ %ctz = call i64 @llvm.cttz.i64(i64 %m, i1 false)
+ %ctz32 = trunc i64 %ctz to i32
+ br label %exit
+exit:
+ %res = phi i32 [ %ctz32, %non_zero ], [ 0, %start ]
+ ret i32 %res
+}
diff --git a/llvm/test/Transforms/InstCombine/select-cmp-cttz-ctlz.ll b/llvm/test/Transforms/InstCombine/select-cmp-cttz-ctlz.ll
index c1afdfef97a1c..5d76cfb73f2a4 100644
--- a/llvm/test/Transforms/InstCombine/select-cmp-cttz-ctlz.ll
+++ b/llvm/test/Transforms/InstCombine/select-cmp-cttz-ctlz.ll
@@ -835,6 +835,180 @@ define i32 @test_abs_int_min_poison_wrong_const(i32 %arg) {
ret i32 %sel
}
+; (X == 0) ? C : ctz(X * OddC) --> is_zero_poison can be set
+
+define i32 @test_cttz_not_bw_odd_mul(i32 %x) {
+; CHECK-LABEL: @test_cttz_not_bw_odd_mul(
+; CHECK-NEXT: [[CT:%.*]] = call range(i32 0, 33) i32 @llvm.cttz.i32(i32 [[X:%.*]], i1 true)
+; CHECK-NEXT: [[CMP_NOT:%.*]] = icmp eq i32 [[X]], 0
+; CHECK-NEXT: [[RES:%.*]] = select i1 [[CMP_NOT]], i32 123, i32 [[CT]]
+; CHECK-NEXT: ret i32 [[RES]]
+;
+ %m = mul i32 %x, 3
+ %ct = tail call i32 @llvm.cttz.i32(i32 %m, i1 false)
+ %cmp = icmp ne i32 %x, 0
+ %res = select i1 %cmp, i32 %ct, i32 123
+ ret i32 %res
+}
+
+define i32 @test_cttz_not_bw_odd_mul_eq(i32 %x) {
+; CHECK-LABEL: @test_cttz_not_bw_odd_mul_eq(
+; CHECK-NEXT: [[CT:%.*]] = call range(i32 0, 33) i32 @llvm.cttz.i32(i32 [[X:%.*]], i1 true)
+; CHECK-NEXT: [[CMP:%.*]] = icmp eq i32 [[X]], 0
+; CHECK-NEXT: [[RES:%.*]] = select i1 [[CMP]], i32 123, i32 [[CT]]
+; CHECK-NEXT: ret i32 [[RES]]
+;
+ %m = mul i32 %x, 3
+ %ct = tail call i32 @llvm.cttz.i32(i32 %m, i1 false)
+ %cmp = icmp eq i32 %x, 0
+ %res = select i1 %cmp, i32 123, i32 %ct
+ ret i32 %res
+}
+
+
+define i32 @test_cttz_not_bw_large_odd_mul(i32 %x) {
+; CHECK-LABEL: @test_cttz_not_bw_large_odd_mul(
+; CHECK-NEXT: [[CT:%.*]] = call range(i32 0, 33) i32 @llvm.cttz.i32(i32 [[X:%.*]], i1 true)
+; CHECK-NEXT: [[CMP_NOT:%.*]] = icmp eq i32 [[X]], 0
+; CHECK-NEXT: [[RES:%.*]] = select i1 [[CMP_NOT]], i32 123, i32 [[CT]]
+; CHECK-NEXT: ret i32 [[RES]]
+;
+ %m = mul i32 %x, 1234567
+ %ct = tail call i32 @llvm.cttz.i32(i32 %m, i1 false)
+ %cmp = icmp ne i32 %x, 0
+ %res = select i1 %cmp, i32 %ct, i32 123
+ ret i32 %res
+}
+
+
+define <2 x i32> @test_cttz_not_bw_odd_mul_vec(<2 x i32> %x) {
+; CHECK-LABEL: @test_cttz_not_bw_odd_mul_vec(
+; CHECK-NEXT: [[CT:%.*]] = call range(i32 0, 33) <2 x i32> @llvm.cttz.v2i32(<2 x i32> [[X:%.*]], i1 true)
+; CHECK-NEXT: [[CMP_NOT:%.*]] = icmp eq <2 x i32> [[X]], zeroinitializer
+; CHECK-NEXT: [[RES:%.*]] = select <2 x i1> [[CMP_NOT]], <2 x i32> splat (i32 123), <2 x i32> [[CT]]
+; CHECK-NEXT: ret <2 x i32> [[RES]]
+;
+ %m = mul <2 x i32> %x, splat (i32 3)
+ %ct = tail call <2 x i32> @llvm.cttz.v2i32(<2 x i32> %m, i1 false)
+ %cmp = icmp ne <2 x i32> %x, zeroinitializer
+ %res = select <2 x i1> %cmp, <2 x i32> %ct, <2 x i32> splat (i32 123)
+ ret <2 x i32> %res
+}
+
+; negative test - X * 6 can be zero for non-zero X (X = 2^31)
+
+define i32 @test_cttz_not_bw_even_mul(i32 %x) {
+; CHECK-LABEL: @test_cttz_not_bw_even_mul(
+; CHECK-NEXT: [[M:%.*]] = mul i32 [[X:%.*]], 6
+; CHECK-NEXT: [[CT:%.*]] = tail call range(i32 1, 33) i32 @llvm.cttz.i32(i32 [[M]], i1 false)
+; CHECK-NEXT: [[CMP_NOT:%.*]] = icmp eq i32 [[X]], 0
+; CHECK-NEXT: [[RES:%.*]] = select i1 [[CMP_NOT]], i32 123, i32 [[CT]]
+; CHECK-NEXT: ret i32 [[RES]]
+;
+ %m = mul i32 %x, 6
+ %ct = tail call i32 @llvm.cttz.i32(i32 %m, i1 false)
+ %cmp = icmp ne i32 %x, 0
+ %res = select i1 %cmp, i32 %ct, i32 123
+ ret i32 %res
+}
+
+; negative test
+
+define i32 @test_cttz_not_bw_even_mul_eq(i32 %x) {
+; CHECK-LABEL: @test_cttz_not_bw_even_mul_eq(
+; CHECK-NEXT: [[M:%.*]] = mul i32 [[X:%.*]], 12
+; CHECK-NEXT: [[CT:%.*]] = tail call range(i32 2, 33) i32 @llvm.cttz.i32(i32 [[M]], i1 false)
+; CHECK-NEXT: [[CMP:%.*]] = icmp eq i32 [[X]], 0
+; CHECK-NEXT: [[RES:%.*]] = select i1 [[CMP]], i32 123, i32 [[CT]]
+; CHECK-NEXT: ret i32 [[RES]]
+;
+ %m = mul i32 %x, 12
+ %ct = tail call i32 @llvm.cttz.i32(i32 %m, i1 false)
+ %cmp = icmp eq i32 %x, 0
+ %res = select i1 %cmp, i32 123, i32 %ct
+ ret i32 %res
+}
+
+; negative test - one lane is even
+
+define <2 x i32> @test_cttz_not_bw_mixed_mul_vec(<2 x i32> %x) {
+; CHECK-LABEL: @test_cttz_not_bw_mixed_mul_vec(
+; CHECK-NEXT: [[M:%.*]] = mul <2 x i32> [[X:%.*]], <i32 3, i32 6>
+; CHECK-NEXT: [[CT:%.*]] = tail call range(i32 0, 33) <2 x i32> @llvm.cttz.v2i32(<2 x i32> [[M]], i1 false)
+; CHECK-NEXT: [[CMP_NOT:%.*]] = icmp eq <2 x i32> [[X]], zeroinitializer
+; CHECK-NEXT: [[RES:%.*]] = select <2 x i1> [[CMP_NOT]], <2 x i32> splat (i32 123), <2 x i32> [[CT]]
+; CHECK-NEXT: ret <2 x i32> [[RES]]
+;
+ %m = mul <2 x i32> %x, <i32 3, i32 6>
+ %ct = tail call <2 x i32> @llvm.cttz.v2i32(<2 x i32> %m, i1 false)
+ %cmp = icmp ne <2 x i32> %x, zeroinitializer
+ %res = select <2 x i1> %cmp, <2 x i32> %ct, <2 x i32> splat (i32 123)
+ ret <2 x i32> %res
+}
+
+; negative test - compared against a non-zero constant
+
+define i32 @test_cttz_not_bw_odd_mul_nonzero_cmp(i32 %x) {
+; CHECK-LABEL: @test_cttz_not_bw_odd_mul_nonzero_cmp(
+; CHECK-NEXT: [[CT:%.*]] = call range(i32 0, 33) i32 @llvm.cttz.i32(i32 [[X:%.*]], i1 false)
+; CHECK-NEXT: [[CMP_NOT:%.*]] = icmp eq i32 [[X]], 5
+; CHECK-NEXT: [[RES:%.*]] = select i1 [[CMP_NOT]], i32 123, i32 [[CT]]
+; CHECK-NEXT: ret i32 [[RES]]
+;
+ %m = mul i32 %x, 3
+ %ct = tail call i32 @llvm.cttz.i32(i32 %m, i1 false)
+ %cmp = icmp ne i32 %x, 5
+ %res = select i1 %cmp, i32 %ct, i32 123
+ ret i32 %res
+}
+
+; negative test - mul operand is not the compared value
+
+define i32 @test_cttz_not_bw_odd_mul_wrong_op(i32 %x, i32 %y) {
+; CHECK-LABEL: @test_cttz_not_bw_odd_mul_wrong_op(
+; CHECK-NEXT: [[CT:%.*]] = call range(i32 0, 33) i32 @llvm.cttz.i32(i32 [[Y:%.*]], i1 false)
+; CHECK-NEXT: [[CMP_NOT:%.*]] = icmp eq i32 [[X:%.*]], 0
+; CHECK-NEXT: [[RES:%.*]] = select i1 [[CMP_NOT]], i32 123, i32 [[CT]]
+; CHECK-NEXT: ret i32 [[RES]]
+;
+ %m = mul i32 %y, 3
+ %ct = tail call i32 @llvm.cttz.i32(i32 %m, i1 false)
+ %cmp = icmp ne i32 %x, 0
+ %res = select i1 %cmp, i32 %ct, i32 123
+ ret i32 %res
+}
+
+; negative test - extra use of the intrinsic
+
+define i32 @test_cttz_not_bw_odd_mul_multiuse(i32 %x) {
+; CHECK-LABEL: @test_cttz_not_bw_odd_mul_multiuse(
+; CHECK-NEXT: [[CT:%.*]] = call range(i32 0, 33) i32 @llvm.cttz.i32(i32 [[X:%.*]], i1 false)
+; CHECK-NEXT: [[CMP_NOT:%.*]] = icmp eq i32 [[X]], 0
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP_NOT]], i32 123, i32 [[CT]]
+; CHECK-NEXT: [[RES:%.*]] = or i32 [[SEL]], [[CT]]
+; CHECK-NEXT: ret i32 [[RES]]
+;
+ %m = mul i32 %x, 3
+ %ct = tail call i32 @llvm.cttz.i32(i32 %m, i1 false)
+ %cmp = icmp ne i32 %x, 0
+ %sel = select i1 %cmp, i32 %ct, i32 123
+ %res = or i32 %sel, %ct
+ ret i32 %res
+}
+define <2 x i32> @test_cttz_not_bw_odd_mul_vec_nonsplat(<2 x i32> %x) {
+; CHECK-LABEL: @test_cttz_not_bw_odd_mul_vec_nonsplat(
+; CHECK-NEXT: [[CT:%.*]] = call range(i32 0, 33) <2 x i32> @llvm.cttz.v2i32(<2 x i32> [[X:%.*]], i1 true)
+; CHECK-NEXT: [[CMP_NOT:%.*]] = icmp eq <2 x i32> [[X]], zeroinitializer
+; CHECK-NEXT: [[RES:%.*]] = select <2 x i1> [[CMP_NOT]], <2 x i32> splat (i32 123), <2 x i32> [[CT]]
+; CHECK-NEXT: ret <2 x i32> [[RES]]
+;
+ %m = mul <2 x i32> %x, <i32 3, i32 5>
+ %ct = tail call <2 x i32> @llvm.cttz.v2i32(<2 x i32> %m, i1 false)
+ %cmp = icmp ne <2 x i32> %x, zeroinitializer
+ %res = select <2 x i1> %cmp, <2 x i32> %ct, <2 x i32> splat (i32 123)
+ ret <2 x i32> %res
+}
+
declare i16 @llvm.ctlz.i16(i16, i1)
declare i32 @llvm.ctlz.i32(i32, i1)
declare i64 @llvm.ctlz.i64(i64, i1)
More information about the llvm-commits
mailing list