[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