This patch provides SSE implementations of __builtin_fmin and
__builtin_fmax.  The one tricky aspect is that x86's v{max,min}{sd,ss}
functions don't quite implement the IEEE specification for fmax and
fmin, for example, they aren't commutative in the presence of NaNs.
Hence a correct implementation requires an additional unordered
comparison to check if the second operand to min/max is NaN,
and if so use a vpblend to return the correct NaN.

Hence for the simple test case:

double foo(double x, double y)
{
  return __builtin_fmax (x,y);
}

with -O2 -mavx, we now generate:

foo:    vmaxsd  %xmm1, %xmm0, %xmm1
        vcmpunordsd     %xmm0, %xmm0, %xmm2
        vblendvpd       %xmm2, %xmm0, %xmm1, %xmm0

[which happens to match what clang/llvm generates].

This patch has been tested on x86_64-pc-linux-gnu with make bootstrap
and make -k check, both with and without --target_board=unix{-m32}
with no new failures.  Ok for mainline?


2026-10-08  Roger Sayle  <[email protected]>

gcc/ChangeLog
        * config/i386/i386.c (ix86_optab_supported_p): Add fmax_optab
        and fmin_optab cases to continue calling fmax/fmin with -Os.
        * config/i386/i386.md (maxmin_unspec_ieee): New code attribute
        to map smax and smin to the corresponding UNSPEC_IEEE_{MAX,MIN}.
        * config/i386/sse.md (f<maxmin_float><mode>3): New define_expand
        for fmax<mode>3 and fmin<mode3> using *ieee_{smax,smin}<mode>3.

gcc/testsuite/ChangeLog
        * gcc.target/i386/builtin-fmax-1.c: New test case.
        * gcc.target/i386/builtin-fmin-1.c: Likewise.


Thanks in advance,
Roger
--

diff --git a/gcc/config/i386/i386.cc b/gcc/config/i386/i386.cc
index b09a5077820..1c7ae7c3e70 100644
--- a/gcc/config/i386/i386.cc
+++ b/gcc/config/i386/i386.cc
@@ -28130,6 +28130,8 @@ ix86_optab_supported_p (int op, machine_mode mode1, 
machine_mode,
     case exp10_optab:
     case exp2_optab:
     case expm1_optab:
+    case fmax_optab:
+    case fmin_optab:
     case ldexp_optab:
     case scalb_optab:
     case lround_optab:
diff --git a/gcc/config/i386/i386.md b/gcc/config/i386/i386.md
index 0d4749e6e09..0a018ed5d3b 100644
--- a/gcc/config/i386/i386.md
+++ b/gcc/config/i386/i386.md
@@ -1080,6 +1080,8 @@
 (define_code_attr maxmin_int [(smax "maxs") (smin "mins")
                              (umax "maxu") (umin "minu")])
 (define_code_attr maxmin_float [(smax "max") (smin "min")])
+(define_code_attr maxmin_unspec_ieee [(smax "UNSPEC_IEEE_MAX")
+                                     (smin "UNSPEC_IEEE_MIN")])
 
 (define_int_iterator IEEE_MAXMIN
        [UNSPEC_IEEE_MAX
diff --git a/gcc/config/i386/sse.md b/gcc/config/i386/sse.md
index 0ce82789dc6..566b6c1e893 100644
--- a/gcc/config/i386/sse.md
+++ b/gcc/config/i386/sse.md
@@ -3635,6 +3635,36 @@
    (set_attr "prefix" "<round_saeonly_scalar_prefix>")
    (set_attr "mode" "<ssescalarmode>")])
 
+;; Expander for __builtin_fmax and __builtin_fmin
+(define_expand "f<maxmin_float><mode>3"
+  [(set (match_operand:MODEF 0 "register_operand")
+        (smaxmin:MODEF
+          (match_operand:MODEF 1 "register_operand")
+          (match_operand:MODEF 2 "nonimmediate_operand")))]
+  "TARGET_SSE4_1
+   && TARGET_SSE_MATH
+   && SSE_FLOAT_MODE_P (<MODE>mode)"
+{
+  /* Expand *ieee_<code><mode>3 to preserve operand order.  */
+  rtx result = gen_reg_rtx (<MODE>mode);
+  rtx unspec = gen_rtx_UNSPEC (<MODE>mode,
+                              gen_rtvec (2, operands[1], operands[2]),
+                              <maxmin_unspec_ieee>);
+  emit_insn (gen_rtx_SET (result, unspec));
+
+  /* Expand gen_<sse>_vmmaskcmp<ssevecmodelower>.  */
+  rtx vmask = gen_reg_rtx (<ssevecmode>mode);
+  rtx vop1 = lowpart_subreg (<ssevecmode>mode, operands[1], <MODE>mode);
+  rtx cmp = gen_rtx_UNORDERED (<ssevecmode>mode, vop1, vop1);
+  rtx merge = gen_rtx_VEC_MERGE (<ssevecmode>mode, cmp, vop1, const1_rtx);
+  emit_insn (gen_rtx_SET (vmask, merge));
+
+  rtx smask = lowpart_subreg (<MODE>mode, vmask, <ssevecmode>mode);
+  emit_insn (gen_sse4_1_blendv<ssemodesuffix> (operands[0], result,
+                                              operands[1], smask));
+  DONE;
+})
+
 (define_mode_attr addsub_cst [(V4DF "5") (V2DF "1")
                               (V4SF "5") (V8SF "85")])
 
diff --git a/gcc/testsuite/gcc.target/i386/builtin-fmax-1.c 
b/gcc/testsuite/gcc.target/i386/builtin-fmax-1.c
new file mode 100644
index 00000000000..b7b92dc4fe6
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/builtin-fmax-1.c
@@ -0,0 +1,19 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -mavx -mfpmath=sse" } */
+
+double foo(double x, double y)
+{
+  return __builtin_fmax (x,y);
+}
+
+float bar(float x, float y)
+{
+  return __builtin_fmaxf (x,y);
+}
+
+/* { dg-final { scan-assembler-times "vblendvpd" 1 } } */
+/* { dg-final { scan-assembler-times "vblendvps" 1 } } */
+/* { dg-final { scan-assembler-times "vcmpunordsd" 1 } } */
+/* { dg-final { scan-assembler-times "vcmpunordss" 1 } } */
+/* { dg-final { scan-assembler-times "vmaxsd" 1 } } */
+/* { dg-final { scan-assembler-times "vmaxss" 1 } } */
diff --git a/gcc/testsuite/gcc.target/i386/builtin-fmin-1.c 
b/gcc/testsuite/gcc.target/i386/builtin-fmin-1.c
new file mode 100644
index 00000000000..fcc30de04a6
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/builtin-fmin-1.c
@@ -0,0 +1,19 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -mavx -mfpmath=sse" } */
+
+double foo(double x, double y)
+{
+  return __builtin_fmin (x,y);
+}
+
+float bar(float x, float y)
+{
+  return __builtin_fminf (x,y);
+}
+
+/* { dg-final { scan-assembler-times "vblendvpd" 1 } } */
+/* { dg-final { scan-assembler-times "vblendvps" 1 } } */
+/* { dg-final { scan-assembler-times "vcmpunordsd" 1 } } */
+/* { dg-final { scan-assembler-times "vcmpunordss" 1 } } */
+/* { dg-final { scan-assembler-times "vminsd" 1 } } */
+/* { dg-final { scan-assembler-times "vminss" 1 } } */

Reply via email to