[llvm] [GlobalISel] Add saturating G_TRUNC support for computeKnownBits (PR #223929)

via llvm-commits llvm-commits at lists.llvm.org
Wed Sep 16 00:16:46 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-globalisel

Author: Vikash Gupta (vg0204)

<details>
<summary>Changes</summary>

Port the saturating-truncate KnownBits handling to the GlobalISel. Look through G_TRUNC_SSAT_S, G_TRUNC_SSAT_U and G_TRUNC_USAT_U by recursing into the source operand and applying KnownBits::truncSSat, truncSSatU and truncUSat respectively.

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


2 Files Affected:

- (modified) llvm/lib/CodeGen/GlobalISel/GISelValueTracking.cpp (+18) 
- (added) llvm/test/CodeGen/AArch64/GlobalISel/knownbits-trunc-ssat-usat.mir (+167) 


``````````diff
diff --git a/llvm/lib/CodeGen/GlobalISel/GISelValueTracking.cpp b/llvm/lib/CodeGen/GlobalISel/GISelValueTracking.cpp
index e608b1ff6bb67..e369de29973d4 100644
--- a/llvm/lib/CodeGen/GlobalISel/GISelValueTracking.cpp
+++ b/llvm/lib/CodeGen/GlobalISel/GISelValueTracking.cpp
@@ -801,6 +801,24 @@ void GISelValueTracking::computeKnownBitsImpl(Register R, KnownBits &Known,
     Known = Known.zextOrTrunc(BitWidth);
     break;
   }
+  case TargetOpcode::G_TRUNC_SSAT_S: {
+    Register SrcReg = MI.getOperand(1).getReg();
+    computeKnownBitsImpl(SrcReg, Known, DemandedElts, Depth + 1);
+    Known = Known.truncSSat(BitWidth);
+    break;
+  }
+  case TargetOpcode::G_TRUNC_SSAT_U: {
+    Register SrcReg = MI.getOperand(1).getReg();
+    computeKnownBitsImpl(SrcReg, Known, DemandedElts, Depth + 1);
+    Known = Known.truncSSatU(BitWidth);
+    break;
+  }
+  case TargetOpcode::G_TRUNC_USAT_U: {
+    Register SrcReg = MI.getOperand(1).getReg();
+    computeKnownBitsImpl(SrcReg, Known, DemandedElts, Depth + 1);
+    Known = Known.truncUSat(BitWidth);
+    break;
+  }
   case TargetOpcode::G_ASSERT_ZEXT: {
     Register SrcReg = MI.getOperand(1).getReg();
     computeKnownBitsImpl(SrcReg, Known, DemandedElts, Depth + 1);
diff --git a/llvm/test/CodeGen/AArch64/GlobalISel/knownbits-trunc-ssat-usat.mir b/llvm/test/CodeGen/AArch64/GlobalISel/knownbits-trunc-ssat-usat.mir
new file mode 100644
index 0000000000000..8538f9f6d455f
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/GlobalISel/knownbits-trunc-ssat-usat.mir
@@ -0,0 +1,167 @@
+# NOTE: Assertions have been autogenerated by utils/update_givaluetracking_test_checks.py UTC_ARGS: --version 5
+# RUN: llc -mtriple=aarch64 -passes="print<gisel-value-tracking>" -filetype=null %s 2>&1 | FileCheck %s
+
+---
+# G_TRUNC_SSAT_S: signed 100000 exceeds the i16 max, so it clamps to +32767 = 0x7FFF.
+name:            SSatS_ScalarConstPosSat
+body:             |
+  bb.0:
+  ; CHECK-LABEL: name: @SSatS_ScalarConstPosSat
+  ; CHECK-NEXT: %0:_ KnownBits:00000000000000011000011010100000 SignBits:15 IsKnownNeverZero:1
+  ; CHECK-NEXT: %1:_ KnownBits:0111111111111111 SignBits:1 IsKnownNeverZero:1
+    %0:_(i32) = G_CONSTANT i32 100000
+    %1:_(i16) = G_TRUNC_SSAT_S %0(i32)
+...
+---
+# G_TRUNC_SSAT_S: signed -70000 is below the i16 min, so it clamps to -32768 = 0x8000.
+name:            SSatS_ScalarConstNegSat
+body:             |
+  bb.0:
+  ; CHECK-LABEL: name: @SSatS_ScalarConstNegSat
+  ; CHECK-NEXT: %0:_ KnownBits:11111111111111101110111010010000 SignBits:15 IsKnownNeverZero:1
+  ; CHECK-NEXT: %1:_ KnownBits:1000000000000000 SignBits:1 IsKnownNeverZero:1
+    %0:_(i32) = G_CONSTANT i32 -70000
+    %1:_(i16) = G_TRUNC_SSAT_S %0(i32)
+...
+---
+# G_AND masks the source to [0,65535] (non-negative). G_TRUNC_SSAT_S caps it at
+# 32767, so the result is always >= 0: the sign bit is a known 0, low bits unknown.
+name:            SSatS_ScalarMasked
+body:             |
+  bb.0:
+    liveins: $w0
+  ; CHECK-LABEL: name: @SSatS_ScalarMasked
+  ; CHECK-NEXT: %0:_ KnownBits:???????????????????????????????? SignBits:1 IsKnownNeverZero:0
+  ; CHECK-NEXT: %1:_ KnownBits:00000000000000001111111111111111 SignBits:16 IsKnownNeverZero:1
+  ; CHECK-NEXT: %2:_ KnownBits:0000000000000000???????????????? SignBits:16 IsKnownNeverZero:0
+  ; CHECK-NEXT: %3:_ KnownBits:0??????????????? SignBits:1 IsKnownNeverZero:0
+    %0:_(i32) = COPY $w0
+    %1:_(i32) = G_CONSTANT i32 65535
+    %2:_(i32) = G_AND %0, %1
+    %3:_(i16) = G_TRUNC_SSAT_S %2(i32)
+...
+---
+# G_TRUNC_SSAT_S of a fully-unknown COPY: the clamp still spans the whole i16
+# range, so nothing is known (baseline negative case).
+name:            SSatS_ScalarVar
+body:             |
+  bb.0:
+    liveins: $w0
+  ; CHECK-LABEL: name: @SSatS_ScalarVar
+  ; CHECK-NEXT: %0:_ KnownBits:???????????????????????????????? SignBits:1 IsKnownNeverZero:0
+  ; CHECK-NEXT: %1:_ KnownBits:???????????????? SignBits:1 IsKnownNeverZero:0
+    %0:_(i32) = COPY $w0
+    %1:_(i16) = G_TRUNC_SSAT_S %0(i32)
+...
+---
+# G_BUILD_VECTOR splats 100000 to every lane; G_TRUNC_SSAT_S clamps each lane to
+# 0x7FFF (per-lane saturation on a fixed vector).
+name:            SSatS_VectorCst
+body:             |
+  bb.0:
+  ; CHECK-LABEL: name: @SSatS_VectorCst
+  ; CHECK-NEXT: %0:_ KnownBits:00000000000000011000011010100000 SignBits:15 IsKnownNeverZero:1
+  ; CHECK-NEXT: %1:_ KnownBits:00000000000000011000011010100000 SignBits:15 IsKnownNeverZero:1
+  ; CHECK-NEXT: %2:_ KnownBits:0111111111111111 SignBits:1 IsKnownNeverZero:1
+    %0:_(i32) = G_CONSTANT i32 100000
+    %1:_(<4 x i32>) = G_BUILD_VECTOR %0, %0, %0, %0
+    %2:_(<4 x i16>) = G_TRUNC_SSAT_S %1(<4 x i32>)
+...
+---
+# G_SPLAT_VECTOR broadcasts -70000; G_TRUNC_SSAT_S clamps each lane to 0x8000
+# (per-lane saturation on a scalable vector).
+name:            SSatS_ScalableCst
+body:             |
+  bb.0:
+  ; CHECK-LABEL: name: @SSatS_ScalableCst
+  ; CHECK-NEXT: %0:_ KnownBits:11111111111111101110111010010000 SignBits:15 IsKnownNeverZero:1
+  ; CHECK-NEXT: %1:_ KnownBits:11111111111111101110111010010000 SignBits:15 IsKnownNeverZero:1
+  ; CHECK-NEXT: %2:_ KnownBits:1000000000000000 SignBits:1 IsKnownNeverZero:1
+    %0:_(i32) = G_CONSTANT i32 -70000
+    %1:_(<vscale x 4 x i32>) = G_SPLAT_VECTOR %0
+    %2:_(<vscale x 4 x i16>) = G_TRUNC_SSAT_S %1(<vscale x 4 x i32>)
+...
+---
+# G_TRUNC_SSAT_U reads the source as signed but targets an unsigned range, so the
+# negative -5 clamps to the unsigned floor 0 = 0x0000.
+name:            SSatU_ScalarConstNegSat
+body:             |
+  bb.0:
+  ; CHECK-LABEL: name: @SSatU_ScalarConstNegSat
+  ; CHECK-NEXT: %0:_ KnownBits:11111111111111111111111111111011 SignBits:29 IsKnownNeverZero:1
+  ; CHECK-NEXT: %1:_ KnownBits:0000000000000000 SignBits:16 IsKnownNeverZero:0
+    %0:_(i32) = G_CONSTANT i32 -5
+    %1:_(i16) = G_TRUNC_SSAT_U %0(i32)
+...
+---
+# G_AND masks the source to [0,32767], which already fits the unsigned i16 range,
+# so G_TRUNC_SSAT_U does not saturate and the known-zero top bit passes through.
+name:            SSatU_ScalarMasked
+body:             |
+  bb.0:
+    liveins: $w0
+  ; CHECK-LABEL: name: @SSatU_ScalarMasked
+  ; CHECK-NEXT: %0:_ KnownBits:???????????????????????????????? SignBits:1 IsKnownNeverZero:0
+  ; CHECK-NEXT: %1:_ KnownBits:00000000000000000111111111111111 SignBits:17 IsKnownNeverZero:1
+  ; CHECK-NEXT: %2:_ KnownBits:00000000000000000??????????????? SignBits:17 IsKnownNeverZero:0
+  ; CHECK-NEXT: %3:_ KnownBits:0??????????????? SignBits:1 IsKnownNeverZero:0
+    %0:_(i32) = COPY $w0
+    %1:_(i32) = G_CONSTANT i32 32767
+    %2:_(i32) = G_AND %0, %1
+    %3:_(i16) = G_TRUNC_SSAT_U %2(i32)
+...
+---
+# G_TRUNC_SSAT_U of a fully-unknown fixed-vector COPY: nothing known (fixed-vector
+# variable path).
+name:            SSatU_VectorVar
+body:             |
+  bb.0:
+    liveins: $q0
+  ; CHECK-LABEL: name: @SSatU_VectorVar
+  ; CHECK-NEXT: %0:_ KnownBits:???????????????????????????????? SignBits:1 IsKnownNeverZero:0
+  ; CHECK-NEXT: %1:_ KnownBits:???????????????? SignBits:1 IsKnownNeverZero:0
+    %0:_(<4 x i32>) = COPY $q0
+    %1:_(<4 x i16>) = G_TRUNC_SSAT_U %0(<4 x i32>)
+...
+---
+# G_TRUNC_USAT_U: unsigned 70000 exceeds the u16 max, so it clamps to the unsigned
+# ceiling 65535 = 0xFFFF.
+name:            USatU_ScalarConstBigSat
+body:             |
+  bb.0:
+  ; CHECK-LABEL: name: @USatU_ScalarConstBigSat
+  ; CHECK-NEXT: %0:_ KnownBits:00000000000000010001000101110000 SignBits:15 IsKnownNeverZero:1
+  ; CHECK-NEXT: %1:_ KnownBits:1111111111111111 SignBits:16 IsKnownNeverZero:1
+    %0:_(i32) = G_CONSTANT i32 70000
+    %1:_(i16) = G_TRUNC_USAT_U %0(i32)
+...
+---
+# G_AND masks the source to [0,131071], a range that straddles the u16 ceiling, so
+# G_TRUNC_USAT_U may or may not saturate: the result can be any 16-bit value.
+name:            USatU_ScalarMasked
+body:             |
+  bb.0:
+    liveins: $w0
+  ; CHECK-LABEL: name: @USatU_ScalarMasked
+  ; CHECK-NEXT: %0:_ KnownBits:???????????????????????????????? SignBits:1 IsKnownNeverZero:0
+  ; CHECK-NEXT: %1:_ KnownBits:00000000000000011111111111111111 SignBits:15 IsKnownNeverZero:1
+  ; CHECK-NEXT: %2:_ KnownBits:000000000000000????????????????? SignBits:15 IsKnownNeverZero:0
+  ; CHECK-NEXT: %3:_ KnownBits:???????????????? SignBits:1 IsKnownNeverZero:0
+    %0:_(i32) = COPY $w0
+    %1:_(i32) = G_CONSTANT i32 131071
+    %2:_(i32) = G_AND %0, %1
+    %3:_(i16) = G_TRUNC_USAT_U %2(i32)
+...
+---
+# G_TRUNC_USAT_U of a fully-unknown scalable-vector COPY: nothing known (scalable
+# variable path).
+name:            USatU_ScalableVar
+body:             |
+  bb.0:
+    liveins: $z0
+  ; CHECK-LABEL: name: @USatU_ScalableVar
+  ; CHECK-NEXT: %0:_ KnownBits:???????????????????????????????? SignBits:1 IsKnownNeverZero:0
+  ; CHECK-NEXT: %1:_ KnownBits:???????????????? SignBits:1 IsKnownNeverZero:0
+    %0:_(<vscale x 4 x i32>) = COPY $z0
+    %1:_(<vscale x 4 x i16>) = G_TRUNC_USAT_U %0(<vscale x 4 x i32>)
+...

``````````

</details>


https://github.com/llvm/llvm-project/pull/223929


More information about the llvm-commits mailing list