[llvm] [AMDGPU][GISel] Fold 'min(min(x,y),z)' and 'max(max(x,y),z)' into min3 and max3 (PR #200410)

via llvm-commits llvm-commits at lists.llvm.org
Mon Jun 1 08:01:01 PDT 2026


https://github.com/xiongzile updated https://github.com/llvm/llvm-project/pull/200410

>From b1b3ac6d59a773cc91fa1f5fb4f96cb1cb177393 Mon Sep 17 00:00:00 2001
From: Zile Xiong <xiongzile at bytedance.com>
Date: Fri, 29 May 2026 21:48:04 +0800
Subject: [PATCH 1/2] [AMDGPU][GISel] Fold 'min(min(x,y),z)' and
 'max(max(x,y),z)' into min3 and max3.

---
 llvm/lib/Target/AMDGPU/AMDGPUCombine.td       |  21 ++-
 llvm/lib/Target/AMDGPU/AMDGPUGISel.td         |   6 +
 .../AMDGPU/AMDGPUPreLegalizerCombiner.cpp     |  59 ++++++
 .../Target/AMDGPU/AMDGPURegisterBankInfo.cpp  |  20 ++
 llvm/lib/Target/AMDGPU/SIInstructions.td      |  36 ++++
 .../AMDGPU/GlobalISel/fmin3-fmax3-combine.ll  |  71 ++++++++
 .../AMDGPU/GlobalISel/min3-max3-combine.ll    | 171 ++++++++++++++++++
 7 files changed, 383 insertions(+), 1 deletion(-)
 create mode 100644 llvm/test/CodeGen/AMDGPU/GlobalISel/fmin3-fmax3-combine.ll
 create mode 100644 llvm/test/CodeGen/AMDGPU/GlobalISel/min3-max3-combine.ll

diff --git a/llvm/lib/Target/AMDGPU/AMDGPUCombine.td b/llvm/lib/Target/AMDGPU/AMDGPUCombine.td
index a2e6e6f448e8f..a20ed2a89029b 100644
--- a/llvm/lib/Target/AMDGPU/AMDGPUCombine.td
+++ b/llvm/lib/Target/AMDGPU/AMDGPUCombine.td
@@ -157,6 +157,23 @@ def zext_of_shift_amount_combines : GICombineGroup<[
   canonicalize_zext_lshr, canonicalize_zext_ashr, canonicalize_zext_shl
 ]>;
 
+def minmax3_matchdata : GIDefMatchData<"MinMaxToMinMax3MatchInfo">;
+
+class minmax_to_minmax3_opcodes<Instruction minmaxOpcode> : GICombineRule<
+  (defs root:$min_or_max, minmax3_matchdata:$matchinfo),
+  (match (minmaxOpcode $dst, $lhs, $rhs):$min_or_max,
+         [{ return matchMinMaxToMinMax3(*${min_or_max}, ${matchinfo}); }]),
+  (apply [{ applyMinMaxToMinMax3(*${min_or_max}, ${matchinfo}); }])>;
+
+def smax_to_minmax3 : minmax_to_minmax3_opcodes<G_SMAX>;
+def smin_to_minmax3 : minmax_to_minmax3_opcodes<G_SMIN>;
+def umax_to_minmax3 : minmax_to_minmax3_opcodes<G_UMAX>;
+def umin_to_minmax3 : minmax_to_minmax3_opcodes<G_UMIN>;
+def fmax_to_minmax3 : minmax_to_minmax3_opcodes<G_FMAXNUM>;
+def fmin_to_minmax3 : minmax_to_minmax3_opcodes<G_FMINNUM>;
+def fmax_ieee_to_minmax3 : minmax_to_minmax3_opcodes<G_FMAXNUM_IEEE>;
+def fmin_ieee_to_minmax3 : minmax_to_minmax3_opcodes<G_FMINNUM_IEEE>;
+
 // (and/or i64:x, i64:y) -> i64:(merge (and/or lo_32(x), lo_32(y)), (and/or hi_32(x), hi_32(y)))
 // when either x or y is all ones in low or high parts
 class combine_binop_s64_with_s32_mask<Instruction opcode> : GICombineRule<
@@ -219,7 +236,9 @@ def AMDGPUPreLegalizerCombiner: GICombiner<
   "AMDGPUPreLegalizerCombinerImpl",
   [all_combines, combine_fmul_with_select_to_fldexp, clamp_i64_to_i16,
    foldable_fneg, combine_shuffle_vector, combine_shuffle_vector_to_build_vector,
-   binop_s64_with_s32_mask_combines, combine_or_s64_s32]> {
+   binop_s64_with_s32_mask_combines, combine_or_s64_s32, smax_to_minmax3, 
+   smin_to_minmax3, umax_to_minmax3, umin_to_minmax3, fmax_to_minmax3, 
+   fmin_to_minmax3, fmax_ieee_to_minmax3, fmin_ieee_to_minmax3]> {
   let CombineAllMethodName = "tryCombineAllImpl";
 }
 
diff --git a/llvm/lib/Target/AMDGPU/AMDGPUGISel.td b/llvm/lib/Target/AMDGPU/AMDGPUGISel.td
index 51a8a476bbf7e..97957f5100067 100644
--- a/llvm/lib/Target/AMDGPU/AMDGPUGISel.td
+++ b/llvm/lib/Target/AMDGPU/AMDGPUGISel.td
@@ -271,6 +271,12 @@ def : GINodeEquiv<G_AMDGPU_CVT_PK_I16_I32, AMDGPUpk_i16_i32_impl>;
 def : GINodeEquiv<G_AMDGPU_SMED3, AMDGPUsmed3>;
 def : GINodeEquiv<G_AMDGPU_UMED3, AMDGPUumed3>;
 def : GINodeEquiv<G_AMDGPU_FMED3, AMDGPUfmed3_impl>;
+def : GINodeEquiv<G_AMDGPU_SMAX3, AMDGPUsmax3>;
+def : GINodeEquiv<G_AMDGPU_UMAX3, AMDGPUumax3>;
+def : GINodeEquiv<G_AMDGPU_FMAX3, AMDGPUfmax3>;
+def : GINodeEquiv<G_AMDGPU_SMIN3, AMDGPUsmin3>;
+def : GINodeEquiv<G_AMDGPU_UMIN3, AMDGPUumin3>;
+def : GINodeEquiv<G_AMDGPU_FMIN3, AMDGPUfmin3>;
 def : GINodeEquiv<G_AMDGPU_CLAMP, AMDGPUclamp>;
 
 def : GINodeEquiv<G_AMDGPU_ATOMIC_CMPXCHG, AMDGPUatomic_cmp_swap>;
diff --git a/llvm/lib/Target/AMDGPU/AMDGPUPreLegalizerCombiner.cpp b/llvm/lib/Target/AMDGPU/AMDGPUPreLegalizerCombiner.cpp
index 67c0bdd35f367..d1966893e18a2 100644
--- a/llvm/lib/Target/AMDGPU/AMDGPUPreLegalizerCombiner.cpp
+++ b/llvm/lib/Target/AMDGPU/AMDGPUPreLegalizerCombiner.cpp
@@ -16,6 +16,7 @@
 #include "AMDGPULegalizerInfo.h"
 #include "GCNSubtarget.h"
 #include "MCTargetDesc/AMDGPUMCTargetDesc.h"
+#include "llvm/ADT/DenseMap.h"
 #include "llvm/CodeGen/GlobalISel/CSEInfo.h"
 #include "llvm/CodeGen/GlobalISel/Combiner.h"
 #include "llvm/CodeGen/GlobalISel/CombinerHelper.h"
@@ -66,6 +67,11 @@ class AMDGPUPreLegalizerCombinerImpl : public Combiner {
     Register Origin;
   };
 
+  struct MinMaxToMinMax3MatchInfo {
+    unsigned Opc;
+    Register Val0, Val1, Val2;
+  };
+
   bool matchClampI64ToI16(MachineInstr &MI, const MachineRegisterInfo &MRI,
                           const MachineFunction &MF,
                           ClampI64ToI16MatchInfo &MatchInfo) const;
@@ -73,6 +79,10 @@ class AMDGPUPreLegalizerCombinerImpl : public Combiner {
   void applyClampI64ToI16(MachineInstr &MI,
                           const ClampI64ToI16MatchInfo &MatchInfo) const;
 
+  bool matchMinMaxToMinMax3(MachineInstr &MI, MinMaxToMinMax3MatchInfo &MatchInfo) const;
+
+  void applyMinMaxToMinMax3(MachineInstr &MI, MinMaxToMinMax3MatchInfo &MatchInfo) const;
+
 private:
 #define GET_GICOMBINER_CLASS_MEMBERS
 #define AMDGPUSubtarget GCNSubtarget
@@ -106,6 +116,55 @@ bool AMDGPUPreLegalizerCombinerImpl::tryCombineAll(MachineInstr &MI) const {
   return false;
 }
 
+void AMDGPUPreLegalizerCombinerImpl::applyMinMaxToMinMax3(
+    MachineInstr &MI, MinMaxToMinMax3MatchInfo &MatchInfo) const {
+  B.buildInstr(MatchInfo.Opc, {MI.getOperand(0)},
+               {MatchInfo.Val0, MatchInfo.Val1, MatchInfo.Val2}, MI.getFlags());
+  MI.eraseFromParent();
+  return;
+}
+
+bool AMDGPUPreLegalizerCombinerImpl::matchMinMaxToMinMax3(
+    MachineInstr &MI, MinMaxToMinMax3MatchInfo &MatchInfo) const {
+  Register dst = MI.getOperand(0).getReg();
+  LLT t = MRI.getType(dst);
+  if (t == LLT::scalar(16)) {
+    if (!STI.hasMin3Max3_16()) {
+      return false;
+    }
+  } else if (t != LLT::scalar(32)) {
+    if (!(t.isVector() && t.getScalarSizeInBits() == 32))
+      return false;
+  }
+
+  Register R0, R1, R2;
+  unsigned opc = MI.getOpcode();
+  auto matchVOP3 = [&](MachineInstr &MI, MachineRegisterInfo &MRI, unsigned op,
+                       Register &r0, Register &r1, Register &r2) {
+    auto p1 = m_BinOp(op, m_OneNonDBGUse(m_BinOp(op, m_Reg(r0), m_Reg(r1))),
+                      m_Reg(r2));
+    auto p2 = m_BinOp(op, m_Reg(r0),
+                      m_OneNonDBGUse(m_BinOp(op, m_Reg(r1), m_Reg(r2))));
+
+    return mi_match(MI, MRI, m_any_of(p1, p2));
+  };
+  if (!matchVOP3(MI, MRI, opc, R0, R1, R2)) {
+    return false;
+  }
+
+  llvm::SmallDenseMap<uint16_t, uint16_t, 8> mp = {
+      {AMDGPU::G_SMAX, AMDGPU::G_AMDGPU_SMAX3},
+      {AMDGPU::G_SMIN, AMDGPU::G_AMDGPU_SMIN3},
+      {AMDGPU::G_UMAX, AMDGPU::G_AMDGPU_UMAX3},
+      {AMDGPU::G_UMIN, AMDGPU::G_AMDGPU_UMIN3},
+      {AMDGPU::G_FMAXNUM, AMDGPU::G_AMDGPU_FMAX3},
+      {AMDGPU::G_FMAXNUM_IEEE, AMDGPU::G_AMDGPU_FMAX3},
+      {AMDGPU::G_FMINNUM, AMDGPU::G_AMDGPU_FMIN3},
+      {AMDGPU::G_FMINNUM_IEEE, AMDGPU::G_AMDGPU_FMIN3}};
+  MatchInfo = {mp.at(opc), R0, R1, R2};
+  return true;
+}
+
 bool AMDGPUPreLegalizerCombinerImpl::matchClampI64ToI16(
     MachineInstr &MI, const MachineRegisterInfo &MRI, const MachineFunction &MF,
     ClampI64ToI16MatchInfo &MatchInfo) const {
diff --git a/llvm/lib/Target/AMDGPU/AMDGPURegisterBankInfo.cpp b/llvm/lib/Target/AMDGPU/AMDGPURegisterBankInfo.cpp
index a24df782cf28a..2b4674736bc7a 100644
--- a/llvm/lib/Target/AMDGPU/AMDGPURegisterBankInfo.cpp
+++ b/llvm/lib/Target/AMDGPU/AMDGPURegisterBankInfo.cpp
@@ -3959,6 +3959,26 @@ AMDGPURegisterBankInfo::getInstrMapping(const MachineInstr &MI) const {
   switch (MI.getOpcode()) {
   default:
     return getInvalidInstructionMapping();
+  case AMDGPU::G_AMDGPU_SMAX3:
+  case AMDGPU::G_AMDGPU_SMIN3:
+  case AMDGPU::G_AMDGPU_UMAX3:
+  case AMDGPU::G_AMDGPU_UMIN3:
+  case AMDGPU::G_AMDGPU_FMAX3:
+  case AMDGPU::G_AMDGPU_FMIN3: {
+    unsigned Size = MRI.getType(MI.getOperand(0).getReg()).getSizeInBits();
+    assert(Size == 32);
+    auto op1 = getRegBankID(MI.getOperand(1).getReg(), MRI,
+                               AMDGPU::VCCRegBankID);
+    auto op2 = getRegBankID(MI.getOperand(2).getReg(), MRI,
+                               AMDGPU::VCCRegBankID);
+    auto op3 = getRegBankID(MI.getOperand(3).getReg(), MRI,
+                               AMDGPU::VCCRegBankID);
+    OpdsMapping[0] = AMDGPU::getValueMapping(op1, Size);
+    OpdsMapping[1] = AMDGPU::getValueMapping(op1, Size);
+    OpdsMapping[2] = AMDGPU::getValueMapping(op2, Size);
+    OpdsMapping[3] = AMDGPU::getValueMapping(op3, Size);
+    break;
+  }
 
   case AMDGPU::G_AND:
   case AMDGPU::G_OR:
diff --git a/llvm/lib/Target/AMDGPU/SIInstructions.td b/llvm/lib/Target/AMDGPU/SIInstructions.td
index 148f15014b823..f24c2196d0af1 100644
--- a/llvm/lib/Target/AMDGPU/SIInstructions.td
+++ b/llvm/lib/Target/AMDGPU/SIInstructions.td
@@ -4702,6 +4702,42 @@ def G_AMDGPU_FMED3 : AMDGPUGenericInstruction {
   let hasSideEffects = 0;
 }
 
+def G_AMDGPU_SMIN3 : AMDGPUGenericInstruction {
+  let OutOperandList = (outs type0:$dst);
+  let InOperandList = (ins type0:$src0, type0:$src1, type0:$src2);
+  let hasSideEffects = 0;
+}
+
+def G_AMDGPU_UMIN3 : AMDGPUGenericInstruction {
+  let OutOperandList = (outs type0:$dst);
+  let InOperandList = (ins type0:$src0, type0:$src1, type0:$src2);
+  let hasSideEffects = 0;
+}
+
+def G_AMDGPU_FMIN3 : AMDGPUGenericInstruction {
+  let OutOperandList = (outs type0:$dst);
+  let InOperandList = (ins type0:$src0, type0:$src1, type0:$src2);
+  let hasSideEffects = 0;
+}
+
+def G_AMDGPU_SMAX3 : AMDGPUGenericInstruction {
+  let OutOperandList = (outs type0:$dst);
+  let InOperandList = (ins type0:$src0, type0:$src1, type0:$src2);
+  let hasSideEffects = 0;
+}
+
+def G_AMDGPU_UMAX3 : AMDGPUGenericInstruction {
+  let OutOperandList = (outs type0:$dst);
+  let InOperandList = (ins type0:$src0, type0:$src1, type0:$src2);
+  let hasSideEffects = 0;
+}
+
+def G_AMDGPU_FMAX3 : AMDGPUGenericInstruction {
+  let OutOperandList = (outs type0:$dst);
+  let InOperandList = (ins type0:$src0, type0:$src1, type0:$src2);
+  let hasSideEffects = 0;
+}
+
 def G_AMDGPU_CLAMP : AMDGPUGenericInstruction {
   let OutOperandList = (outs type0:$dst);
   let InOperandList = (ins type0:$src);
diff --git a/llvm/test/CodeGen/AMDGPU/GlobalISel/fmin3-fmax3-combine.ll b/llvm/test/CodeGen/AMDGPU/GlobalISel/fmin3-fmax3-combine.ll
new file mode 100644
index 0000000000000..0a6a63e5289c1
--- /dev/null
+++ b/llvm/test/CodeGen/AMDGPU/GlobalISel/fmin3-fmax3-combine.ll
@@ -0,0 +1,71 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
+; RUN: llc -global-isel -mtriple=amdgcn-amd-mesa3d -mcpu=gfx1010 -verify-machineinstrs < %s | FileCheck -check-prefix=GFX10 %s
+
+define float @test_fmin3(float %a, float %b, float %c) {
+; GFX10-LABEL: test_fmin3:
+; GFX10:       ; %bb.0:
+; GFX10-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; GFX10-NEXT:    v_min3_f32 v0, v0, v1, v2
+; GFX10-NEXT:    s_setpc_b64 s[30:31]
+  %min1 = call float @llvm.minnum.f32(float %a, float %b)
+  %min2 = call float @llvm.minnum.f32(float %min1, float %c)
+  ret float %min2
+}
+
+define float @test_fmin3_nnan(float %a, float %b, float %c) {
+; GFX10-LABEL: test_fmin3_nnan:
+; GFX10:       ; %bb.0:
+; GFX10-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; GFX10-NEXT:    v_min3_f32 v0, v0, v1, v2
+; GFX10-NEXT:    s_setpc_b64 s[30:31]
+  %min1 = call nnan float @llvm.minnum.f32(float %a, float %b)
+  %min2 = call nnan float @llvm.minnum.f32(float %min1, float %c)
+  ret float %min2
+}
+
+define float @test_fmin3_with_constants_nnan(float %a, float %b) {
+; GFX10-LABEL: test_fmin3_with_constants_nnan:
+; GFX10:       ; %bb.0:
+; GFX10-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; GFX10-NEXT:    v_min3_f32 v0, v0, v1, 0x40e00000
+; GFX10-NEXT:    s_setpc_b64 s[30:31]
+  %min1 = call nnan float @llvm.minnum.f32(float %a, float %b)
+  %min2 = call nnan float @llvm.minnum.f32(float %min1, float 7.0)
+  ret float %min2
+}
+
+define float @test_fmax3(float %a, float %b, float %c) {
+; GFX10-LABEL: test_fmax3:
+; GFX10:       ; %bb.0:
+; GFX10-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; GFX10-NEXT:    v_max3_f32 v0, v0, v1, v2
+; GFX10-NEXT:    s_setpc_b64 s[30:31]
+  %max1 = call float @llvm.maxnum.f32(float %a, float %b)
+  %max2 = call float @llvm.maxnum.f32(float %max1, float %c)
+  ret float %max2
+}
+
+define float @test_fmax3_nnan(float %a, float %b, float %c) {
+; GFX10-LABEL: test_fmax3_nnan:
+; GFX10:       ; %bb.0:
+; GFX10-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; GFX10-NEXT:    v_max3_f32 v0, v0, v1, v2
+; GFX10-NEXT:    s_setpc_b64 s[30:31]
+  %max1 = call nnan float @llvm.maxnum.f32(float %a, float %b)
+  %max2 = call nnan float @llvm.maxnum.f32(float %max1, float %c)
+  ret float %max2
+}
+
+define float @test_fmax3_with_constants_nnan(float %a, float %b) {
+; GFX10-LABEL: test_fmax3_with_constants_nnan:
+; GFX10:       ; %bb.0:
+; GFX10-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; GFX10-NEXT:    v_max3_f32 v0, v0, v1, 0x40e00000
+; GFX10-NEXT:    s_setpc_b64 s[30:31]
+  %max1 = call nnan float @llvm.maxnum.f32(float %a, float %b)
+  %max2 = call nnan float @llvm.maxnum.f32(float %max1, float 7.0)
+  ret float %max2
+}
+
+declare float @llvm.minnum.f32(float, float)
+declare float @llvm.maxnum.f32(float, float)
diff --git a/llvm/test/CodeGen/AMDGPU/GlobalISel/min3-max3-combine.ll b/llvm/test/CodeGen/AMDGPU/GlobalISel/min3-max3-combine.ll
new file mode 100644
index 0000000000000..e4165c090c399
--- /dev/null
+++ b/llvm/test/CodeGen/AMDGPU/GlobalISel/min3-max3-combine.ll
@@ -0,0 +1,171 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
+; RUN: llc -global-isel -mtriple=amdgcn-amd-mesa3d -mcpu=gfx1010 -verify-machineinstrs < %s | FileCheck -check-prefix=GFX10 %s
+
+define i32 @test_smin3(i32 %a, i32 %b, i32 %c) {
+; GFX10-LABEL: test_smin3:
+; GFX10:       ; %bb.0:
+; GFX10-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; GFX10-NEXT:    v_min3_i32 v0, v0, v1, v2
+; GFX10-NEXT:    s_setpc_b64 s[30:31]
+  %min1 = call i32 @llvm.smin.i32(i32 %a, i32 %b)
+  %min2 = call i32 @llvm.smin.i32(i32 %min1, i32 %c)
+  ret i32 %min2
+}
+
+define i32 @test_smin3_with_constants(i32 %a, i32 %b) {
+; GFX10-LABEL: test_smin3_with_constants:
+; GFX10:       ; %bb.0:
+; GFX10-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; GFX10-NEXT:    v_min3_i32 v0, v0, v1, 7
+; GFX10-NEXT:    s_setpc_b64 s[30:31]
+  %min1 = call i32 @llvm.smin.i32(i32 %a, i32 %b)
+  %min2 = call i32 @llvm.smin.i32(i32 %min1, i32 7)
+  ret i32 %min2
+}
+
+define i32 @test_smin3_smin_umin(i32 %a, i32 %b) {
+; GFX10-LABEL: test_smin3_smin_umin:
+; GFX10:       ; %bb.0:
+; GFX10-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; GFX10-NEXT:    v_min_i32_e32 v0, v0, v1
+; GFX10-NEXT:    v_min_u32_e32 v0, 7, v0
+; GFX10-NEXT:    s_setpc_b64 s[30:31]
+  %min1 = call i32 @llvm.smin.i32(i32 %a, i32 %b)
+  %min2 = call i32 @llvm.umin.i32(i32 %min1, i32 7)
+  ret i32 %min2
+}
+
+define <2 x i16> @test_smin3_v2i16(<2 x i16> %a, <2 x i16> %b, <2 x i16> %c) {
+; GFX10-LABEL: test_smin3_v2i16:
+; GFX10:       ; %bb.0:
+; GFX10-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; GFX10-NEXT:    v_pk_min_i16 v0, v0, v1
+; GFX10-NEXT:    v_pk_min_i16 v0, v0, v2
+; GFX10-NEXT:    s_setpc_b64 s[30:31]
+  %min1 = call <2 x i16> @llvm.smin.v2i16(<2 x i16> %a, <2 x i16> %b)
+  %min2 = call <2 x i16> @llvm.smin.v2i16(<2 x i16> %min1, <2 x i16> %c)
+  ret <2 x i16> %min2
+}
+
+define i32 @test_smax3(i32 %a, i32 %b, i32 %c) {
+; GFX10-LABEL: test_smax3:
+; GFX10:       ; %bb.0:
+; GFX10-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; GFX10-NEXT:    v_max3_i32 v0, v0, v1, v2
+; GFX10-NEXT:    s_setpc_b64 s[30:31]
+  %max1 = call i32 @llvm.smax.i32(i32 %a, i32 %b)
+  %max2 = call i32 @llvm.smax.i32(i32 %max1, i32 %c)
+  ret i32 %max2
+}
+
+define i32 @test_smax3_with_constants(i32 %a, i32 %b) {
+; GFX10-LABEL: test_smax3_with_constants:
+; GFX10:       ; %bb.0:
+; GFX10-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; GFX10-NEXT:    v_max3_i32 v0, v0, v1, 7
+; GFX10-NEXT:    s_setpc_b64 s[30:31]
+  %min1 = call i32 @llvm.smax.i32(i32 %a, i32 %b)
+  %min2 = call i32 @llvm.smax.i32(i32 %min1, i32 7)
+  ret i32 %min2
+}
+
+define i32 @test_smin3_smax_umax(i32 %a, i32 %b) {
+; GFX10-LABEL: test_smin3_smax_umax:
+; GFX10:       ; %bb.0:
+; GFX10-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; GFX10-NEXT:    v_max_i32_e32 v0, v0, v1
+; GFX10-NEXT:    v_max_u32_e32 v0, 7, v0
+; GFX10-NEXT:    s_setpc_b64 s[30:31]
+  %min1 = call i32 @llvm.smax.i32(i32 %a, i32 %b)
+  %min2 = call i32 @llvm.umax.i32(i32 %min1, i32 7)
+  ret i32 %min2
+}
+
+define <2 x i16> @test_smax3_v2i16(<2 x i16> %a, <2 x i16> %b, <2 x i16> %c) {
+; GFX10-LABEL: test_smax3_v2i16:
+; GFX10:       ; %bb.0:
+; GFX10-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; GFX10-NEXT:    v_pk_max_i16 v0, v0, v1
+; GFX10-NEXT:    v_pk_max_i16 v0, v0, v2
+; GFX10-NEXT:    s_setpc_b64 s[30:31]
+  %max1 = call <2 x i16> @llvm.smax.v2i16(<2 x i16> %a, <2 x i16> %b)
+  %max2 = call <2 x i16> @llvm.smax.v2i16(<2 x i16> %max1, <2 x i16> %c)
+  ret <2 x i16> %max2
+}
+
+define i32 @test_umin3(i32 %a, i32 %b, i32 %c) {
+; GFX10-LABEL: test_umin3:
+; GFX10:       ; %bb.0:
+; GFX10-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; GFX10-NEXT:    v_min3_u32 v0, v0, v1, v2
+; GFX10-NEXT:    s_setpc_b64 s[30:31]
+  %min1 = call i32 @llvm.umin.i32(i32 %a, i32 %b)
+  %min2 = call i32 @llvm.umin.i32(i32 %min1, i32 %c)
+  ret i32 %min2
+}
+
+define i32 @test_umin3_with_constants(i32 %a, i32 %b) {
+; GFX10-LABEL: test_umin3_with_constants:
+; GFX10:       ; %bb.0:
+; GFX10-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; GFX10-NEXT:    v_min3_u32 v0, v0, v1, 7
+; GFX10-NEXT:    s_setpc_b64 s[30:31]
+  %min1 = call i32 @llvm.umin.i32(i32 %a, i32 %b)
+  %min2 = call i32 @llvm.umin.i32(i32 %min1, i32 7)
+  ret i32 %min2
+}
+
+define <2 x i16> @test_umin3_v2i16(<2 x i16> %a, <2 x i16> %b, <2 x i16> %c) {
+; GFX10-LABEL: test_umin3_v2i16:
+; GFX10:       ; %bb.0:
+; GFX10-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; GFX10-NEXT:    v_pk_min_u16 v0, v0, v1
+; GFX10-NEXT:    v_pk_min_u16 v0, v0, v2
+; GFX10-NEXT:    s_setpc_b64 s[30:31]
+  %min1 = call <2 x i16> @llvm.umin.v2i16(<2 x i16> %a, <2 x i16> %b)
+  %min2 = call <2 x i16> @llvm.umin.v2i16(<2 x i16> %min1, <2 x i16> %c)
+  ret <2 x i16> %min2
+}
+
+define i32 @test_umax3(i32 %a, i32 %b, i32 %c) {
+; GFX10-LABEL: test_umax3:
+; GFX10:       ; %bb.0:
+; GFX10-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; GFX10-NEXT:    v_max3_u32 v0, v0, v1, v2
+; GFX10-NEXT:    s_setpc_b64 s[30:31]
+  %max1 = call i32 @llvm.umax.i32(i32 %a, i32 %b)
+  %max2 = call i32 @llvm.umax.i32(i32 %max1, i32 %c)
+  ret i32 %max2
+}
+
+define i32 @test_umax3_with_constants(i32 %a, i32 %b) {
+; GFX10-LABEL: test_umax3_with_constants:
+; GFX10:       ; %bb.0:
+; GFX10-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; GFX10-NEXT:    v_max3_u32 v0, v0, v1, 7
+; GFX10-NEXT:    s_setpc_b64 s[30:31]
+  %max1 = call i32 @llvm.umax.i32(i32 %a, i32 %b)
+  %max2 = call i32 @llvm.umax.i32(i32 %max1, i32 7)
+  ret i32 %max2
+}
+
+define <2 x i16> @test_umax3_v2i16(<2 x i16> %a, <2 x i16> %b, <2 x i16> %c) {
+; GFX10-LABEL: test_umax3_v2i16:
+; GFX10:       ; %bb.0:
+; GFX10-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; GFX10-NEXT:    v_pk_max_u16 v0, v0, v1
+; GFX10-NEXT:    v_pk_max_u16 v0, v0, v2
+; GFX10-NEXT:    s_setpc_b64 s[30:31]
+  %max1 = call <2 x i16> @llvm.umax.v2i16(<2 x i16> %a, <2 x i16> %b)
+  %max2 = call <2 x i16> @llvm.umax.v2i16(<2 x i16> %max1, <2 x i16> %c)
+  ret <2 x i16> %max2
+}
+
+declare i32 @llvm.smin.i32(i32, i32)
+declare i32 @llvm.umin.i32(i32, i32)
+declare i32 @llvm.smax.i32(i32, i32)
+declare i32 @llvm.umax.i32(i32, i32)
+declare <2 x i16> @llvm.smin.v2i16(<2 x i16>, <2 x i16>)
+declare <2 x i16> @llvm.umin.v2i16(<2 x i16>, <2 x i16>)
+declare <2 x i16> @llvm.smax.v2i16(<2 x i16>, <2 x i16>)
+declare <2 x i16> @llvm.umax.v2i16(<2 x i16>, <2 x i16>)
\ No newline at end of file

>From 6089c39eb06d7a392d31b564636f61df1617d2c1 Mon Sep 17 00:00:00 2001
From: Zile Xiong <xiongzile at bytedance.com>
Date: Mon, 1 Jun 2026 22:31:56 +0800
Subject: [PATCH 2/2] opt in regcombine

---
 llvm/lib/Target/AMDGPU/AMDGPUCombine.td       |  8 +--
 .../AMDGPU/AMDGPUPreLegalizerCombiner.cpp     | 57 -----------------
 .../Target/AMDGPU/AMDGPURegBankCombiner.cpp   | 62 +++++++++++++++++++
 .../AMDGPU/GlobalISel/fmin3-fmax3-combine.ll  |  7 +--
 4 files changed, 67 insertions(+), 67 deletions(-)

diff --git a/llvm/lib/Target/AMDGPU/AMDGPUCombine.td b/llvm/lib/Target/AMDGPU/AMDGPUCombine.td
index a20ed2a89029b..f9b0e0956b13f 100644
--- a/llvm/lib/Target/AMDGPU/AMDGPUCombine.td
+++ b/llvm/lib/Target/AMDGPU/AMDGPUCombine.td
@@ -236,9 +236,7 @@ def AMDGPUPreLegalizerCombiner: GICombiner<
   "AMDGPUPreLegalizerCombinerImpl",
   [all_combines, combine_fmul_with_select_to_fldexp, clamp_i64_to_i16,
    foldable_fneg, combine_shuffle_vector, combine_shuffle_vector_to_build_vector,
-   binop_s64_with_s32_mask_combines, combine_or_s64_s32, smax_to_minmax3, 
-   smin_to_minmax3, umax_to_minmax3, umin_to_minmax3, fmax_to_minmax3, 
-   fmin_to_minmax3, fmax_ieee_to_minmax3, fmin_ieee_to_minmax3]> {
+   binop_s64_with_s32_mask_combines, combine_or_s64_s32]> {
   let CombineAllMethodName = "tryCombineAllImpl";
 }
 
@@ -258,5 +256,7 @@ def AMDGPURegBankCombiner : GICombiner<
    fp_minmax_to_clamp, fp_minmax_to_med3, fmed3_intrinsic_to_clamp,
    identity_combines, redundant_and, constant_fold_cast_op,
    cast_of_cast_combines, sext_trunc, zext_of_shift_amount_combines,
-   d16_load]> {
+   d16_load, smax_to_minmax3, smin_to_minmax3, umax_to_minmax3, 
+   umin_to_minmax3, fmax_to_minmax3, fmin_to_minmax3, fmax_ieee_to_minmax3, 
+   fmin_ieee_to_minmax3]> {
 }
diff --git a/llvm/lib/Target/AMDGPU/AMDGPUPreLegalizerCombiner.cpp b/llvm/lib/Target/AMDGPU/AMDGPUPreLegalizerCombiner.cpp
index d1966893e18a2..8fd0af7fd3c72 100644
--- a/llvm/lib/Target/AMDGPU/AMDGPUPreLegalizerCombiner.cpp
+++ b/llvm/lib/Target/AMDGPU/AMDGPUPreLegalizerCombiner.cpp
@@ -67,11 +67,6 @@ class AMDGPUPreLegalizerCombinerImpl : public Combiner {
     Register Origin;
   };
 
-  struct MinMaxToMinMax3MatchInfo {
-    unsigned Opc;
-    Register Val0, Val1, Val2;
-  };
-
   bool matchClampI64ToI16(MachineInstr &MI, const MachineRegisterInfo &MRI,
                           const MachineFunction &MF,
                           ClampI64ToI16MatchInfo &MatchInfo) const;
@@ -79,9 +74,6 @@ class AMDGPUPreLegalizerCombinerImpl : public Combiner {
   void applyClampI64ToI16(MachineInstr &MI,
                           const ClampI64ToI16MatchInfo &MatchInfo) const;
 
-  bool matchMinMaxToMinMax3(MachineInstr &MI, MinMaxToMinMax3MatchInfo &MatchInfo) const;
-
-  void applyMinMaxToMinMax3(MachineInstr &MI, MinMaxToMinMax3MatchInfo &MatchInfo) const;
 
 private:
 #define GET_GICOMBINER_CLASS_MEMBERS
@@ -116,55 +108,6 @@ bool AMDGPUPreLegalizerCombinerImpl::tryCombineAll(MachineInstr &MI) const {
   return false;
 }
 
-void AMDGPUPreLegalizerCombinerImpl::applyMinMaxToMinMax3(
-    MachineInstr &MI, MinMaxToMinMax3MatchInfo &MatchInfo) const {
-  B.buildInstr(MatchInfo.Opc, {MI.getOperand(0)},
-               {MatchInfo.Val0, MatchInfo.Val1, MatchInfo.Val2}, MI.getFlags());
-  MI.eraseFromParent();
-  return;
-}
-
-bool AMDGPUPreLegalizerCombinerImpl::matchMinMaxToMinMax3(
-    MachineInstr &MI, MinMaxToMinMax3MatchInfo &MatchInfo) const {
-  Register dst = MI.getOperand(0).getReg();
-  LLT t = MRI.getType(dst);
-  if (t == LLT::scalar(16)) {
-    if (!STI.hasMin3Max3_16()) {
-      return false;
-    }
-  } else if (t != LLT::scalar(32)) {
-    if (!(t.isVector() && t.getScalarSizeInBits() == 32))
-      return false;
-  }
-
-  Register R0, R1, R2;
-  unsigned opc = MI.getOpcode();
-  auto matchVOP3 = [&](MachineInstr &MI, MachineRegisterInfo &MRI, unsigned op,
-                       Register &r0, Register &r1, Register &r2) {
-    auto p1 = m_BinOp(op, m_OneNonDBGUse(m_BinOp(op, m_Reg(r0), m_Reg(r1))),
-                      m_Reg(r2));
-    auto p2 = m_BinOp(op, m_Reg(r0),
-                      m_OneNonDBGUse(m_BinOp(op, m_Reg(r1), m_Reg(r2))));
-
-    return mi_match(MI, MRI, m_any_of(p1, p2));
-  };
-  if (!matchVOP3(MI, MRI, opc, R0, R1, R2)) {
-    return false;
-  }
-
-  llvm::SmallDenseMap<uint16_t, uint16_t, 8> mp = {
-      {AMDGPU::G_SMAX, AMDGPU::G_AMDGPU_SMAX3},
-      {AMDGPU::G_SMIN, AMDGPU::G_AMDGPU_SMIN3},
-      {AMDGPU::G_UMAX, AMDGPU::G_AMDGPU_UMAX3},
-      {AMDGPU::G_UMIN, AMDGPU::G_AMDGPU_UMIN3},
-      {AMDGPU::G_FMAXNUM, AMDGPU::G_AMDGPU_FMAX3},
-      {AMDGPU::G_FMAXNUM_IEEE, AMDGPU::G_AMDGPU_FMAX3},
-      {AMDGPU::G_FMINNUM, AMDGPU::G_AMDGPU_FMIN3},
-      {AMDGPU::G_FMINNUM_IEEE, AMDGPU::G_AMDGPU_FMIN3}};
-  MatchInfo = {mp.at(opc), R0, R1, R2};
-  return true;
-}
-
 bool AMDGPUPreLegalizerCombinerImpl::matchClampI64ToI16(
     MachineInstr &MI, const MachineRegisterInfo &MRI, const MachineFunction &MF,
     ClampI64ToI16MatchInfo &MatchInfo) const {
diff --git a/llvm/lib/Target/AMDGPU/AMDGPURegBankCombiner.cpp b/llvm/lib/Target/AMDGPU/AMDGPURegBankCombiner.cpp
index 35c0d4046f41e..291397965c62b 100644
--- a/llvm/lib/Target/AMDGPU/AMDGPURegBankCombiner.cpp
+++ b/llvm/lib/Target/AMDGPU/AMDGPURegBankCombiner.cpp
@@ -74,6 +74,11 @@ class AMDGPURegBankCombinerImpl : public Combiner {
     Register Val0, Val1, Val2;
   };
 
+  struct MinMaxToMinMax3MatchInfo {
+    unsigned Opc;
+    Register Val0, Val1, Val2;
+  };
+
   MinMaxMedOpc getMinMaxPair(unsigned Opc) const;
 
   template <class m_Cst, typename CstTy>
@@ -93,6 +98,9 @@ class AMDGPURegBankCombinerImpl : public Combiner {
   bool applyD16Load(unsigned D16Opc, MachineInstr &DstMI,
                     MachineInstr *SmallLoad, Register ToOverwriteD16) const;
 
+  bool matchMinMaxToMinMax3(MachineInstr &MI, MinMaxToMinMax3MatchInfo &MatchInfo) const;
+  void applyMinMaxToMinMax3(MachineInstr &MI, MinMaxToMinMax3MatchInfo &MatchInfo) const;
+
 private:
   SIModeRegisterDefaults getMode() const;
   bool getIEEE() const;
@@ -481,6 +489,60 @@ bool AMDGPURegBankCombinerImpl::combineD16Load(MachineInstr &MI) const {
   return false;
 }
 
+void AMDGPURegBankCombinerImpl::applyMinMaxToMinMax3(
+    MachineInstr &MI, MinMaxToMinMax3MatchInfo &MatchInfo) const {
+  B.buildInstr(MatchInfo.Opc, {MI.getOperand(0)},
+               {MatchInfo.Val0, MatchInfo.Val1, MatchInfo.Val2}, MI.getFlags());
+  MI.eraseFromParent();
+  return;
+}
+
+bool AMDGPURegBankCombinerImpl::matchMinMaxToMinMax3(
+    MachineInstr &MI, MinMaxToMinMax3MatchInfo &MatchInfo) const {
+  Register dst = MI.getOperand(0).getReg();
+  // if the register is SGPR, don't optimize it.
+  if (!isVgprRegBank(dst)) {
+    return false;
+  }
+
+  LLT t = MRI.getType(dst);
+  if (t == LLT::scalar(16)) {
+    if (!STI.hasMin3Max3_16()) {
+      return false;
+    }
+  } else if (t != LLT::scalar(32)) {
+    if (!(t.isVector() && t.getScalarSizeInBits() == 32))
+      return false;
+  }
+
+  Register R0, R1, R2;
+  unsigned opc = MI.getOpcode();
+  auto matchMinOrMax3 = [&](MachineInstr &MI, MachineRegisterInfo &MRI, unsigned op,
+                       Register &r0, Register &r1, Register &r2) {
+    auto p1 = m_BinOp(op, m_OneNonDBGUse(m_BinOp(op, m_Reg(r0), m_Reg(r1))),
+                      m_Reg(r2));
+    auto p2 = m_BinOp(op, m_Reg(r0),
+                      m_OneNonDBGUse(m_BinOp(op, m_Reg(r1), m_Reg(r2))));
+
+    return mi_match(MI, MRI, m_any_of(p1, p2));
+  };
+  if (!matchMinOrMax3(MI, MRI, opc, R0, R1, R2)) {
+    return false;
+  }
+
+  llvm::SmallDenseMap<uint16_t, uint16_t, 8> mp = {
+      {AMDGPU::G_SMAX, AMDGPU::G_AMDGPU_SMAX3},
+      {AMDGPU::G_SMIN, AMDGPU::G_AMDGPU_SMIN3},
+      {AMDGPU::G_UMAX, AMDGPU::G_AMDGPU_UMAX3},
+      {AMDGPU::G_UMIN, AMDGPU::G_AMDGPU_UMIN3},
+      {AMDGPU::G_FMAXNUM, AMDGPU::G_AMDGPU_FMAX3},
+      {AMDGPU::G_FMAXNUM_IEEE, AMDGPU::G_AMDGPU_FMAX3},
+      {AMDGPU::G_FMINNUM, AMDGPU::G_AMDGPU_FMIN3},
+      {AMDGPU::G_FMINNUM_IEEE, AMDGPU::G_AMDGPU_FMIN3}};
+  MatchInfo = {mp.at(opc), R0, R1, R2};
+  return true;
+}
+
 bool AMDGPURegBankCombinerImpl::applyD16Load(
     unsigned D16Opc, MachineInstr &DstMI, MachineInstr *SmallLoad,
     Register SrcReg32ToOverwriteD16) const {
diff --git a/llvm/test/CodeGen/AMDGPU/GlobalISel/fmin3-fmax3-combine.ll b/llvm/test/CodeGen/AMDGPU/GlobalISel/fmin3-fmax3-combine.ll
index 0a6a63e5289c1..34d5ad69843bb 100644
--- a/llvm/test/CodeGen/AMDGPU/GlobalISel/fmin3-fmax3-combine.ll
+++ b/llvm/test/CodeGen/AMDGPU/GlobalISel/fmin3-fmax3-combine.ll
@@ -1,12 +1,7 @@
 ; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
 ; RUN: llc -global-isel -mtriple=amdgcn-amd-mesa3d -mcpu=gfx1010 -verify-machineinstrs < %s | FileCheck -check-prefix=GFX10 %s
 
-define float @test_fmin3(float %a, float %b, float %c) {
-; GFX10-LABEL: test_fmin3:
-; GFX10:       ; %bb.0:
-; GFX10-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
-; GFX10-NEXT:    v_min3_f32 v0, v0, v1, v2
-; GFX10-NEXT:    s_setpc_b64 s[30:31]
+define float @test_fmin3(float inreg %a, float inreg %b, float inreg %c) {
   %min1 = call float @llvm.minnum.f32(float %a, float %b)
   %min2 = call float @llvm.minnum.f32(float %min1, float %c)
   ret float %min2



More information about the llvm-commits mailing list