Skip to content

[GISel] computeKnownBits - add CTLS handling - #178063

Merged
arsenm merged 9 commits into
llvm:mainfrom
stomfaig:issue_174370_2
Feb 9, 2026
Merged

[GISel] computeKnownBits - add CTLS handling#178063
arsenm merged 9 commits into
llvm:mainfrom
stomfaig:issue_174370_2

Conversation

@stomfaig

Copy link
Copy Markdown
Contributor

Closes #174370

@llvmbot

llvmbot commented Jan 26, 2026

Copy link
Copy Markdown
Member

@llvm/pr-subscribers-llvm-globalisel

@llvm/pr-subscribers-backend-risc-v

Author: Gergo Stomfai (stomfaig)

Changes

Closes llvm/llvm-project#174370


Full diff: https://github.com/llvm/llvm-project/pull/178063.diff

4 Files Affected:

  • (modified) llvm/lib/CodeGen/GlobalISel/GISelValueTracking.cpp (+9)
  • (modified) llvm/test/CodeGen/AArch64/arm64-clrsb.ll (+134-3)
  • (modified) llvm/test/CodeGen/RISCV/GlobalISel/rv32p.ll (+85)
  • (modified) llvm/test/CodeGen/RISCV/GlobalISel/rv64p.ll (+23)
diff --git a/llvm/lib/CodeGen/GlobalISel/GISelValueTracking.cpp b/llvm/lib/CodeGen/GlobalISel/GISelValueTracking.cpp
index 34692f0b4c4ee..014e0efcaed2d 100644
--- a/llvm/lib/CodeGen/GlobalISel/GISelValueTracking.cpp
+++ b/llvm/lib/CodeGen/GlobalISel/GISelValueTracking.cpp
@@ -681,6 +681,15 @@ void GISelValueTracking::computeKnownBitsImpl(Register R, KnownBits &Known,
     Known.Zero.setBitsFrom(LowBits);
     break;
   }
+  case TargetOpcode::G_CTLS: {
+    unsigned MinRedundantSignBits =
+        computeNumSignBits(MI.getOperand(0).getReg(), Depth + 1) - 1;
+
+    ConstantRange Range(APInt(BitWidth, MinRedundantSignBits),
+                        APInt(BitWidth, BitWidth));
+    Known = Range.toKnownBits();
+    break;
+  }
   case TargetOpcode::G_EXTRACT_VECTOR_ELT: {
     GExtractVectorElement &Extract = cast<GExtractVectorElement>(MI);
     Register InVec = Extract.getVectorReg();
diff --git a/llvm/test/CodeGen/AArch64/arm64-clrsb.ll b/llvm/test/CodeGen/AArch64/arm64-clrsb.ll
index 4597c6178e2ba..2f4b0ac3653a0 100644
--- a/llvm/test/CodeGen/AArch64/arm64-clrsb.ll
+++ b/llvm/test/CodeGen/AArch64/arm64-clrsb.ll
@@ -91,6 +91,137 @@ entry:
   ret i64 %0
 }
 
