[llvm] [AMDGPU][GlobalISel] Fix i64-to-i16 clamp combine on inverted bounds (PR #216306)

Arseniy Obolenskiy via llvm-commits llvm-commits at lists.llvm.org
Thu Aug 27 07:47:24 PDT 2026


https://github.com/aobolensk updated https://github.com/llvm/llvm-project/pull/216306

>From a05b29e4326acfac7ea7378db8fb05cac3887d73 Mon Sep 17 00:00:00 2001
From: Arseniy Obolenskiy <arseniy.obolenskiy at amd.com>
Date: Fri, 14 Aug 2026 13:20:54 +0200
Subject: [PATCH 1/2] [AMDGPU][GlobalISel] Fix i64-to-i16 clamp combine on
 inverted bounds

`abs(Cmp2 - Cmp1)` check couldn't tell inverted bounds `(Lo > Hi)` from valid ones, letting the combine build a med3 that clamped to the wrong range instead of the constant smin/smax result
---
 .../AMDGPU/AMDGPUPreLegalizerCombiner.cpp     | 26 ++++---
 .../combine-short-clamp-inverted-bounds.mir   | 71 +++++++++++++++++++
 2 files changed, 83 insertions(+), 14 deletions(-)
 create mode 100644 llvm/test/CodeGen/AMDGPU/GlobalISel/combine-short-clamp-inverted-bounds.mir

diff --git a/llvm/lib/Target/AMDGPU/AMDGPUPreLegalizerCombiner.cpp b/llvm/lib/Target/AMDGPU/AMDGPUPreLegalizerCombiner.cpp
index 29a1adbb06602..82df0ac70b7bd 100644
--- a/llvm/lib/Target/AMDGPU/AMDGPUPreLegalizerCombiner.cpp
+++ b/llvm/lib/Target/AMDGPU/AMDGPUPreLegalizerCombiner.cpp
@@ -122,22 +122,20 @@ bool AMDGPUPreLegalizerCombinerImpl::matchClampI64ToI16(
 
   Register Base;
 
-  auto IsApplicableForCombine = [&MatchInfo]() -> bool {
-    const auto Cmp1 = MatchInfo.Cmp1;
-    const auto Cmp2 = MatchInfo.Cmp2;
-    const auto Diff = std::abs(Cmp2 - Cmp1);
-
-    // If the difference between both comparison values is 0 or 1, there is no
-    // need to clamp.
-    if (Diff == 0 || Diff == 1)
-      return false;
+  // Lo must not exceed Hi: with inverted bounds smin(smax(X, Lo), Hi) is
+  // constant, but the med3 built below would still clamp X to [Hi, Lo].
+  auto IsApplicableForCombine = [&MatchInfo](bool OuterIsMin) -> bool {
+    const int64_t Lo = OuterIsMin ? MatchInfo.Cmp2 : MatchInfo.Cmp1;
+    const int64_t Hi = OuterIsMin ? MatchInfo.Cmp1 : MatchInfo.Cmp2;
 
+    // Range-check first so Hi - Lo below can't overflow.
     const int64_t Min = std::numeric_limits<int16_t>::min();
     const int64_t Max = std::numeric_limits<int16_t>::max();
+    if (Lo < Min || Lo > Max || Hi < Min || Hi > Max)
+      return false;
 
-    // Check if the comparison values are between SHORT_MIN and SHORT_MAX.
-    return ((Cmp2 >= Cmp1 && Cmp1 >= Min && Cmp2 <= Max) ||
-            (Cmp1 >= Cmp2 && Cmp1 <= Max && Cmp2 >= Min));
+    // Reject inverted bounds, and bounds so close there is no need to clamp.
+    return Hi - Lo > 1;
   };
 
   // Try to match a combination of min / max MIR opcodes.
@@ -145,7 +143,7 @@ bool AMDGPUPreLegalizerCombinerImpl::matchClampI64ToI16(
                m_GSMin(m_Reg(Base), m_ICst(MatchInfo.Cmp1)))) {
     if (mi_match(Base, MRI,
                  m_GSMax(m_Reg(MatchInfo.Origin), m_ICst(MatchInfo.Cmp2)))) {
-      return IsApplicableForCombine();
+      return IsApplicableForCombine(/*OuterIsMin=*/true);
     }
   }
 
@@ -153,7 +151,7 @@ bool AMDGPUPreLegalizerCombinerImpl::matchClampI64ToI16(
                m_GSMax(m_Reg(Base), m_ICst(MatchInfo.Cmp1)))) {
     if (mi_match(Base, MRI,
                  m_GSMin(m_Reg(MatchInfo.Origin), m_ICst(MatchInfo.Cmp2)))) {
-      return IsApplicableForCombine();
+      return IsApplicableForCombine(/*OuterIsMin=*/false);
     }
   }
 
diff --git a/llvm/test/CodeGen/AMDGPU/GlobalISel/combine-short-clamp-inverted-bounds.mir b/llvm/test/CodeGen/AMDGPU/GlobalISel/combine-short-clamp-inverted-bounds.mir
new file mode 100644
index 0000000000000..8f02f7fb113cb
--- /dev/null
+++ b/llvm/test/CodeGen/AMDGPU/GlobalISel/combine-short-clamp-inverted-bounds.mir
@@ -0,0 +1,71 @@
+# NOTE: Assertions have been autogenerated by utils/update_mir_test_checks.py
+# RUN: llc -mtriple=amdgpu9.00-amd-amdhsa -run-pass=amdgpu-prelegalizer-combiner -verify-machineinstrs %s -o - | FileCheck %s
+
+# matchClampI64ToI16 must not fire on inverted bounds: smin(smax(x,100),0) is
+# constant 0, not a [0,100] clamp.
+
+---
+name: clamp_i64_i16_inverted_bounds
+tracksRegLiveness: true
+body:             |
+  bb.0:
+    liveins: $vgpr0, $vgpr1
+
+    ; CHECK-LABEL: name: clamp_i64_i16_inverted_bounds
+    ; CHECK: liveins: $vgpr0, $vgpr1
+    ; CHECK-NEXT: {{  $}}
+    ; CHECK-NEXT: [[COPY:%[0-9]+]]:_(s32) = COPY $vgpr0
+    ; CHECK-NEXT: [[COPY1:%[0-9]+]]:_(s32) = COPY $vgpr1
+    ; CHECK-NEXT: [[MV:%[0-9]+]]:_(s64) = G_MERGE_VALUES [[COPY]](s32), [[COPY1]](s32)
+    ; CHECK-NEXT: [[C:%[0-9]+]]:_(s64) = G_CONSTANT i64 100
+    ; CHECK-NEXT: [[SMAX:%[0-9]+]]:_(s64) = G_SMAX [[MV]], [[C]]
+    ; CHECK-NEXT: [[C1:%[0-9]+]]:_(s64) = G_CONSTANT i64 0
+    ; CHECK-NEXT: [[SMIN:%[0-9]+]]:_(s64) = G_SMIN [[SMAX]], [[C1]]
+    ; CHECK-NEXT: [[TRUNC:%[0-9]+]]:_(s16) = G_TRUNC [[SMIN]](s64)
+    ; CHECK-NEXT: [[ANYEXT:%[0-9]+]]:_(s32) = G_ANYEXT [[TRUNC]](s16)
+    ; CHECK-NEXT: $vgpr0 = COPY [[ANYEXT]](s32)
+    %0:_(s32) = COPY $vgpr0
+    %1:_(s32) = COPY $vgpr1
+    %2:_(s64) = G_MERGE_VALUES %0(s32), %1(s32)
+    %3:_(s64) = G_CONSTANT i64 100
+    %4:_(s64) = G_SMAX %2, %3
+    %5:_(s64) = G_CONSTANT i64 0
+    %6:_(s64) = G_SMIN %4, %5
+    %7:_(s16) = G_TRUNC %6(s64)
+    %8:_(s32) = G_ANYEXT %7(s16)
+    $vgpr0 = COPY %8(s32)
+
+...
+
+---
+name: clamp_i64_i16_inverted_bounds_minmax
+tracksRegLiveness: true
+body:             |
+  bb.0:
+    liveins: $vgpr0, $vgpr1
+
+    ; CHECK-LABEL: name: clamp_i64_i16_inverted_bounds_minmax
+    ; CHECK: liveins: $vgpr0, $vgpr1
+    ; CHECK-NEXT: {{  $}}
+    ; CHECK-NEXT: [[COPY:%[0-9]+]]:_(s32) = COPY $vgpr0
+    ; CHECK-NEXT: [[COPY1:%[0-9]+]]:_(s32) = COPY $vgpr1
+    ; CHECK-NEXT: [[MV:%[0-9]+]]:_(s64) = G_MERGE_VALUES [[COPY]](s32), [[COPY1]](s32)
+    ; CHECK-NEXT: [[C:%[0-9]+]]:_(s64) = G_CONSTANT i64 0
+    ; CHECK-NEXT: [[SMIN:%[0-9]+]]:_(s64) = G_SMIN [[MV]], [[C]]
+    ; CHECK-NEXT: [[C1:%[0-9]+]]:_(s64) = G_CONSTANT i64 100
+    ; CHECK-NEXT: [[SMAX:%[0-9]+]]:_(s64) = G_SMAX [[SMIN]], [[C1]]
+    ; CHECK-NEXT: [[TRUNC:%[0-9]+]]:_(s16) = G_TRUNC [[SMAX]](s64)
+    ; CHECK-NEXT: [[ANYEXT:%[0-9]+]]:_(s32) = G_ANYEXT [[TRUNC]](s16)
+    ; CHECK-NEXT: $vgpr0 = COPY [[ANYEXT]](s32)
+    %0:_(s32) = COPY $vgpr0
+    %1:_(s32) = COPY $vgpr1
+    %2:_(s64) = G_MERGE_VALUES %0(s32), %1(s32)
+    %3:_(s64) = G_CONSTANT i64 0
+    %4:_(s64) = G_SMIN %2, %3
+    %5:_(s64) = G_CONSTANT i64 100
+    %6:_(s64) = G_SMAX %4, %5
+    %7:_(s16) = G_TRUNC %6(s64)
+    %8:_(s32) = G_ANYEXT %7(s16)
+    $vgpr0 = COPY %8(s32)
+
+...

>From 590c8f0cad5ab0446103c8267bbfce25c25c6a5c Mon Sep 17 00:00:00 2001
From: Arseniy Obolenskiy <arseniy.obolenskiy at amd.com>
Date: Thu, 27 Aug 2026 16:47:13 +0200
Subject: [PATCH 2/2] add tests

---
 .../combine-short-clamp-inverted-bounds.mir   | 66 +++++++++++++++++++
 1 file changed, 66 insertions(+)

diff --git a/llvm/test/CodeGen/AMDGPU/GlobalISel/combine-short-clamp-inverted-bounds.mir b/llvm/test/CodeGen/AMDGPU/GlobalISel/combine-short-clamp-inverted-bounds.mir
index 8f02f7fb113cb..924b58f720444 100644
--- a/llvm/test/CodeGen/AMDGPU/GlobalISel/combine-short-clamp-inverted-bounds.mir
+++ b/llvm/test/CodeGen/AMDGPU/GlobalISel/combine-short-clamp-inverted-bounds.mir
@@ -69,3 +69,69 @@ body:             |
     $vgpr0 = COPY %8(s32)
 
 ...
+
+---
+name: clamp_i64_i16_inverted_bounds_extreme
+tracksRegLiveness: true
+body:             |
+  bb.0:
+    liveins: $vgpr0, $vgpr1
+
+    ; CHECK-LABEL: name: clamp_i64_i16_inverted_bounds_extreme
+    ; CHECK: liveins: $vgpr0, $vgpr1
+    ; CHECK-NEXT: {{  $}}
+    ; CHECK-NEXT: [[COPY:%[0-9]+]]:_(s32) = COPY $vgpr0
+    ; CHECK-NEXT: [[COPY1:%[0-9]+]]:_(s32) = COPY $vgpr1
+    ; CHECK-NEXT: [[MV:%[0-9]+]]:_(s64) = G_MERGE_VALUES [[COPY]](s32), [[COPY1]](s32)
+    ; CHECK-NEXT: [[C:%[0-9]+]]:_(s64) = G_CONSTANT i64 32767
+    ; CHECK-NEXT: [[SMAX:%[0-9]+]]:_(s64) = G_SMAX [[MV]], [[C]]
+    ; CHECK-NEXT: [[C1:%[0-9]+]]:_(s64) = G_CONSTANT i64 -32768
+    ; CHECK-NEXT: [[SMIN:%[0-9]+]]:_(s64) = G_SMIN [[SMAX]], [[C1]]
+    ; CHECK-NEXT: [[TRUNC:%[0-9]+]]:_(s16) = G_TRUNC [[SMIN]](s64)
+    ; CHECK-NEXT: [[ANYEXT:%[0-9]+]]:_(s32) = G_ANYEXT [[TRUNC]](s16)
+    ; CHECK-NEXT: $vgpr0 = COPY [[ANYEXT]](s32)
+    %0:_(s32) = COPY $vgpr0
+    %1:_(s32) = COPY $vgpr1
+    %2:_(s64) = G_MERGE_VALUES %0(s32), %1(s32)
+    %3:_(s64) = G_CONSTANT i64 32767
+    %4:_(s64) = G_SMAX %2, %3
+    %5:_(s64) = G_CONSTANT i64 -32768
+    %6:_(s64) = G_SMIN %4, %5
+    %7:_(s16) = G_TRUNC %6(s64)
+    %8:_(s32) = G_ANYEXT %7(s16)
+    $vgpr0 = COPY %8(s32)
+
+...
+
+---
+name: clamp_i64_i16_inverted_bounds_extreme_minmax
+tracksRegLiveness: true
+body:             |
+  bb.0:
+    liveins: $vgpr0, $vgpr1
+
+    ; CHECK-LABEL: name: clamp_i64_i16_inverted_bounds_extreme_minmax
+    ; CHECK: liveins: $vgpr0, $vgpr1
+    ; CHECK-NEXT: {{  $}}
+    ; CHECK-NEXT: [[COPY:%[0-9]+]]:_(s32) = COPY $vgpr0
+    ; CHECK-NEXT: [[COPY1:%[0-9]+]]:_(s32) = COPY $vgpr1
+    ; CHECK-NEXT: [[MV:%[0-9]+]]:_(s64) = G_MERGE_VALUES [[COPY]](s32), [[COPY1]](s32)
+    ; CHECK-NEXT: [[C:%[0-9]+]]:_(s64) = G_CONSTANT i64 -32768
+    ; CHECK-NEXT: [[SMIN:%[0-9]+]]:_(s64) = G_SMIN [[MV]], [[C]]
+    ; CHECK-NEXT: [[C1:%[0-9]+]]:_(s64) = G_CONSTANT i64 32767
+    ; CHECK-NEXT: [[SMAX:%[0-9]+]]:_(s64) = G_SMAX [[SMIN]], [[C1]]
+    ; CHECK-NEXT: [[TRUNC:%[0-9]+]]:_(s16) = G_TRUNC [[SMAX]](s64)
+    ; CHECK-NEXT: [[ANYEXT:%[0-9]+]]:_(s32) = G_ANYEXT [[TRUNC]](s16)
+    ; CHECK-NEXT: $vgpr0 = COPY [[ANYEXT]](s32)
+    %0:_(s32) = COPY $vgpr0
+    %1:_(s32) = COPY $vgpr1
+    %2:_(s64) = G_MERGE_VALUES %0(s32), %1(s32)
+    %3:_(s64) = G_CONSTANT i64 -32768
+    %4:_(s64) = G_SMIN %2, %3
+    %5:_(s64) = G_CONSTANT i64 32767
+    %6:_(s64) = G_SMAX %4, %5
+    %7:_(s16) = G_TRUNC %6(s64)
+    %8:_(s32) = G_ANYEXT %7(s16)
+    $vgpr0 = COPY %8(s32)
+
+...



More information about the llvm-commits mailing list