[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