-;; NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line:
-; CHECK-GI: {{.*}}
-; CHECK-SD: {{.*}}
+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 cls.
+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
+}
+
+; Check that the range max in ctls cls knownbits
+; is not set to 32
+define i64 @cls_i64_not_32(i64 %x) {
+; CHECK-SD-LABEL: cls_i64_not_32:
+; CHECK-SD:       ; %bb.0:
+; CHECK-SD-NEXT:    asr x8, x0, #16
+; CHECK-SD-NEXT:    cls x8, x8
+; CHECK-SD-NEXT:    orr x0, x8, #0x10
+; CHECK-SD-NEXT:    ret
+;
+; CHECK-GI-LABEL: cls_i64_not_32:
+; CHECK-GI:       ; %bb.0:
+; CHECK-GI-NEXT:    asr x8, x0, #63
+; CHECK-GI-NEXT:    eor x8, x8, x0, asr #16
+; CHECK-GI-NEXT:    lsl x8, x8, #1
+; CHECK-GI-NEXT:    orr x8, x8, #0x1
+; CHECK-GI-NEXT:    clz x8, x8
+; CHECK-GI-NEXT:    orr x0, x8, #0x10
+; CHECK-GI-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
+}
+
+; 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:
+; 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
+ }
+
+; 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-SD-LABEL: cls_i32_knownbits_no_overestimate:
+; CHECK-SD:       ; %bb.0:
+; CHECK-SD-NEXT:    asr w8, w0, #15
+; CHECK-SD-NEXT:    cls w8, w8
+; CHECK-SD-NEXT:    orr w0, w8, #0x10
+; CHECK-SD-NEXT:    ret
+;
+; CHECK-GI-LABEL: cls_i32_knownbits_no_overestimate:
+; CHECK-GI:       ; %bb.0:
+; CHECK-GI-NEXT:    asr w8, w0, #31
+; CHECK-GI-NEXT:    eor w8, w8, w0, asr #15
+; CHECK-GI-NEXT:    clz w8, w8
+; CHECK-GI-NEXT:    sub w8, w8, #1
+; CHECK-GI-NEXT:    orr w0, w8, #0x10
+; CHECK-GI-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/GlobalISel/rv32p.ll b/llvm/test/CodeGen/RISCV/GlobalISel/rv32p.ll
index 3f403fd8cb9e5..5708077fcefb1 100644
--- a/llvm/test/CodeGen/RISCV/GlobalISel/rv32p.ll
+++ b/llvm/test/CodeGen/RISCV/GlobalISel/rv32p.ll
@@ -139,3 +139,88 @@ define i64 @cls_i64_2(i64 %x) {
   %e = call i64 @llvm.ctlz.i64(i64 %d, i1 true)
   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
+}
+
+; 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:
+; 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
+ }
+
+; Negative test. Check that the number of sign bits is not
+; overestimated. If it is, the ori disappears.
+define i32 @cls_i32_knownbits_no_overestimate(i32 signext %x) {
+; CHECK-LABEL: cls_i32_knownbits_no_overestimate:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    srai a1, a0, 15
+; CHECK-NEXT:    srai a0, a0, 31
+; CHECK-NEXT:    xor a0, a1, a0
+; CHECK-NEXT:    clz a0, a0
+; CHECK-NEXT:    addi a0, a0, -1
+; 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
+ }
diff --git a/llvm/test/CodeGen/RISCV/GlobalISel/rv64p.ll b/llvm/test/CodeGen/RISCV/GlobalISel/rv64p.ll
index 5faf1079a7804..fa32f3ed39c92 100644
--- a/llvm/test/CodeGen/RISCV/GlobalISel/rv64p.ll
+++ b/llvm/test/CodeGen/RISCV/GlobalISel/rv64p.ll
@@ -112,3 +112,26 @@ define i64 @cls_i64_2(i64 %x) {
   %e = call i64 @llvm.ctlz.i64(i64 %d, i1 true)
   ret i64 %e
 }
+
+; Check that the range max in ctls cls knownbits
+; is not set to 32. If it is, then ori disappears.
+define i64 @cls_i64_not_32(i64 %x) {
+; CHECK-LABEL: cls_i64_not_32:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    srai a1, a0, 16
+; CHECK-NEXT:    srai a0, a0, 63
+; CHECK-NEXT:    xor a0, a1, a0
+; CHECK-NEXT:    slli a0, a0, 1
+; CHECK-NEXT:    ori a0, a0, 1
+; CHECK-NEXT:    clz 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
+}

@llvmbot

llvmbot commented Jan 26, 2026

Copy link
Copy Markdown
Member

@llvm/pr-subscribers-backend-aarch64

Author: Gergo Stomfai (stomfaig)

Changes

Closes llvm/llvm-project#174370


Full diff: https://github.com/llvm/llvm-project/pull/178063.diff

4 Files Affected:

  • (modified) llvm/lib/CodeGen/GlobalISel/GISelValueTracking.cpp (+9)
  • (modified) llvm/test/CodeGen/AArch64/arm64-clrsb.ll (+134-3)
  • (modified) llvm/test/CodeGen/RISCV/GlobalISel/rv32p.ll (+85)
  • (modified) llvm/test/CodeGen/RISCV/GlobalISel/rv64p.ll (+23)
diff --git a/llvm/lib/CodeGen/GlobalISel/GISelValueTracking.cpp b/llvm/lib/CodeGen/GlobalISel/GISelValueTracking.cpp
index 34692f0b4c4ee..014e0efcaed2d 100644
--- a/llvm/lib/CodeGen/GlobalISel/GISelValueTracking.cpp
+++ b/llvm/lib/CodeGen/GlobalISel/GISelValueTracking.cpp
@@ -681,6 +681,15 @@ void GISelValueTracking::computeKnownBitsImpl(Register R, KnownBits &Known,
     Known.Zero.setBitsFrom(LowBits);
     break;
   }
