[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