[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