+  case TargetOpcode::G_CTLS: {
+    unsigned MinRedundantSignBits =
+        computeNumSignBits(MI.getOperand(0).getReg(), Depth + 1) - 1;
+
+    ConstantRange Range(APInt(BitWidth, MinRedundantSignBits),
+                        APInt(BitWidth, BitWidth));
+    Known = Range.toKnownBits();
+    break;
+  }
   case TargetOpcode::G_EXTRACT_VECTOR_ELT: {
     GExtractVectorElement &Extract = cast<GExtractVectorElement>(MI);
     Register InVec = Extract.getVectorReg();
diff --git a/llvm/test/CodeGen/AArch64/arm64-clrsb.ll b/llvm/test/CodeGen/AArch64/arm64-clrsb.ll
index 4597c6178e2ba..2f4b0ac3653a0 100644
--- a/llvm/test/CodeGen/AArch64/arm64-clrsb.ll
+++ b/llvm/test/CodeGen/AArch64/arm64-clrsb.ll
@@ -91,6 +91,137 @@ entry:
   ret i64 %0
 }
 
-;; NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line:
-; CHECK-GI: {{.*}}
-; CHECK-SD: {{.*}}
+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 cls.
+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
+}
+
+; Check that the range max in ctls cls knownbits
+; is not set to 32
+define i64 @cls_i64_not_32(i64 %x) {
+; CHECK-SD-LABEL: cls_i64_not_32:
+; CHECK-SD:       ; %bb.0:
+; CHECK-SD-NEXT:    asr x8, x0, #16
+; CHECK-SD-NEXT:    cls x8, x8
+; CHECK-SD-NEXT:    orr x0, x8, #0x10
+; CHECK-SD-NEXT:    ret
+;
+; CHECK-GI-LABEL: cls_i64_not_32:
+; CHECK-GI:       ; %bb.0:
+; CHECK-GI-NEXT:    asr x8, x0, #63
+; CHECK-GI-NEXT:    eor x8, x8, x0, asr #16
+; CHECK-GI-NEXT:    lsl x8, x8, #1
+; CHECK-GI-NEXT:    orr x8, x8, #0x1
+; CHECK-GI-NEXT:    clz x8, x8
+; CHECK-GI-NEXT:    orr x0, x8, #0x10
+; CHECK-GI-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
+}
+
+; 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:
+; 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
+ }
+
+; 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-SD-LABEL: cls_i32_knownbits_no_overestimate:
+; CHECK-SD:       ; %bb.0:
+; CHECK-SD-NEXT:    asr w8, w0, #15
+; CHECK-SD-NEXT:    cls w8, w8
+; CHECK-SD-NEXT:    orr w0, w8, #0x10
+; CHECK-SD-NEXT:    ret
+;
+; CHECK-GI-LABEL: cls_i32_knownbits_no_overestimate:
+; CHECK-GI:       ; %bb.0:
+; CHECK-GI-NEXT:    asr w8, w0, #31
+; CHECK-GI-NEXT:    eor w8, w8, w0, asr #15
+; CHECK-GI-NEXT:    clz w8, w8
+; CHECK-GI-NEXT:    sub w8, w8, #1
+; CHECK-GI-NEXT:    orr w0, w8, #0x10
+; CHECK-GI-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/GlobalISel/rv32p.ll b/llvm/test/CodeGen/RISCV/GlobalISel/rv32p.ll
index 3f403fd8cb9e5..5708077fcefb1 100644
--- a/llvm/test/CodeGen/RISCV/GlobalISel/rv32p.ll
+++ b/llvm/test/CodeGen/RISCV/GlobalISel/rv32p.ll
@@ -139,3 +139,88 @@ define i64 @cls_i64_2(i64 %x) {
   %e = call i64 @llvm.ctlz.i64(i64 %d, i1 true)
   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
+}
+
+; 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:
+; 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
+ }
+
+; Negative test. Check that the number of sign bits is not
+; overestimated. If it is, the ori disappears.
+define i32 @cls_i32_knownbits_no_overestimate(i32 signext %x) {
+; CHECK-LABEL: cls_i32_knownbits_no_overestimate:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    srai a1, a0, 15
+; CHECK-NEXT:    srai a0, a0, 31
+; CHECK-NEXT:    xor a0, a1, a0
+; CHECK-NEXT:    clz a0, a0
+; CHECK-NEXT:    addi a0, a0, -1
+; 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
+ }
diff --git a/llvm/test/CodeGen/RISCV/GlobalISel/rv64p.ll b/llvm/test/CodeGen/RISCV/GlobalISel/rv64p.ll
index 5faf1079a7804..fa32f3ed39c92 100644
--- a/llvm/test/CodeGen/RISCV/GlobalISel/rv64p.ll
+++ b/llvm/test/CodeGen/RISCV/GlobalISel/rv64p.ll
@@ -112,3 +112,26 @@ define i64 @cls_i64_2(i64 %x) {
   %e = call i64 @llvm.ctlz.i64(i64 %d, i1 true)
   ret i64 %e
 }
