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 } } */