[llvm] [AMDGPU] Lower uniform usubsat to SOP (S_MAX_U32 + S_SUB_I32) (PR #203155)

Anshil Gandhi via llvm-commits llvm-commits at lists.llvm.org
Wed Jun 10 20:00:22 PDT 2026


https://github.com/gandhi56 created https://github.com/llvm/llvm-project/pull/203155

Add TableGen GCNPat rules in SOPInstructions.td so uniform usubsat lowers to scalar ops: max(a, b) - b, which matches unsigned saturating subtract (result is a - b when a >= b, otherwise 0). i32 uses S_MAX_U32 and S_SUB_I32 directly; i16 zero-extends operands with S_AND_B32 / 0xffff before the same sequence. Patterns use AddedComplexity = 20 so the backend prefers these scalar (SOP) forms over VALU when the operation is uniform.

Authored by: Jeffrey Byrnes

>From b5c40dda64a1aa57439691f48f6276976722542f Mon Sep 17 00:00:00 2001
From: Anshil Gandhi <Anshil.Gandhi at amd.com>
Date: Wed, 10 Jun 2026 17:47:58 -0500
Subject: [PATCH] [AMDGPU] Lower uniform usubsat to SOP (S_MAX_U32 + S_SUB_I32)

Add TableGen GCNPat rules in SOPInstructions.td so
uniform usubsat lowers to scalar ops: max(a, b) - b,
which matches unsigned saturating subtract (result
is a - b when a >= b, otherwise 0). i32 uses S_MAX_U32
and S_SUB_I32 directly; i16 zero-extends operands with
S_AND_B32 / 0xffff before the same sequence. Patterns
use AddedComplexity = 20 so the backend prefers these
scalar (SOP) forms over VALU when the operation is
uniform.

Authored by: Jeffrey Byrnes
---
 llvm/lib/Target/AMDGPU/SOPInstructions.td     | 21 ++++++++++++
 .../CodeGen/AMDGPU/usubsat-uniform-sdag.ll    | 33 +++++++++++++++++++
 2 files changed, 54 insertions(+)
 create mode 100644 llvm/test/CodeGen/AMDGPU/usubsat-uniform-sdag.ll

diff --git a/llvm/lib/Target/AMDGPU/SOPInstructions.td b/llvm/lib/Target/AMDGPU/SOPInstructions.td
index b49bd100f9d90..1c5c331d56585 100644
--- a/llvm/lib/Target/AMDGPU/SOPInstructions.td
+++ b/llvm/lib/Target/AMDGPU/SOPInstructions.td
@@ -2078,6 +2078,27 @@ let AddedComplexity = 20 in {
   >;
 }
 
+// Uniform saturating unsigned subtraction: usubsat(a, b) = max(a, b) - b
+// If a >= b, result is a - b; otherwise max(a, b) = b, so b - b = 0.
+// Higher complexity to prefer scalar over VALU patterns for uniform values.
+let AddedComplexity = 20 in {
+  def : GCNPat<
+    (i32 (UniformBinFrag<usubsat> i32:$src0, i32:$src1)),
+    (S_SUB_I32 (S_MAX_U32 $src0, $src1), $src1)
+  >;
+
+  // i16 uniform usubsat - zero-extend to i32, apply max+sub, result is already
+  // correct since usubsat clamps to 0 (never negative).
+  def : GCNPat<
+    (i16 (UniformBinFrag<usubsat> i16:$src0, i16:$src1)),
+    (S_SUB_I32
+      (S_MAX_U32
+        (S_AND_B32 $src0, (i32 0xffff)),
+        (S_AND_B32 $src1, (i32 0xffff))),
+      (S_AND_B32 $src1, (i32 0xffff)))
+  >;
+} // End AddedComplexity = 20
+
 // V_ADD_I32_e32/S_ADD_U32 produces carry in VCC/SCC. For the vector
 // case, the sgpr-copies pass will fix this to use the vector version.
 def : GCNPat <
diff --git a/llvm/test/CodeGen/AMDGPU/usubsat-uniform-sdag.ll b/llvm/test/CodeGen/AMDGPU/usubsat-uniform-sdag.ll
new file mode 100644
index 0000000000000..87c5b57e10520
--- /dev/null
+++ b/llvm/test/CodeGen/AMDGPU/usubsat-uniform-sdag.ll
@@ -0,0 +1,33 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+; NOTE: Tests SelectionDAG (default llc) lowering of uniform @llvm.usub.sat
+; to scalar SOPs (s_max_u32 + s_sub_i32). GlobalISel coverage lives in
+; GlobalISel/usubsat.ll.
+;
+; RUN: llc -mtriple=amdgcn-amd-amdpal -mcpu=gfx1201 < %s | FileCheck %s
+
+declare i32 @llvm.usub.sat.i32(i32, i32)
+declare i16 @llvm.usub.sat.i16(i16, i16)
+
+define amdgpu_ps i32 @uniform_usubsat_i32(i32 inreg %a, i32 inreg %b) {
+; CHECK-LABEL: uniform_usubsat_i32:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_max_u32 s0, s0, s1
+; CHECK-NEXT:    s_delay_alu instid0(SALU_CYCLE_1)
+; CHECK-NEXT:    s_sub_co_i32 s0, s0, s1
+; CHECK-NEXT:    ; return to shader part epilog
+  %r = call i32 @llvm.usub.sat.i32(i32 %a, i32 %b)
+  ret i32 %r
+}
+
+define amdgpu_ps i16 @uniform_usubsat_i16(i16 inreg %a, i16 inreg %b) {
+; CHECK-LABEL: uniform_usubsat_i16:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_and_b32 s1, s1, 0xffff
+; CHECK-NEXT:    s_and_b32 s0, s0, 0xffff
+; CHECK-NEXT:    s_delay_alu instid0(SALU_CYCLE_1) | instskip(NEXT) | instid1(SALU_CYCLE_1)
+; CHECK-NEXT:    s_max_u32 s0, s0, s1
+; CHECK-NEXT:    s_sub_co_i32 s0, s0, s1
+; CHECK-NEXT:    ; return to shader part epilog
+  %r = call i16 @llvm.usub.sat.i16(i16 %a, i16 %b)
+  ret i16 %r
+}



More information about the llvm-commits mailing list