[llvm] [InstCombine] Fix incorrect is_zero_poison when folding select+ctlz to cttz (PR #202388)

Justin Lebar via llvm-commits llvm-commits at lists.llvm.org
Mon Jun 8 09:47:05 PDT 2026


https://github.com/jlebar created https://github.com/llvm/llvm-project/pull/202388

foldSelectCtlzToCttz folds

  %lz = call i32 @llvm.ctlz.i32(i32 (x & -x), i1 is_zero_poison)
  %r  = select (icmp eq x, 0), i32 32, i32 (xor %lz, 31)

into

  %r = call i32 @llvm.cttz.i32(i32 x, i1 is_zero_poison)

The original select's result is defined when x is zero, even if
is_zero_poison is true.  Therefore in the new cttz call, we need to pass
false for the second param, we can't reuse is_zero_poison.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply at anthropic.com>


>From 18756744f7cb54f12c8819c298833ed4b19cab6f Mon Sep 17 00:00:00 2001
From: Justin Lebar <justin.lebar at gmail.com>
Date: Sun, 7 Jun 2026 23:48:58 -0400
Subject: [PATCH] [InstCombine] Fix incorrect is_zero_poison when folding
 select+ctlz to cttz

foldSelectCtlzToCttz folds

  %lz = call i32 @llvm.ctlz.i32(i32 (x & -x), i1 is_zero_poison)
  %r  = select (icmp eq x, 0), i32 32, i32 (xor %lz, 31)

into

  %r = call i32 @llvm.cttz.i32(i32 x, i1 is_zero_poison)

The original select's result is defined when x is zero, even if
is_zero_poison is true.  Therefore in the new cttz call, we need to pass
false for the second param, we can't reuse is_zero_poison.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply at anthropic.com>
---
 .../InstCombine/InstCombineSelect.cpp         | 11 ++++++++-
 .../InstCombine/select-ctlz-to-cttz.ll        | 23 +++++++++++++++++++
 2 files changed, 33 insertions(+), 1 deletion(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
index 730dc53fcc557..dd5dbfcd4a1db 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
@@ -1530,9 +1530,18 @@ static Instruction *foldSelectCtlzToCttz(ICmpInst *ICI, Value *TrueVal,
   if (!match(II->getOperand(0), m_c_And(m_Specific(X), m_Neg(m_Specific(X)))))
     return nullptr;
 
+  // The select is either
+  //   (a) `x == 0 ?  a : 31 - a`  OR
+  //   (b) `x == 0 ? 32 : 31 - a`
+  // where `a = ctlz(x & -x)`.  In the former case, the result is poison if the
+  // `a` is poison, so we can propagate the ctlz's is_zero_poison flag to the
+  // cttz.  In the latter case, the result is not poison when x is 0.
+  Value *ZeroPoison =
+      TrueVal == Ctlz ? II->getArgOperand(1) : Builder.getFalse();
+
   Function *F = Intrinsic::getOrInsertDeclaration(
       II->getModule(), Intrinsic::cttz, II->getType());
-  return CallInst::Create(F, {X, II->getArgOperand(1)});
+  return CallInst::Create(F, {X, ZeroPoison});
 }
 
 /// Attempt to fold a cttz/ctlz followed by a icmp plus select into a single
diff --git a/llvm/test/Transforms/InstCombine/select-ctlz-to-cttz.ll b/llvm/test/Transforms/InstCombine/select-ctlz-to-cttz.ll
index fa689108ce187..d1f25b19d35a0 100644
--- a/llvm/test/Transforms/InstCombine/select-ctlz-to-cttz.ll
+++ b/llvm/test/Transforms/InstCombine/select-ctlz-to-cttz.ll
@@ -56,6 +56,29 @@ define i32 @select_clz_to_ctz_constant_for_zero(i32 %a) {
   ret i32 %cond
 }
 
+; The zero case selects the constant 32 (defined), but the ctlz has
+; is_zero_poison=true. The result cttz must use is_zero_poison=false, otherwise
+; the a==0 case becomes poison instead of 32. The extra use of the ctlz blocks
+; the standalone xor->cttz rewrite so the select reaches foldSelectCtlzToCttz.
+define i32 @select_clz_to_ctz_constant_for_zero_poison_flag(i32 %a) {
+; CHECK-LABEL: @select_clz_to_ctz_constant_for_zero_poison_flag(
+; CHECK-NEXT:    [[SUB:%.*]] = sub i32 0, [[A:%.*]]
+; CHECK-NEXT:    [[AND:%.*]] = and i32 [[A]], [[SUB]]
+; CHECK-NEXT:    [[LZ:%.*]] = tail call range(i32 0, 33) i32 @llvm.ctlz.i32(i32 [[AND]], i1 true)
+; CHECK-NEXT:    call void @use(i32 [[LZ]])
+; CHECK-NEXT:    [[COND:%.*]] = call range(i32 0, 33) i32 @llvm.cttz.i32(i32 [[A]], i1 false)
+; CHECK-NEXT:    ret i32 [[COND]]
+;
+  %sub = sub i32 0, %a
+  %and = and i32 %sub, %a
+  %lz = tail call i32 @llvm.ctlz.i32(i32 %and, i1 true)
+  call void @use(i32 %lz)
+  %tobool = icmp eq i32 %a, 0
+  %sub1 = xor i32 %lz, 31
+  %cond = select i1 %tobool, i32 32, i32 %sub1
+  ret i32 %cond
+}
+
 define <2 x i32> @select_clz_to_ctz_vec(<2 x i32> %a) {
 ; CHECK-LABEL: @select_clz_to_ctz_vec(
 ; CHECK-NEXT:    [[COND:%.*]] = call range(i32 0, 33) <2 x i32> @llvm.cttz.v2i32(<2 x i32> [[A:%.*]], i1 true)



More information about the llvm-commits mailing list