+
+; Check that the range max in ctls cls knownbits
+; is not set to 32. If it is, then ori disappears.
+define i64 @cls_i64_not_32(i64 %x) {
+; CHECK-LABEL: cls_i64_not_32:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    srai a1, a0, 16
+; CHECK-NEXT:    srai a0, a0, 63
+; CHECK-NEXT:    xor a0, a1, a0
+; CHECK-NEXT:    slli a0, a0, 1
+; CHECK-NEXT:    ori a0, a0, 1
+; CHECK-NEXT:    clz 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
+}

@davemgreen

Copy link
Copy Markdown
Contributor

Can you add some direct tests, as in for example llvm/test/CodeGen/AArch64/GlobalISel/knownbits-sub.mir?
And a gisel run line to the existing tests in llvm/test/CodeGen/AArch64/cls.ll.

@arsenm arsenm left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a test with vectors

@stomfaig

Copy link
Copy Markdown
Contributor Author

@arsenm, Do you mean on mir level, or in ll tests too?

KnownBits SrcOpKnown;
auto Reg = MI.getOperand(1).getReg();

computeKnownBitsImpl(Reg, SrcOpKnown, DemandedElts, Depth + 1);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this more accurate than using computeNumSignBits like you had before? Doesn't computeNumSignBits use computeKnownBits as a fallback?

@stomfaig stomfaig Jan 28, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main point is that having SrcOpKnown we can decide if the value is constant. Because if it is, then we know it's ctls exactly.

But if I only use computeNumSignBits, then I would have to excavate the constantness information again, so likely would need to have a computeKnownBitsImpl anyway.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If all the input bits are known, then I hope the input would be constant folded elsewhere. If that's the case, then we should be able to add a combine that constant folds ctls without needing computeKnownBits to do it.

Not using computeNumSignBits prevents us from being able to determine sign bits if the input is a G_SEXT or G_SEXT_INREG. In those cases we know the number of sign bits but not the exact value.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. I was wondering why the G_SEXT and G_SEXT_INREG was not working properly. Thanks for pointing this out.

@github-actions

github-actions Bot commented Jan 28, 2026

Copy link
Copy Markdown

🪟 Windows x64 Test Results

  • 130292 tests passed
  • 2889 tests skipped

✅ The build succeeded and all tests passed.

@github-actions

github-actions Bot commented Jan 28, 2026

Copy link
Copy Markdown

🐧 Linux x64 Test Results

  • 189395 tests passed
  • 5026 tests skipped

✅ The build succeeded and all tests passed.

@topperc

topperc commented Jan 28, 2026

Copy link
Copy Markdown
Contributor

Looks like there's some constant folding for G_CTLZ/G_CTTZ in CSEMIRBuilder::buildInstr. G_CTLS should probably be added there in another PR..

Comment thread llvm/test/CodeGen/AArch64/cls.ll
;; NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line:
; CHECK-GI: {{.*}}
; CHECK-SD: {{.*}}
define i8 @cls_i8(i8 %x) {

@topperc topperc Jan 29, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a gisel RUN line to llvm/test/CodeGen/AArch64/cls.ll instead of copy and pasting the tests here?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh. You already did that. So are these tests duplicated?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, they are duplicates apart from the mtriple. Will remove.

@topperc topperc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

auto Reg = MI.getOperand(1).getReg();
unsigned MinRedundantSignBits = computeNumSignBits(Reg, Depth + 1) - 1;

unsigned MaxUpperRedundantSignBits = MRI.getType(Reg).getScalarSizeInBits();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be different to BitWidth? Does GISel allow mismatching src/dst types?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I am not sure if you mean it should allow, or if they should be the same?

@arsenm arsenm Feb 5, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The source and dest types should not be assumed to match. G_CTLS is defined with 2 type indexes

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case, I believe that this mr is ready?

Comment thread llvm/lib/CodeGen/GlobalISel/GISelValueTracking.cpp Outdated
@arsenm
arsenm enabled auto-merge (squash) February 9, 2026 09:04
@arsenm
arsenm merged commit 2298b86 into llvm:main Feb 9, 2026
9 of 10 checks passed
rishabhmadan19 pushed a commit to rishabhmadan19/llvm-project that referenced this pull request Feb 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[DAG][GISel] computeKnownBits - add CTLS handling

6 participants