[llvm] [AArch64][GlobalISel] Combine trunc_nsw(smin) to truncsat (PR #218605)

via llvm-commits llvm-commits at lists.llvm.org
Tue Aug 25 00:12:01 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-backend-aarch64

Author: David Green (davemgreen)

<details>
<summary>Changes</summary>

The midend of LLVM, in CVP, will often transform the canonical pattern for a saturate trunc(smin(smax)) into trunc nsw (smin), as the range analysis proves that the smax is outside of range. This adds a fold back, converting the trunc with nsw + smin into G_TRUNC_SSAT_S.
https://alive2.llvm.org/ce/z/xfPEXE

Support for widening is added for truncsat nodes too, to prevent fallbacks. They can be widened in the same way as a standard trunc using buildPadVectorWithUndefElements.

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


4 Files Affected:

- (modified) llvm/lib/CodeGen/GlobalISel/CombinerHelper.cpp (+18-8) 
- (modified) llvm/lib/CodeGen/GlobalISel/LegalizerHelper.cpp (+4-1) 
- (modified) llvm/test/CodeGen/AArch64/qmovn.ll (+6-15) 
- (modified) llvm/test/CodeGen/AArch64/saturating-vec-smull.ll (+37-75) 


``````````diff
diff --git a/llvm/lib/CodeGen/GlobalISel/CombinerHelper.cpp b/llvm/lib/CodeGen/GlobalISel/CombinerHelper.cpp
index 5804be63fad48..167cd480d398b 100644
--- a/llvm/lib/CodeGen/GlobalISel/CombinerHelper.cpp
+++ b/llvm/lib/CodeGen/GlobalISel/CombinerHelper.cpp
@@ -6290,14 +6290,24 @@ bool CombinerHelper::matchTruncSSatS(MachineInstr &MI,
 
   APInt SignedMax = APInt::getSignedMaxValue(NumDstBits).sext(NumSrcBits);
   APInt SignedMin = APInt::getSignedMinValue(NumDstBits).sext(NumSrcBits);
-  return mi_match(Src, MRI,
-                  m_GSMin(m_GSMax(m_Reg(MatchInfo),
-                                  m_SpecificICstOrSplat(SignedMin)),
-                          m_SpecificICstOrSplat(SignedMax))) ||
-         mi_match(Src, MRI,
-                  m_GSMax(m_GSMin(m_Reg(MatchInfo),
-                                  m_SpecificICstOrSplat(SignedMax)),
-                          m_SpecificICstOrSplat(SignedMin)));
+  if (mi_match(
+          Src, MRI,
+          m_GSMin(m_GSMax(m_Reg(MatchInfo), m_SpecificICstOrSplat(SignedMin)),
+                  m_SpecificICstOrSplat(SignedMax))) ||
+      mi_match(
+          Src, MRI,
+          m_GSMax(m_GSMin(m_Reg(MatchInfo), m_SpecificICstOrSplat(SignedMax)),
+                  m_SpecificICstOrSplat(SignedMin))))
+    return true;
+
+  // CVP in the midend will often transform trunc(smin(smax(..)) into
+  // trunc nsw(smin(..)) as the smax against INT_MIN never saturates.
+  if (MI.getFlag(MachineInstr::MIFlag::NoSWrap) &&
+      mi_match(Src, MRI,
+               m_GSMin(m_Reg(MatchInfo), m_SpecificICstOrSplat(SignedMax))))
+    return true;
+
+  return false;
 }
 
 void CombinerHelper::applyTruncSSatS(MachineInstr &MI,
diff --git a/llvm/lib/CodeGen/GlobalISel/LegalizerHelper.cpp b/llvm/lib/CodeGen/GlobalISel/LegalizerHelper.cpp
index 9e8895898532e..734dbab45b9a2 100644
--- a/llvm/lib/CodeGen/GlobalISel/LegalizerHelper.cpp
+++ b/llvm/lib/CodeGen/GlobalISel/LegalizerHelper.cpp
@@ -7074,7 +7074,10 @@ LegalizerHelper::moreElementsVector(MachineInstr &MI, unsigned TypeIdx,
   case TargetOpcode::G_FPTOSI_SAT:
   case TargetOpcode::G_FPTOUI_SAT:
   case TargetOpcode::G_SITOFP:
-  case TargetOpcode::G_UITOFP: {
+  case TargetOpcode::G_UITOFP:
+  case TargetOpcode::G_TRUNC_SSAT_S:
+  case TargetOpcode::G_TRUNC_SSAT_U:
+  case TargetOpcode::G_TRUNC_USAT_U: {
     Observer.changingInstr(MI);
     LLT SrcExtTy;
     LLT DstExtTy;
diff --git a/llvm/test/CodeGen/AArch64/qmovn.ll b/llvm/test/CodeGen/AArch64/qmovn.ll
index 30bff916f1f02..c1b8c1c221614 100644
--- a/llvm/test/CodeGen/AArch64/qmovn.ll
+++ b/llvm/test/CodeGen/AArch64/qmovn.ll
@@ -640,10 +640,8 @@ define <16 x i8> @signed_minnsw_v16i16_to_v16i8(<16 x i16> %y) {
 ;
 ; CHECK-GI-LABEL: signed_minnsw_v16i16_to_v16i8:
 ; CHECK-GI:       // %bb.0: // %entry
-; CHECK-GI-NEXT:    movi v2.8h, #127
-; CHECK-GI-NEXT:    smin v0.8h, v0.8h, v2.8h
-; CHECK-GI-NEXT:    smin v1.8h, v1.8h, v2.8h
-; CHECK-GI-NEXT:    uzp1 v0.16b, v0.16b, v1.16b
+; CHECK-GI-NEXT:    sqxtn v0.8b, v0.8h
+; CHECK-GI-NEXT:    sqxtn2 v0.16b, v1.8h
 ; CHECK-GI-NEXT:    ret
 entry:
   %min = call <16 x i16> @llvm.smin.v16i16(<16 x i16> %y, <16 x i16> <i16 127, i16 127, i16 127, i16 127, i16 127, i16 127, i16 127, i16 127, i16 127, i16 127, i16 127, i16 127, i16 127, i16 127, i16 127, i16 127>)
@@ -662,10 +660,8 @@ define <8 x i16> @signed_minnsw_v8i32_to_v8i16(<8 x i32> %y) {
 ;
 ; CHECK-GI-LABEL: signed_minnsw_v8i32_to_v8i16:
 ; CHECK-GI:       // %bb.0: // %entry
-; CHECK-GI-NEXT:    movi v2.4s, #127, msl #8
-; CHECK-GI-NEXT:    smin v0.4s, v0.4s, v2.4s
-; CHECK-GI-NEXT:    smin v1.4s, v1.4s, v2.4s
-; CHECK-GI-NEXT:    uzp1 v0.8h, v0.8h, v1.8h
+; CHECK-GI-NEXT:    sqxtn v0.4h, v0.4s
+; CHECK-GI-NEXT:    sqxtn2 v0.8h, v1.4s
 ; CHECK-GI-NEXT:    ret
 entry:
   %min = call <8 x i32> @llvm.smin.v8i32(<8 x i32> %y, <8 x i32> <i32 32767, i32 32767, i32 32767, i32 32767, i32 32767, i32 32767, i32 32767, i32 32767>)
@@ -687,13 +683,8 @@ define <4 x i32> @signed_minnsw_v4i64_to_v4i32(<4 x i64> %y) {
 ;
 ; CHECK-GI-LABEL: signed_minnsw_v4i64_to_v4i32:
 ; CHECK-GI:       // %bb.0: // %entry
-; CHECK-GI-NEXT:    adrp x8, .LCPI47_0
-; CHECK-GI-NEXT:    ldr q2, [x8, :lo12:.LCPI47_0]
-; CHECK-GI-NEXT:    cmgt v3.2d, v2.2d, v0.2d
-; CHECK-GI-NEXT:    cmgt v4.2d, v2.2d, v1.2d
-; CHECK-GI-NEXT:    bif v0.16b, v2.16b, v3.16b
-; CHECK-GI-NEXT:    bif v1.16b, v2.16b, v4.16b
-; CHECK-GI-NEXT:    uzp1 v0.4s, v0.4s, v1.4s
+; CHECK-GI-NEXT:    sqxtn v0.2s, v0.2d
+; CHECK-GI-NEXT:    sqxtn2 v0.4s, v1.2d
 ; CHECK-GI-NEXT:    ret
 entry:
   %min = call <4 x i64> @llvm.smin.v4i64(<4 x i64> %y, <4 x i64> <i64 2147483647, i64 2147483647, i64 2147483647, i64 2147483647>)
diff --git a/llvm/test/CodeGen/AArch64/saturating-vec-smull.ll b/llvm/test/CodeGen/AArch64/saturating-vec-smull.ll
index fe9b060be6ae2..a16eb65b32a71 100644
--- a/llvm/test/CodeGen/AArch64/saturating-vec-smull.ll
+++ b/llvm/test/CodeGen/AArch64/saturating-vec-smull.ll
@@ -43,10 +43,7 @@ define <4 x i16> @saturating_4xi16(<4 x i16> %a, <4 x i16> %b) {
 ; CHECK-GI-LABEL: saturating_4xi16:
 ; CHECK-GI:       // %bb.0:
 ; CHECK-GI-NEXT:    smull v0.4s, v1.4h, v0.4h
-; CHECK-GI-NEXT:    movi v1.4s, #127, msl #8
-; CHECK-GI-NEXT:    sshr v0.4s, v0.4s, #15
-; CHECK-GI-NEXT:    smin v0.4s, v0.4s, v1.4s
-; CHECK-GI-NEXT:    xtn v0.4h, v0.4s
+; CHECK-GI-NEXT:    sqshrn v0.4h, v0.4s, #15
 ; CHECK-GI-NEXT:    ret
   %as = sext <4 x i16> %a to <4 x i32>
   %bs = sext <4 x i16> %b to <4 x i32>
@@ -65,14 +62,10 @@ define <8 x i16> @saturating_8xi16(<8 x i16> %a, <8 x i16> %b) {
 ;
 ; CHECK-GI-LABEL: saturating_8xi16:
 ; CHECK-GI:       // %bb.0:
-; CHECK-GI-NEXT:    smull v3.4s, v1.4h, v0.4h
-; CHECK-GI-NEXT:    smull2 v0.4s, v1.8h, v0.8h
-; CHECK-GI-NEXT:    movi v2.4s, #127, msl #8
-; CHECK-GI-NEXT:    sshr v1.4s, v3.4s, #15
-; CHECK-GI-NEXT:    sshr v0.4s, v0.4s, #15
-; CHECK-GI-NEXT:    smin v1.4s, v1.4s, v2.4s
-; CHECK-GI-NEXT:    smin v0.4s, v0.4s, v2.4s
-; CHECK-GI-NEXT:    uzp1 v0.8h, v1.8h, v0.8h
+; CHECK-GI-NEXT:    smull v2.4s, v1.4h, v0.4h
+; CHECK-GI-NEXT:    smull2 v1.4s, v1.8h, v0.8h
+; CHECK-GI-NEXT:    sqshrn v0.4h, v2.4s, #15
+; CHECK-GI-NEXT:    sqshrn2 v0.8h, v1.4s, #15
 ; CHECK-GI-NEXT:    ret
   %as = sext <8 x i16> %a to <8 x i32>
   %bs = sext <8 x i16> %b to <8 x i32>
@@ -92,12 +85,7 @@ define <2 x i32> @saturating_2xi32(<2 x i32> %a, <2 x i32> %b) {
 ; CHECK-GI-LABEL: saturating_2xi32:
 ; CHECK-GI:       // %bb.0:
 ; CHECK-GI-NEXT:    smull v0.2d, v1.2s, v0.2s
-; CHECK-GI-NEXT:    adrp x8, .LCPI3_0
-; CHECK-GI-NEXT:    ldr q1, [x8, :lo12:.LCPI3_0]
-; CHECK-GI-NEXT:    sshr v0.2d, v0.2d, #31
-; CHECK-GI-NEXT:    cmgt v2.2d, v1.2d, v0.2d
-; CHECK-GI-NEXT:    bif v0.16b, v1.16b, v2.16b
-; CHECK-GI-NEXT:    xtn v0.2s, v0.2d
+; CHECK-GI-NEXT:    sqshrn v0.2s, v0.2d, #31
 ; CHECK-GI-NEXT:    ret
   %as = sext <2 x i32> %a to <2 x i64>
   %bs = sext <2 x i32> %b to <2 x i64>
@@ -117,16 +105,9 @@ define <4 x i32> @saturating_4xi32(<4 x i32> %a, <4 x i32> %b) {
 ; CHECK-GI-LABEL: saturating_4xi32:
 ; CHECK-GI:       // %bb.0:
 ; CHECK-GI-NEXT:    smull v2.2d, v1.2s, v0.2s
-; CHECK-GI-NEXT:    smull2 v0.2d, v1.4s, v0.4s
-; CHECK-GI-NEXT:    adrp x8, .LCPI4_0
-; CHECK-GI-NEXT:    sshr v1.2d, v2.2d, #31
-; CHECK-GI-NEXT:    sshr v0.2d, v0.2d, #31
-; CHECK-GI-NEXT:    ldr q2, [x8, :lo12:.LCPI4_0]
-; CHECK-GI-NEXT:    cmgt v3.2d, v2.2d, v1.2d
-; CHECK-GI-NEXT:    cmgt v4.2d, v2.2d, v0.2d
-; CHECK-GI-NEXT:    bif v1.16b, v2.16b, v3.16b
-; CHECK-GI-NEXT:    bif v0.16b, v2.16b, v4.16b
-; CHECK-GI-NEXT:    uzp1 v0.4s, v1.4s, v0.4s
+; CHECK-GI-NEXT:    smull2 v1.2d, v1.4s, v0.4s
+; CHECK-GI-NEXT:    sqshrn v0.2s, v2.2d, #31
+; CHECK-GI-NEXT:    sqshrn2 v0.4s, v1.2d, #31
 ; CHECK-GI-NEXT:    ret
   %as = sext <4 x i32> %a to <4 x i64>
   %bs = sext <4 x i32> %b to <4 x i64>
@@ -147,25 +128,13 @@ define <8 x i32> @saturating_8xi32(<8 x i32> %a, <8 x i32> %b) {
 ; CHECK-GI-LABEL: saturating_8xi32:
 ; CHECK-GI:       // %bb.0:
 ; CHECK-GI-NEXT:    smull v4.2d, v2.2s, v0.2s
-; CHECK-GI-NEXT:    smull2 v0.2d, v2.4s, v0.4s
-; CHECK-GI-NEXT:    adrp x8, .LCPI5_0
-; CHECK-GI-NEXT:    smull v2.2d, v3.2s, v1.2s
-; CHECK-GI-NEXT:    smull2 v1.2d, v3.4s, v1.4s
-; CHECK-GI-NEXT:    ldr q3, [x8, :lo12:.LCPI5_0]
-; CHECK-GI-NEXT:    sshr v4.2d, v4.2d, #31
-; CHECK-GI-NEXT:    sshr v0.2d, v0.2d, #31
-; CHECK-GI-NEXT:    sshr v2.2d, v2.2d, #31
-; CHECK-GI-NEXT:    sshr v1.2d, v1.2d, #31
-; CHECK-GI-NEXT:    cmgt v5.2d, v3.2d, v4.2d
-; CHECK-GI-NEXT:    cmgt v6.2d, v3.2d, v0.2d
-; CHECK-GI-NEXT:    cmgt v7.2d, v3.2d, v2.2d
-; CHECK-GI-NEXT:    cmgt v16.2d, v3.2d, v1.2d
-; CHECK-GI-NEXT:    bif v4.16b, v3.16b, v5.16b
-; CHECK-GI-NEXT:    bif v0.16b, v3.16b, v6.16b
-; CHECK-GI-NEXT:    bif v2.16b, v3.16b, v7.16b
-; CHECK-GI-NEXT:    bif v1.16b, v3.16b, v16.16b
-; CHECK-GI-NEXT:    uzp1 v0.4s, v4.4s, v0.4s
-; CHECK-GI-NEXT:    uzp1 v1.4s, v2.4s, v1.4s
+; CHECK-GI-NEXT:    smull v5.2d, v3.2s, v1.2s
+; CHECK-GI-NEXT:    smull2 v2.2d, v2.4s, v0.4s
+; CHECK-GI-NEXT:    smull2 v3.2d, v3.4s, v1.4s
+; CHECK-GI-NEXT:    sqshrn v0.2s, v4.2d, #31
+; CHECK-GI-NEXT:    sqshrn v1.2s, v5.2d, #31
+; CHECK-GI-NEXT:    sqshrn2 v0.4s, v2.2d, #31
+; CHECK-GI-NEXT:    sqshrn2 v1.4s, v3.2d, #31
 ; CHECK-GI-NEXT:    ret
   %as = sext <8 x i32> %a to <8 x i64>
   %bs = sext <8 x i32> %b to <8 x i64>
@@ -217,38 +186,31 @@ define <6 x i16> @saturating_6xi16(<6 x i16> %a, <6 x i16> %b) {
 ; CHECK-GI-NEXT:    smov w9, v0.h[0]
 ; CHECK-GI-NEXT:    smov w10, v1.h[1]
 ; CHECK-GI-NEXT:    smov w11, v0.h[1]
-; CHECK-GI-NEXT:    smov w12, v1.h[2]
-; CHECK-GI-NEXT:    movi v2.4s, #127, msl #8
-; CHECK-GI-NEXT:    fmov s3, w8
-; CHECK-GI-NEXT:    fmov s4, w9
-; CHECK-GI-NEXT:    smov w8, v0.h[2]
-; CHECK-GI-NEXT:    smov w9, v1.h[3]
+; CHECK-GI-NEXT:    fmov s2, w8
+; CHECK-GI-NEXT:    fmov s3, w9
+; CHECK-GI-NEXT:    smov w8, v1.h[2]
+; CHECK-GI-NEXT:    smov w9, v0.h[2]
+; CHECK-GI-NEXT:    mov v2.s[1], w10
+; CHECK-GI-NEXT:    mov v3.s[1], w11
+; CHECK-GI-NEXT:    smov w10, v1.h[3]
 ; CHECK-GI-NEXT:    sshll2 v1.4s, v1.8h, #0
-; CHECK-GI-NEXT:    mov v3.s[1], w10
-; CHECK-GI-NEXT:    mov v4.s[1], w11
-; CHECK-GI-NEXT:    smov w10, v0.h[3]
+; CHECK-GI-NEXT:    mov v2.s[2], w8
+; CHECK-GI-NEXT:    smov w8, v0.h[3]
+; CHECK-GI-NEXT:    mov v3.s[2], w9
 ; CHECK-GI-NEXT:    sshll2 v0.4s, v0.8h, #0
-; CHECK-GI-NEXT:    mov v3.s[2], w12
-; CHECK-GI-NEXT:    mov v4.s[2], w8
+; CHECK-GI-NEXT:    mov v2.s[3], w10
+; CHECK-GI-NEXT:    mov v3.s[3], w8
 ; CHECK-GI-NEXT:    mul v0.2s, v1.2s, v0.2s
-; CHECK-GI-NEXT:    movi v1.2s, #127, msl #8
-; CHECK-GI-NEXT:    mov v3.s[3], w9
-; CHECK-GI-NEXT:    mov v4.s[3], w10
+; CHECK-GI-NEXT:    mul v2.4s, v2.4s, v3.4s
 ; CHECK-GI-NEXT:    sshr v0.2s, v0.2s, #15
-; CHECK-GI-NEXT:    smin v0.2s, v0.2s, v1.2s
-; CHECK-GI-NEXT:    mul v3.4s, v3.4s, v4.4s
-; CHECK-GI-NEXT:    sshr v3.4s, v3.4s, #15
-; CHECK-GI-NEXT:    smin v2.4s, v3.4s, v2.4s
-; CHECK-GI-NEXT:    mov w8, v2.s[1]
-; CHECK-GI-NEXT:    mov w9, v2.s[2]
-; CHECK-GI-NEXT:    mov w10, v2.s[3]
-; CHECK-GI-NEXT:    mov v2.h[1], w8
-; CHECK-GI-NEXT:    fmov w8, s0
-; CHECK-GI-NEXT:    mov v2.h[2], w9
-; CHECK-GI-NEXT:    mov w9, v0.s[1]
-; CHECK-GI-NEXT:    mov v2.h[3], w10
-; CHECK-GI-NEXT:    mov v2.h[4], w8
-; CHECK-GI-NEXT:    mov v2.h[5], w9
+; CHECK-GI-NEXT:    sqxtn v0.4h, v0.4s
+; CHECK-GI-NEXT:    sqshrn v3.4h, v2.4s, #15
+; CHECK-GI-NEXT:    mov v2.h[0], v3.h[0]
+; CHECK-GI-NEXT:    mov v2.h[1], v3.h[1]
+; CHECK-GI-NEXT:    mov v2.h[2], v3.h[2]
+; CHECK-GI-NEXT:    mov v2.h[3], v3.h[3]
+; CHECK-GI-NEXT:    mov v2.h[4], v0.h[0]
+; CHECK-GI-NEXT:    mov v2.h[5], v0.h[1]
 ; CHECK-GI-NEXT:    mov v0.16b, v2.16b
 ; CHECK-GI-NEXT:    ret
   %as = sext <6 x i16> %a to <6 x i32>

``````````

</details>


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


More information about the llvm-commits mailing list