[llvm] [DAG] computeKnownBits - add CTLS handling (PR #174824)
Gergo Stomfai via llvm-commits
llvm-commits at lists.llvm.org
Thu Jan 8 01:32:04 PST 2026
https://github.com/stomfaig updated https://github.com/llvm/llvm-project/pull/174824
>From 195c522e8059842f5c908a6ba2ce1cd06627004f Mon Sep 17 00:00:00 2001
From: stomfaig <stomfaig at gmail.com>
Date: Wed, 7 Jan 2026 18:23:23 +0000
Subject: [PATCH 1/6] add handling CTLS in DAG
---
llvm/lib/CodeGen/SelectionDAG/SelectionDAG.cpp | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAG.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAG.cpp
index 15f86cb94f958..ba130a78a1af7 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAG.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAG.cpp
@@ -3870,6 +3870,14 @@ KnownBits SelectionDAG::computeKnownBits(SDValue Op, const APInt &DemandedElts,
Known.Zero.setBitsFrom(LowBits);
break;
}
+ case ISD::CTLS: {
+ unsigned MinRedundantSignBits =
+ ComputeNumSignBits(Op.getOperand(0), DemandedElts, Depth + 1);
+ ConstantRange Range(APInt(BitWidth, MinRedundantSignBits),
+ APInt(BitWidth, 32));
+ Known = Range.toKnownBits();
+ break;
+ }
case ISD::CTPOP: {
Known2 = computeKnownBits(Op.getOperand(0), DemandedElts, Depth + 1);
// If we know some of the bits are zero, they can't be one.
>From 7b4c9e9067a200c2e23bdb28d858f15420334ed1 Mon Sep 17 00:00:00 2001
From: stomfaig <stomfaig at gmail.com>
Date: Wed, 7 Jan 2026 18:23:54 +0000
Subject: [PATCH 2/6] add tests
---
llvm/test/CodeGen/AArch64/cls.ll | 77 ++++++++++++++++++++++++++++++++
llvm/test/CodeGen/RISCV/rv32p.ll | 64 ++++++++++++++++++++++++++
2 files changed, 141 insertions(+)
diff --git a/llvm/test/CodeGen/AArch64/cls.ll b/llvm/test/CodeGen/AArch64/cls.ll
index f17ccf7d6f682..7a053a9483e3f 100644
--- a/llvm/test/CodeGen/AArch64/cls.ll
+++ b/llvm/test/CodeGen/AArch64/cls.ll
@@ -18,3 +18,80 @@ define i32 @cls64(i64 %t) {
declare i32 @llvm.aarch64.cls(i32) nounwind
declare i32 @llvm.aarch64.cls64(i64) nounwind
+
+define i8 @cls_i8(i8 %x) {
+; CHECK-LABEL: cls_i8:
+; CHECK: // %bb.0:
+; CHECK-NEXT: sxtb w8, w0
+; CHECK-NEXT: cls w8, w8
+; CHECK-NEXT: sub w0, w8, #24
+; CHECK-NEXT: ret
+
+ %a = ashr i8 %x, 7
+ %b = xor i8 %x, %a
+ %c = call i8 @llvm.ctlz.i8(i8 %b, i1 false)
+ %d = sub i8 %c, 1
+ ret i8 %d
+}
+
+; The result is in the range [1-31], so we don't need an andi after the cls.
+define i32 @cls_i32_knownbits(i32 %x) {
+; CHECK-LABEL: cls_i32_knownbits:
+; CHECK: // %bb.0:
+; CHECK-NEXT: cls w0, w0
+; CHECK-NEXT: ret
+ %a = ashr i32 %x, 31
+ %b = xor i32 %x, %a
+ %c = call i32 @llvm.ctlz.i32(i32 %b, i1 false)
+ %d = sub i32 %c, 1
+ %e = and i32 %d, 31
+ ret i32 %e
+}
+
+; There are at least 16 redundant sign bits so we don't need an ori after the clsw.
+define i32 @cls_i32_knownbits_2(i16 signext %x) {
+; CHECK-LABEL: cls_i32_knownbits_2:
+; CHECK: // %bb.0:
+; CHECK-NEXT: cls w0, w0
+; CHECK-NEXT: ret
+ %sext = sext i16 %x to i32
+ %a = ashr i32 %sext, 31
+ %b = xor i32 %sext, %a
+ %c = call i32 @llvm.ctlz.i32(i32 %b, i1 false)
+ %d = sub i32 %c, 1
+ %e = or i32 %d, 16
+ ret i32 %e
+}
+
+define i32 @cls_i32_knownbits_3(i8 signext %x) {
+; CHECK-LABEL: cls_i32_knownbits_3:
+; CHECK: // %bb.0:
+; CHECK-NEXT: cls w0, w0
+; CHECK-NEXT: ret
+ %sext = sext i8 %x to i32
+ %a = ashr i32 %sext, 31
+ %b = xor i32 %sext, %a
+ %c = call i32 @llvm.ctlz.i32(i32 %b, i1 false)
+ %d = sub i32 %c, 1
+ %e = or i32 %d, 24
+ ret i32 %e
+}
+
+; Negative test. We only know there is at least 1 redundant sign bit. We can't
+; remove the ori.
+define i32 @cls_i32_knownbits_4(i32 signext %x) {
+; CHECK-LABEL: cls_i32_knownbits_4:
+; CHECK: // %bb.0:
+; CHECK-NEXT: sbfx w8, w0, #0, #31
+; CHECK-NEXT: cls w8, w8
+; CHECK-NEXT: orr w0, w8, #0x1
+; CHECK-NEXT: ret
+ %shl = shl i32 %x, 1
+ %ashr = ashr i32 %shl, 1
+ %a = ashr i32 %ashr, 31
+ %b = xor i32 %ashr, %a
+ %c = call i32 @llvm.ctlz.i32(i32 %b, i1 false)
+ %d = sub i32 %c, 1
+ %e = or i32 %d, 1
+ ret i32 %e
+ }
diff --git a/llvm/test/CodeGen/RISCV/rv32p.ll b/llvm/test/CodeGen/RISCV/rv32p.ll
index 9faac5df2a414..11cdf3ea4799f 100644
--- a/llvm/test/CodeGen/RISCV/rv32p.ll
+++ b/llvm/test/CodeGen/RISCV/rv32p.ll
@@ -234,6 +234,70 @@ define i64 @cls_i64_2(i64 %x) {
ret i64 %e
}
+; The result is in the range [1-31], so we don't need an andi after the cls.
+define i32 @cls_i32_knownbits(i32 %x) {
+; CHECK-LABEL: cls_i32_knownbits:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cls a0, a0
+; CHECK-NEXT: ret
+ %a = ashr i32 %x, 31
+ %b = xor i32 %x, %a
+ %c = call i32 @llvm.ctlz.i32(i32 %b, i1 false)
+ %d = sub i32 %c, 1
+ %e = and i32 %d, 31
+ ret i32 %e
+}
+
+; There are at least 16 redundant sign bits so we don't need an ori after the clsw.
+define i32 @cls_i32_knownbits_2(i16 signext %x) {
+; CHECK-LABEL: cls_i32_knownbits_2:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cls a0, a0
+; CHECK-NEXT: ret
+ %sext = sext i16 %x to i32
+ %a = ashr i32 %sext, 31
+ %b = xor i32 %sext, %a
+ %c = call i32 @llvm.ctlz.i32(i32 %b, i1 false)
+ %d = sub i32 %c, 1
+ %e = or i32 %d, 16
+ ret i32 %e
+}
+
+define i32 @cls_i32_knownbits_3(i8 signext %x) {
+; CHECK-LABEL: cls_i32_knownbits_3:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cls a0, a0
+; CHECK-NEXT: ret
+ %sext = sext i8 %x to i32
+ %a = ashr i32 %sext, 31
+ %b = xor i32 %sext, %a
+ %c = call i32 @llvm.ctlz.i32(i32 %b, i1 false)
+ %d = sub i32 %c, 1
+ %e = or i32 %d, 24
+ ret i32 %e
+}
+
+; Negative test. We only know there is at least 1 redundant sign bit. We can't
+; remove the ori.
+define i32 @cls_i32_knownbits_4(i32 signext %x) {
+; CHECK-LABEL: cls_i32_knownbits_4:
+; CHECK: # %bb.0:
+; CHECK-NEXT: slli a0, a0, 1
+; CHECK-NEXT: srai a0, a0, 1
+; CHECK-NEXT: cls a0, a0
+; CHECK-NEXT: ori a0, a0, 1
+; CHECK-NEXT: ret
+ %shl = shl i32 %x, 1
+ %ashr = ashr i32 %shl, 1
+ %a = ashr i32 %ashr, 31
+ %b = xor i32 %ashr, %a
+ %c = call i32 @llvm.ctlz.i32(i32 %b, i1 false)
+ %d = sub i32 %c, 1
+ %e = or i32 %d, 1
+ ret i32 %e
+ }
+
+
define i64 @slx_i64(i64 %x, i64 %y) {
; CHECK-LABEL: slx_i64:
; CHECK: # %bb.0:
>From e87003b90120ddf34df00ba7e313496a2cf30e13 Mon Sep 17 00:00:00 2001
From: stomfaig <stomfaig at gmail.com>
Date: Wed, 7 Jan 2026 18:30:33 +0000
Subject: [PATCH 3/6] recover docstrings
---
llvm/test/CodeGen/AArch64/cls.ll | 1 +
llvm/test/CodeGen/RISCV/rv32p.ll | 1 +
2 files changed, 2 insertions(+)
diff --git a/llvm/test/CodeGen/AArch64/cls.ll b/llvm/test/CodeGen/AArch64/cls.ll
index 7a053a9483e3f..1eb6f7411a49d 100644
--- a/llvm/test/CodeGen/AArch64/cls.ll
+++ b/llvm/test/CodeGen/AArch64/cls.ll
@@ -63,6 +63,7 @@ define i32 @cls_i32_knownbits_2(i16 signext %x) {
ret i32 %e
}
+; There are at least 24 redundant sign bits so we don't need an ori after the clsw.
define i32 @cls_i32_knownbits_3(i8 signext %x) {
; CHECK-LABEL: cls_i32_knownbits_3:
; CHECK: // %bb.0:
diff --git a/llvm/test/CodeGen/RISCV/rv32p.ll b/llvm/test/CodeGen/RISCV/rv32p.ll
index 11cdf3ea4799f..9611af1ee8c0e 100644
--- a/llvm/test/CodeGen/RISCV/rv32p.ll
+++ b/llvm/test/CodeGen/RISCV/rv32p.ll
@@ -263,6 +263,7 @@ define i32 @cls_i32_knownbits_2(i16 signext %x) {
ret i32 %e
}
+; There are at least 24 redundant sign bits so we don't need an ori after the clsw.
define i32 @cls_i32_knownbits_3(i8 signext %x) {
; CHECK-LABEL: cls_i32_knownbits_3:
; CHECK: # %bb.0:
>From 5b83242f45949bdf5664f97ade0557b56dbf1c8e Mon Sep 17 00:00:00 2001
From: stomfaig <stomfaig at gmail.com>
Date: Wed, 7 Jan 2026 22:17:03 +0000
Subject: [PATCH 4/6] correct left-in test logic and ctls semantics
---
llvm/lib/CodeGen/SelectionDAG/SelectionDAG.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAG.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAG.cpp
index ba130a78a1af7..9258ca0bef3b8 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAG.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAG.cpp
@@ -3872,9 +3872,9 @@ KnownBits SelectionDAG::computeKnownBits(SDValue Op, const APInt &DemandedElts,
}
case ISD::CTLS: {
unsigned MinRedundantSignBits =
- ComputeNumSignBits(Op.getOperand(0), DemandedElts, Depth + 1);
+ ComputeNumSignBits(Op.getOperand(0), DemandedElts, Depth + 1) - 1;
ConstantRange Range(APInt(BitWidth, MinRedundantSignBits),
- APInt(BitWidth, 32));
+ APInt(BitWidth, BitWidth));
Known = Range.toKnownBits();
break;
}
>From 2183625ef903b13628b07e043840b2ec8093aeb1 Mon Sep 17 00:00:00 2001
From: stomfaig <stomfaig at gmail.com>
Date: Thu, 8 Jan 2026 09:02:02 +0000
Subject: [PATCH 5/6] fixed knownbits range max and knownbits overestimation
test
---
llvm/test/CodeGen/AArch64/cls.ll | 38 +++++++++++++++++++++++++++++++-
llvm/test/CodeGen/RISCV/rv32p.ll | 17 ++++++++++++++
llvm/test/CodeGen/RISCV/rv64p.ll | 19 ++++++++++++++++
3 files changed, 73 insertions(+), 1 deletion(-)
diff --git a/llvm/test/CodeGen/AArch64/cls.ll b/llvm/test/CodeGen/AArch64/cls.ll
index 1eb6f7411a49d..745c287e6134c 100644
--- a/llvm/test/CodeGen/AArch64/cls.ll
+++ b/llvm/test/CodeGen/AArch64/cls.ll
@@ -48,7 +48,7 @@ define i32 @cls_i32_knownbits(i32 %x) {
ret i32 %e
}
-; There are at least 16 redundant sign bits so we don't need an ori after the clsw.
+; There are at least 16 redundant sign bits so we don't need an ori after the cls.
define i32 @cls_i32_knownbits_2(i16 signext %x) {
; CHECK-LABEL: cls_i32_knownbits_2:
; CHECK: // %bb.0:
@@ -63,6 +63,24 @@ define i32 @cls_i32_knownbits_2(i16 signext %x) {
ret i32 %e
}
+; Check that the range max in ctls cls knownbits
+; is not set to 32
+define i64 @cls_i64_knownbits_2(i32 signext %x) {
+; CHECK-LABEL: cls_i64_knownbits_2:
+; CHECK: // %bb.0:
+; CHECK-NEXT: //{{.*}}
+; CHECK-NEXT: sxtw x8, w0
+; CHECK-NEXT: cls x0, x8
+; CHECK-NEXT: ret
+ %sext = sext i32 %x to i64
+ %a = ashr i64 %sext, 31
+ %b = xor i64 %sext, %a
+ %c = call i64 @llvm.ctlz.i64(i64 %b, i1 false)
+ %d = sub i64 %c, 1
+ %e = or i64 %d, 16
+ ret i64 %e
+}
+
; There are at least 24 redundant sign bits so we don't need an ori after the clsw.
define i32 @cls_i32_knownbits_3(i8 signext %x) {
; CHECK-LABEL: cls_i32_knownbits_3:
@@ -96,3 +114,21 @@ define i32 @cls_i32_knownbits_4(i32 signext %x) {
%e = or i32 %d, 1
ret i32 %e
}
+
+; Negative test. Check that the number of sign bits is not
+; overestimated. If it is, the orr disappears.
+define i32 @cls_i32_knownbits_no_overestimate(i32 signext %x) {
+; CHECK-LABEL: cls_i32_knownbits_no_overestimate:
+; CHECK: // %bb.0:
+; CHECK-NEXT: asr w8, w0, #15
+; CHECK-NEXT: cls w8, w8
+; CHECK-NEXT: orr w0, w8, #0x1
+; CHECK-NEXT: ret
+ %ashr = ashr i32 %x, 15
+ %a = ashr i32 %ashr, 31
+ %b = xor i32 %ashr, %a
+ %c = call i32 @llvm.ctlz.i32(i32 %b, i1 false)
+ %d = sub i32 %c, 1
+ %e = or i32 %d, 16
+ ret i32 %e
+ }
diff --git a/llvm/test/CodeGen/RISCV/rv32p.ll b/llvm/test/CodeGen/RISCV/rv32p.ll
index 9611af1ee8c0e..19033c52bfbe3 100644
--- a/llvm/test/CodeGen/RISCV/rv32p.ll
+++ b/llvm/test/CodeGen/RISCV/rv32p.ll
@@ -298,6 +298,23 @@ define i32 @cls_i32_knownbits_4(i32 signext %x) {
ret i32 %e
}
+; Negative test. Check that the number of sign bits is not
+; overestimated. If it is, the orr disappears.
+define i32 @cls_i32_knownbits_no_overestimate(i32 signext %x) {
+; CHECK-LABEL: cls_i32_knownbits_no_overestimate:
+; CHECK: # %bb.0:
+; CHECK-NEXT: srai a0, a0, 15
+; CHECK-NEXT: cls a0, a0
+; CHECK-NEXT: ori a0, a0, 16
+; CHECK-NEXT: ret
+ %ashr = ashr i32 %x, 15
+ %a = ashr i32 %ashr, 31
+ %b = xor i32 %ashr, %a
+ %c = call i32 @llvm.ctlz.i32(i32 %b, i1 false)
+ %d = sub i32 %c, 1
+ %e = or i32 %d, 16
+ ret i32 %e
+ }
define i64 @slx_i64(i64 %x, i64 %y) {
; CHECK-LABEL: slx_i64:
diff --git a/llvm/test/CodeGen/RISCV/rv64p.ll b/llvm/test/CodeGen/RISCV/rv64p.ll
index 931771154f485..26fa9748ba12b 100644
--- a/llvm/test/CodeGen/RISCV/rv64p.ll
+++ b/llvm/test/CodeGen/RISCV/rv64p.ll
@@ -247,6 +247,25 @@ define i64 @cls_i64_2(i64 %x) {
ret i64 %e
}
+; Check that the range max in ctls cls knownbits
+; is not set to 32
+define i64 @cls_i64_not_32(i64 %x) {
+; CHECK-LABEL: cls_i64_3:
+; CHECK: # %bb.0:
+; CHECK-NEXT: srai a0, a0, 16
+; CHECK-NEXT: cls a0, a0
+; CHECK-NEXT: ori a0, a0, 16
+; CHECK-NEXT: ret
+ %val = ashr i64 %x, 16
+ %a = ashr i64 %val, 63
+ %b = xor i64 %val, %a
+ %c = shl i64 %b, 1
+ %d = or i64 %c, 1
+ %e = call i64 @llvm.ctlz.i64(i64 %d, i1 true)
+ %f = or i64 %e, 16
+ ret i64 %f
+}
+
define i128 @slx_i128(i128 %x, i128 %y) {
; CHECK-LABEL: slx_i128:
; CHECK: # %bb.0:
>From 458d7cd79718f6ad93df97e5f1dfe23c6f3762c3 Mon Sep 17 00:00:00 2001
From: stomfaig <stomfaig at gmail.com>
Date: Thu, 8 Jan 2026 09:31:47 +0000
Subject: [PATCH 6/6] fix tests
---
llvm/test/CodeGen/AArch64/cls.ll | 25 +++++++++++++------------
llvm/test/CodeGen/RISCV/rv64p.ll | 2 +-
2 files changed, 14 insertions(+), 13 deletions(-)
diff --git a/llvm/test/CodeGen/AArch64/cls.ll b/llvm/test/CodeGen/AArch64/cls.ll
index 745c287e6134c..9420819c07343 100644
--- a/llvm/test/CodeGen/AArch64/cls.ll
+++ b/llvm/test/CodeGen/AArch64/cls.ll
@@ -65,20 +65,21 @@ define i32 @cls_i32_knownbits_2(i16 signext %x) {
; Check that the range max in ctls cls knownbits
; is not set to 32
-define i64 @cls_i64_knownbits_2(i32 signext %x) {
-; CHECK-LABEL: cls_i64_knownbits_2:
+define i64 @cls_i64_not_32(i64 %x) {
+; CHECK-LABEL: cls_i64_not_32:
; CHECK: // %bb.0:
-; CHECK-NEXT: //{{.*}}
-; CHECK-NEXT: sxtw x8, w0
-; CHECK-NEXT: cls x0, x8
+; CHECK-NEXT: asr x8, x0, #16
+; CHECK-NEXT: cls x8, x8
+; CHECK-NEXT: orr x0, x8, #0x10
; CHECK-NEXT: ret
- %sext = sext i32 %x to i64
- %a = ashr i64 %sext, 31
- %b = xor i64 %sext, %a
- %c = call i64 @llvm.ctlz.i64(i64 %b, i1 false)
- %d = sub i64 %c, 1
- %e = or i64 %d, 16
- ret i64 %e
+ %val = ashr i64 %x, 16
+ %a = ashr i64 %val, 63
+ %b = xor i64 %val, %a
+ %c = shl i64 %b, 1
+ %d = or i64 %c, 1
+ %e = call i64 @llvm.ctlz.i64(i64 %d, i1 true)
+ %f = or i64 %e, 16
+ ret i64 %f
}
; There are at least 24 redundant sign bits so we don't need an ori after the clsw.
diff --git a/llvm/test/CodeGen/RISCV/rv64p.ll b/llvm/test/CodeGen/RISCV/rv64p.ll
index 26fa9748ba12b..fcf17294d69ca 100644
--- a/llvm/test/CodeGen/RISCV/rv64p.ll
+++ b/llvm/test/CodeGen/RISCV/rv64p.ll
@@ -250,7 +250,7 @@ define i64 @cls_i64_2(i64 %x) {
; Check that the range max in ctls cls knownbits
; is not set to 32
define i64 @cls_i64_not_32(i64 %x) {
-; CHECK-LABEL: cls_i64_3:
+; CHECK-LABEL: cls_i64_not_32:
; CHECK: # %bb.0:
; CHECK-NEXT: srai a0, a0, 16
; CHECK-NEXT: cls a0, a0
More information about the llvm-commits
mailing list