[llvm] [InstCombine] Fold shl of constant by cttz into multiply of lowest set bit (PR #214517)

via llvm-commits llvm-commits at lists.llvm.org
Wed Aug 12 21:26:19 PDT 2026


https://github.com/milkHongYe updated https://github.com/llvm/llvm-project/pull/214517

>From eaa2f15c096b73e480a94755de27fc8752a5f5a7 Mon Sep 17 00:00:00 2001
From: milkHongYe <1483685464 at qq.com>
Date: Thu, 6 Aug 2026 23:28:11 +0800
Subject: [PATCH 1/2] [InstCombine] Fold shl of constant by cttz into multiply
 of lowest set bit

---
 .../Transforms/InstCombine/InstCombineShifts.cpp    | 13 +++++++++++++
 llvm/test/Transforms/InstCombine/shift.ll           |  5 +++--
 2 files changed, 16 insertions(+), 2 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp b/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp
index 241bc3bba51c2..5eaa326faaf10 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp
@@ -1382,6 +1382,19 @@ Instruction *InstCombinerImpl::visitShl(BinaryOperator &I) {
     }
   }
 
+  // C << (cttz X, true) --> (-X & X) * C  (C must be a scalar constant, cttz
+  // must have one use)
+  {
+    Value *X;
+    const APInt *C;
+    if (match(Op0, m_APInt(C)) &&
+        match(Op1, m_OneUse(m_Cttz(m_Value(X), m_One())))) {
+      Value *NegX = Builder.CreateNeg(X, "neg");
+      Value *LowBit = Builder.CreateAnd(NegX, X);
+      return BinaryOperator::CreateMul(LowBit, Op0);
+    }
+  }
+
   return nullptr;
 }
 
diff --git a/llvm/test/Transforms/InstCombine/shift.ll b/llvm/test/Transforms/InstCombine/shift.ll
index d63ba48830068..4c05a56b1dbc0 100644
--- a/llvm/test/Transforms/InstCombine/shift.ll
+++ b/llvm/test/Transforms/InstCombine/shift.ll
@@ -2097,8 +2097,9 @@ define i32 @shl1_cttz_extra_use(i32 %x) {
 
 define i32 @shl2_cttz(i32 %x) {
 ; CHECK-LABEL: @shl2_cttz(
-; CHECK-NEXT:    [[TZ:%.*]] = call range(i32 0, 33) i32 @llvm.cttz.i32(i32 [[X:%.*]], i1 true)
-; CHECK-NEXT:    [[SHL:%.*]] = shl i32 2, [[TZ]]
+; CHECK-NEXT:    [[NEG:%.*]] = sub i32 0, [[X:%.*]]
+; CHECK-NEXT:    [[TMP1:%.*]] = and i32 [[X]], [[NEG]]
+; CHECK-NEXT:    [[SHL:%.*]] = shl i32 [[TMP1]], 1
 ; CHECK-NEXT:    ret i32 [[SHL]]
 ;
   %tz = call i32 @llvm.cttz.i32(i32 %x, i1 true)

>From a2b3ba4a10e7a5c3e188111ca573dc73ee7edbea Mon Sep 17 00:00:00 2001
From: milkHongYe <1483685464 at qq.com>
Date: Thu, 13 Aug 2026 12:15:59 +0800
Subject: [PATCH 2/2] [InstCombine] Add some tests for shl-cttz optimizaion

---
 llvm/test/Transforms/InstCombine/shift.ll | 40 +++++++++++++++++++++++
 1 file changed, 40 insertions(+)

diff --git a/llvm/test/Transforms/InstCombine/shift.ll b/llvm/test/Transforms/InstCombine/shift.ll
index 4c05a56b1dbc0..074ff7a8ed993 100644
--- a/llvm/test/Transforms/InstCombine/shift.ll
+++ b/llvm/test/Transforms/InstCombine/shift.ll
@@ -2107,6 +2107,46 @@ define i32 @shl2_cttz(i32 %x) {
   ret i32 %shl
 }
 
+; C=4: different constant, should also fold
+define i32 @shl4_cttz(i32 %x) {
+; CHECK-LABEL: @shl4_cttz(
+; CHECK-NEXT:    [[NEG:%.*]] = sub i32 0, [[X:%.*]]
+; CHECK-NEXT:    [[TMP1:%.*]] = and i32 [[X]], [[NEG]]
+; CHECK-NEXT:    [[SHL:%.*]] = shl i32 [[TMP1]], 2
+; CHECK-NEXT:    ret i32 [[SHL]]
+;
+  %tz = call i32 @llvm.cttz.i32(i32 %x, i1 true)
+  %shl = shl i32 4, %tz
+  ret i32 %shl
+}
+
+; negative test: cttz has multiple uses, should not fold
+define i32 @shl2_cttz_multiuse(i32 %x) {
+; CHECK-LABEL: @shl2_cttz_multiuse(
+; CHECK-NEXT:    [[TZ:%.*]] = call range(i32 0, 33) i32 @llvm.cttz.i32(i32 [[X:%.*]], i1 true)
+; CHECK-NEXT:    call void @use_i32(i32 [[TZ]])
+; CHECK-NEXT:    [[SHL:%.*]] = shl i32 2, [[TZ]]
+; CHECK-NEXT:    ret i32 [[SHL]]
+;
+  %tz = call i32 @llvm.cttz.i32(i32 %x, i1 true)
+  call void @use_i32(i32 %tz)
+  %shl = shl i32 2, %tz
+  ret i32 %shl
+}
+
+; negative test: is_zero_poison = false, should not fold
+define i32 @shl2_cttz_zero_poison_false(i32 %x) {
+; CHECK-LABEL: @shl2_cttz_zero_poison_false(
+; CHECK-NEXT:    [[NEG:%.*]] = sub i32 0, [[X:%.*]]
+; CHECK-NEXT:    [[TMP1:%.*]] = and i32 [[X]], [[NEG]]
+; CHECK-NEXT:    [[SHL:%.*]] = shl i32 [[TMP1]], 1
+; CHECK-NEXT:    ret i32 [[SHL]]
+;
+  %tz = call i32 @llvm.cttz.i32(i32 %x, i1 false)
+  %shl = shl i32 2, %tz
+  ret i32 %shl
+}
+
 ; shift (X, amt | bitwidth - 1) -> shift (X, bitwidth - 1)
 define i6 @shl_or7_eq_shl7(i6 %x, i6 %c) {
 ; CHECK-LABEL: @shl_or7_eq_shl7(



More information about the llvm-commits mailing list