The AArch64 FEAT_FAMINMAX extension is optional from Armv9.2-a and
mandatory from Armv9.5-a. It introduces instructions for computing the
floating point absolute maximum and minimum of the two vectors element-wise.
This patch does three things:
1. Introduces AdvSIMD faminmax intrinsics.
2. Adds code generation support for famax and famin in terms of the
existing operators.
3. Move report_missing_extension and reported_missing_extension_p to
make it more usable.
The intrinsics of this extension are implemented as the following
builtin functions:
* vamax_f16
* vamaxq_f16
* vamax_f32
* vamaxq_f32
* vamaxq_f64
* vamin_f16
* vaminq_f16
* vamin_f32
* vaminq_f32
* vaminq_f64
For code generation, famax/famin is equivalent to first taking fabs of
the operands and then taking fmax/fmin of the results of fabs.
famax/famin (a, b) = fmax/fmin (fabs (a), fabs (b))
This is correct because NaN/Inf handling of famax/famin and fmax/fmin
are same. We cannot use fmaxnm/fminnm here as Nan/Inf are handled
differently in them.
We moved the definition of `report_missing_extension` from
gcc/config/aarch64/aarch64-sve-builtins.cc to
gcc/config/aarch64/aarch64-builtins.cc and its declaration to
gcc/config/aarch64/aarch64-builtins.h. We also moved the declaration
of `reported_missing_extension_p` from
gcc/config/aarch64/aarch64-sve-builtins.cc
to gcc/config/aarch64/aarch64-builtins.cc, closer to the definition of
`report_missing_extension`. In the exsiting code structure, this leads
to `report_missing_extension` being usable from both normal builtins
and sve builtins.
gcc/ChangeLog:
* config/aarch64/aarch64-builtins.cc
(enum aarch64_builtins): New enum values for faminmax builtins.
(aarch64_init_faminmax_builtins): New function to declare new builtins.
(handle_arm_neon_h): Modify to call aarch64_init_faminmax_builtins.
(aarch64_general_check_builtin_call): Modify to check whether +faminmax
flag is being used and printing error message if not being used.
(aarch64_expand_builtin_faminmax): New function to emit instructions of
this extension.
(aarch64_general_expand_builtin): Modify to call
aarch64_expand_builtin_faminmax.
(report_missing_extension): Move from
config/aarch64/aarch64-sve-builtins.cc.
* config/aarch64/aarch64-builtins.h
(report_missing_extension): Declaration for this function so
that it can be used wherever this header is included.
(reported_missing_extension_p): Move from
config/aarch64/aarch64-sve-builtins.cc
* config/aarch64/aarch64-option-extensions.def
(AARCH64_OPT_EXTENSION): Introduce new flag for this extension.
* config/aarch64/aarch64-simd.md
(aarch64_<faminmax><mode>): Introduce instruction pattern for this
extension.
* config/aarch64/aarch64-sve-builtins.cc
(reported_missing_extension_p): Move to
config/aarch64/aarch64-builtins.cc
(report_missing_extension): Move to
config/aarch64/aarch64-builtins.cc.
* config/aarch64/aarch64.h
(TARGET_FAMINMAX): Introduce new flag for this extension.
* config/aarch64/iterators.md: Introduce new iterators for this
extension.
* config/arm/types.md: Introduce neon_fp_aminmax<q> attributes.
* doc/invoke.texi: Document extension in AArch64 Options.
gcc/testsuite/ChangeLog:
* gcc.target/aarch64/simd/faminmax-builtins-no-flag.c: New test.
* gcc.target/aarch64/simd/faminmax-builtins.c: New test.
* gcc.target/aarch64/simd/faminmax-codegen-no-flag.c: New test.
* gcc.target/aarch64/simd/faminmax-codegen.c: New test.
---
Hi,
Regression tested for aarch64-none-linux-gnu and found no regressions.
This is a respin of
https://gcc.gnu.org/pipermail/gcc-patches/2024-August/658968.html
as the previous version failed patchwork CI due to not being able to
apply.
Ok for master? I don't have commit access so can someone please commit
on my behalf?
Regards,
Saurabh
---
gcc/config/aarch64/aarch64-builtins.cc | 173 +++++++++++++++++-
gcc/config/aarch64/aarch64-builtins.h | 5 +-
.../aarch64/aarch64-option-extensions.def | 2 +
gcc/config/aarch64/aarch64-simd.md | 12 ++
gcc/config/aarch64/aarch64-sve-builtins.cc | 22 ---
gcc/config/aarch64/aarch64.h | 4 +
gcc/config/aarch64/iterators.md | 8 +
gcc/config/arm/types.md | 6 +
gcc/doc/invoke.texi | 2 +
.../aarch64/simd/faminmax-builtins-no-flag.c | 10 +
.../aarch64/simd/faminmax-builtins.c | 75 ++++++++
.../aarch64/simd/faminmax-codegen-no-flag.c | 54 ++++++
.../aarch64/simd/faminmax-codegen.c | 104 +++++++++++
13 files changed, 445 insertions(+), 32 deletions(-)
create mode 100644
gcc/testsuite/gcc.target/aarch64/simd/faminmax-builtins-no-flag.c
create mode 100644 gcc/testsuite/gcc.target/aarch64/simd/faminmax-builtins.c
create mode 100644
gcc/testsuite/gcc.target/aarch64/simd/faminmax-codegen-no-flag.c
create mode 100644 gcc/testsuite/gcc.target/aarch64/simd/faminmax-codegen.c
diff --git a/gcc/config/aarch64/aarch64-builtins.cc b/gcc/config/aarch64/aarch64-builtins.cc
index 30669f8aa18..cd590186f22 100644
--- a/gcc/config/aarch64/aarch64-builtins.cc
+++ b/gcc/config/aarch64/aarch64-builtins.cc
@@ -829,6 +829,17 @@ enum aarch64_builtins
AARCH64_RBIT,
AARCH64_RBITL,
AARCH64_RBITLL,
+ /* FAMINMAX builtins. */
+ AARCH64_FAMINMAX_BUILTIN_FAMAX4H,
+ AARCH64_FAMINMAX_BUILTIN_FAMAX8H,
+ AARCH64_FAMINMAX_BUILTIN_FAMAX2S,
+ AARCH64_FAMINMAX_BUILTIN_FAMAX4S,
+ AARCH64_FAMINMAX_BUILTIN_FAMAX2D,
+ AARCH64_FAMINMAX_BUILTIN_FAMIN4H,
+ AARCH64_FAMINMAX_BUILTIN_FAMIN8H,
+ AARCH64_FAMINMAX_BUILTIN_FAMIN2S,
+ AARCH64_FAMINMAX_BUILTIN_FAMIN4S,
+ AARCH64_FAMINMAX_BUILTIN_FAMIN2D,
/* System register builtins. */
AARCH64_RSR,
AARCH64_RSRP,
@@ -1547,6 +1558,66 @@ aarch64_init_simd_builtin_functions (bool called_from_pragma)
}
}
+/* Initialize the absolute maximum/minimum (FAMINMAX) builtins. */
+
+typedef struct
+{
+ const char *name;
+ unsigned int code;
+ tree eltype;
+ machine_mode mode;
+} faminmax_builtins_data;
+
+static void
+aarch64_init_faminmax_builtins ()
+{
+ faminmax_builtins_data data[] = {
+ /* Absolute maximum. */
+ {"vamax_f16", AARCH64_FAMINMAX_BUILTIN_FAMAX4H,
+ aarch64_simd_types[Float16x4_t].eltype,
+ aarch64_simd_types[Float16x4_t].mode},
+ {"vamaxq_f16", AARCH64_FAMINMAX_BUILTIN_FAMAX8H,
+ aarch64_simd_types[Float16x8_t].eltype,
+ aarch64_simd_types[Float16x8_t].mode},
+ {"vamax_f32", AARCH64_FAMINMAX_BUILTIN_FAMAX2S,
+ aarch64_simd_types[Float32x2_t].eltype,
+ aarch64_simd_types[Float32x2_t].mode},
+ {"vamaxq_f32", AARCH64_FAMINMAX_BUILTIN_FAMAX4S,
+ aarch64_simd_types[Float32x4_t].eltype,
+ aarch64_simd_types[Float32x4_t].mode},
+ {"vamaxq_f64", AARCH64_FAMINMAX_BUILTIN_FAMAX2D,
+ aarch64_simd_types[Float64x2_t].eltype,
+ aarch64_simd_types[Float64x2_t].mode},
+ /* Absolute minimum. */
+ {"vamin_f16", AARCH64_FAMINMAX_BUILTIN_FAMIN4H,
+ aarch64_simd_types[Float16x4_t].eltype,
+ aarch64_simd_types[Float16x4_t].mode},
+ {"vaminq_f16", AARCH64_FAMINMAX_BUILTIN_FAMIN8H,
+ aarch64_simd_types[Float16x8_t].eltype,
+ aarch64_simd_types[Float16x8_t].mode},
+ {"vamin_f32", AARCH64_FAMINMAX_BUILTIN_FAMIN2S,
+ aarch64_simd_types[Float32x2_t].eltype,
+ aarch64_simd_types[Float32x2_t].mode},
+ {"vaminq_f32", AARCH64_FAMINMAX_BUILTIN_FAMIN4S,
+ aarch64_simd_types[Float32x4_t].eltype,
+ aarch64_simd_types[Float32x4_t].mode},
+ {"vaminq_f64", AARCH64_FAMINMAX_BUILTIN_FAMIN2D,
+ aarch64_simd_types[Float64x2_t].eltype,
+ aarch64_simd_types[Float64x2_t].mode},
+ };
+
+ for (size_t i = 0; i < ARRAY_SIZE (data); ++i)
+ {
+ tree type
+ = build_vector_type (data[i].eltype, GET_MODE_NUNITS (data[i].mode));
+ tree fntype = build_function_type_list (type, type, type, NULL_TREE);
+ unsigned int code = data[i].code;
+ const char *name = data[i].name;
+ aarch64_builtin_decls[code]
+ = aarch64_general_simulate_builtin (name, fntype, code);
+ }
+}
+
/* Register the tuple type that contains NUM_VECTORS of the AdvSIMD type
indexed by TYPE_INDEX. */
static void
@@ -1640,6 +1711,7 @@ handle_arm_neon_h (void)
aarch64_init_simd_builtin_functions (true);
aarch64_init_simd_intrinsics ();
+ aarch64_init_faminmax_builtins ();
}
static void
@@ -2197,15 +2269,35 @@ aarch64_general_check_builtin_call (location_t location, vec<location_t>,
case AARCH64_WSR64:
case AARCH64_WSRF:
case AARCH64_WSRF64:
- tree addr = STRIP_NOPS (args[0]);
- if (TREE_CODE (TREE_TYPE (addr)) != POINTER_TYPE
- || TREE_CODE (addr) != ADDR_EXPR
- || TREE_CODE (TREE_OPERAND (addr, 0)) != STRING_CST)
- {
- error_at (location, "first argument to %qD must be a string literal",
- fndecl);
- return false;
- }
+ {
+ tree addr = STRIP_NOPS (args[0]);
+ if (TREE_CODE (TREE_TYPE (addr)) != POINTER_TYPE
+ || TREE_CODE (addr) != ADDR_EXPR
+ || TREE_CODE (TREE_OPERAND (addr, 0)) != STRING_CST)
+ {
+ error_at (location,
+ "first argument to %qD must be a string literal",
+ fndecl);
+ return false;
+ }
+ }
+ case AARCH64_FAMINMAX_BUILTIN_FAMAX4H:
+ case AARCH64_FAMINMAX_BUILTIN_FAMAX8H:
+ case AARCH64_FAMINMAX_BUILTIN_FAMAX2S:
+ case AARCH64_FAMINMAX_BUILTIN_FAMAX4S:
+ case AARCH64_FAMINMAX_BUILTIN_FAMAX2D:
+ case AARCH64_FAMINMAX_BUILTIN_FAMIN4H:
+ case AARCH64_FAMINMAX_BUILTIN_FAMIN8H:
+ case AARCH64_FAMINMAX_BUILTIN_FAMIN2S:
+ case AARCH64_FAMINMAX_BUILTIN_FAMIN4S:
+ case AARCH64_FAMINMAX_BUILTIN_FAMIN2D:
+ {
+ if (!TARGET_FAMINMAX)
+ {
+ report_missing_extension (location, fndecl, "faminmax");
+ return false;
+ }
+ }
}
/* Default behavior. */
return true;
@@ -3071,6 +3163,44 @@ aarch64_expand_builtin_data_intrinsic (unsigned int fcode, tree exp, rtx target)
return ops[0].value;
}
+static rtx
+aarch64_expand_builtin_faminmax (unsigned int fcode, tree exp, rtx target)
+{
+ machine_mode mode = TYPE_MODE (TREE_TYPE (exp));
+ rtx op0 = force_reg (mode, expand_normal (CALL_EXPR_ARG (exp, 0)));
+ rtx op1 = force_reg (mode, expand_normal (CALL_EXPR_ARG (exp, 1)));
+
+ enum insn_code icode;
+ if (fcode == AARCH64_FAMINMAX_BUILTIN_FAMAX4H)
+ icode = CODE_FOR_aarch64_famaxv4hf;
+ else if (fcode == AARCH64_FAMINMAX_BUILTIN_FAMAX8H)
+ icode = CODE_FOR_aarch64_famaxv8hf;
+ else if (fcode == AARCH64_FAMINMAX_BUILTIN_FAMAX2S)
+ icode = CODE_FOR_aarch64_famaxv2sf;
+ else if (fcode == AARCH64_FAMINMAX_BUILTIN_FAMAX4S)
+ icode = CODE_FOR_aarch64_famaxv4sf;
+ else if (fcode == AARCH64_FAMINMAX_BUILTIN_FAMAX2D)
+ icode = CODE_FOR_aarch64_famaxv2df;
+ else if (fcode == AARCH64_FAMINMAX_BUILTIN_FAMIN4H)
+ icode = CODE_FOR_aarch64_faminv4hf;
+ else if (fcode == AARCH64_FAMINMAX_BUILTIN_FAMIN8H)
+ icode = CODE_FOR_aarch64_faminv8hf;
+ else if (fcode == AARCH64_FAMINMAX_BUILTIN_FAMIN2S)
+ icode = CODE_FOR_aarch64_faminv2sf;
+ else if (fcode == AARCH64_FAMINMAX_BUILTIN_FAMIN4S)
+ icode = CODE_FOR_aarch64_faminv4sf;
+ else if (fcode == AARCH64_FAMINMAX_BUILTIN_FAMIN2D)
+ icode = CODE_FOR_aarch64_faminv2df;
+ else
+ gcc_unreachable ();
+
+ rtx pat = GEN_FCN (icode) (target, op0, op1);
+
+ emit_insn (pat);
+
+ return target;
+}
+
/* Expand an expression EXP as fpsr or fpcr setter (depending on
UNSPEC) using MODE. */
static void
@@ -3250,6 +3380,9 @@ aarch64_general_expand_builtin (unsigned int fcode, tree exp, rtx target,
if (fcode >= AARCH64_REV16
&& fcode <= AARCH64_RBITLL)
return aarch64_expand_builtin_data_intrinsic (fcode, exp, target);
+ if (fcode >= AARCH64_FAMINMAX_BUILTIN_FAMAX4H
+ && fcode <= AARCH64_FAMINMAX_BUILTIN_FAMIN2D)
+ return aarch64_expand_builtin_faminmax (fcode, exp, target);
gcc_unreachable ();
}
@@ -3794,6 +3927,28 @@ aarch64_atomic_assign_expand_fenv (tree *hold, tree *clear, tree *update)
reload_fenv, restore_fnenv), update_call);
}
+/* True if we've already complained about attempts to use functions
+ when the required extension is disabled. */
+static bool reported_missing_extension_p;
+
+/* Report an error against LOCATION that the user has tried to use
+ function FNDECL when extension EXTENSION is disabled. */
+void
+report_missing_extension (location_t location, tree fndecl,
+ const char *extension)
+{
+ /* Avoid reporting a slew of messages for a single oversight. */
+ if (reported_missing_extension_p)
+ return;
+
+ error_at (location, "ACLE function %qD requires ISA extension %qs",
+ fndecl, extension);
+ inform (location, "you can enable %qs using the command-line"
+ " option %<-march%>, or by using the %<target%>"
+ " attribute or pragma", extension);
+ reported_missing_extension_p = true;
+}
+
/* Resolve overloaded MEMTAG build-in functions. */
#define AARCH64_BUILTIN_SUBCODE(F) \
(DECL_MD_FUNCTION_CODE (F) >> AARCH64_BUILTIN_SHIFT)
diff --git a/gcc/config/aarch64/aarch64-builtins.h b/gcc/config/aarch64/aarch64-builtins.h
index e326fe66676..93e31a30ec6 100644
--- a/gcc/config/aarch64/aarch64-builtins.h
+++ b/gcc/config/aarch64/aarch64-builtins.h
@@ -96,4 +96,7 @@ struct GTY(()) aarch64_simd_type_info
extern aarch64_simd_type_info aarch64_simd_types[];
-#endif
\ No newline at end of file
+void report_missing_extension (location_t location, tree fndecl,
+ const char *extension);
+
+#endif
diff --git a/gcc/config/aarch64/aarch64-option-extensions.def b/gcc/config/aarch64/aarch64-option-extensions.def
index 6998627f377..8279f5a76ea 100644
--- a/gcc/config/aarch64/aarch64-option-extensions.def
+++ b/gcc/config/aarch64/aarch64-option-extensions.def
@@ -234,6 +234,8 @@ AARCH64_OPT_EXTENSION("gcs", GCS, (), (), (), "gcs")
AARCH64_OPT_EXTENSION("fp8", FP8, (SIMD), (), (), "fp8")
+AARCH64_OPT_EXTENSION("faminmax", FAMINMAX, (SIMD), (), (), "faminmax")
+
#undef AARCH64_OPT_FMV_EXTENSION
#undef AARCH64_OPT_EXTENSION
#undef AARCH64_FMV_FEATURE
diff --git a/gcc/config/aarch64/aarch64-simd.md b/gcc/config/aarch64/aarch64-simd.md
index 459e11b09a1..b87bac2e7ed 100644
--- a/gcc/config/aarch64/aarch64-simd.md
+++ b/gcc/config/aarch64/aarch64-simd.md
@@ -9881,3 +9881,15 @@
"shl\\t%d0, %d1, #16"
[(set_attr "type" "neon_shift_imm")]
)
+
+;; faminmax
+(define_insn "aarch64_<faminmax><mode>"
+ [(set (match_operand:VHSDF 0 "register_operand" "=w")
+ (unspec:VHSDF
+ [(abs:VHSDF (match_operand:VHSDF 1 "register_operand" "w"))
+ (abs:VHSDF (match_operand:VHSDF 2 "register_operand" "w"))]
+ FMAXMIN_ONLY_UNS))]
+ "TARGET_FAMINMAX"
+ "<faminmax>\t%0.<Vtype>, %1.<Vtype>, %2.<Vtype>"
+ [(set_attr "type" "neon_fp_aminmax<q>")]
+)
diff --git a/gcc/config/aarch64/aarch64-sve-builtins.cc b/gcc/config/aarch64/aarch64-sve-builtins.cc
index 0a560eaedca..f9120575671 100644
--- a/gcc/config/aarch64/aarch64-sve-builtins.cc
+++ b/gcc/config/aarch64/aarch64-sve-builtins.cc
@@ -947,10 +947,6 @@ static hash_table<registered_function_hasher> *function_table;
are IDENTIFIER_NODEs. */
static GTY(()) hash_map<tree, registered_function *> *overload_names[2];
-/* True if we've already complained about attempts to use functions
- when the required extension is disabled. */
-static bool reported_missing_extension_p;
-
/* True if we've already complained about attempts to use functions
which require registers that are missing. */
static bool reported_missing_registers_p;
@@ -1076,24 +1072,6 @@ lookup_fndecl (tree fndecl)
return &(*registered_functions)[subcode]->instance;
}
-/* Report an error against LOCATION that the user has tried to use
- function FNDECL when extension EXTENSION is disabled. */
-static void
-report_missing_extension (location_t location, tree fndecl,
- const char *extension)
-{
- /* Avoid reporting a slew of messages for a single oversight. */
- if (reported_missing_extension_p)
- return;
-
- error_at (location, "ACLE function %qD requires ISA extension %qs",
- fndecl, extension);
- inform (location, "you can enable %qs using the command-line"
- " option %<-march%>, or by using the %<target%>"
- " attribute or pragma", extension);
- reported_missing_extension_p = true;
-}
-
/* Check whether the registers required by SVE function fndecl are available.
Report an error against LOCATION and return false if not. */
static bool
diff --git a/gcc/config/aarch64/aarch64.h b/gcc/config/aarch64/aarch64.h
index 2dfb999bea5..de14f57071a 100644
--- a/gcc/config/aarch64/aarch64.h
+++ b/gcc/config/aarch64/aarch64.h
@@ -457,6 +457,10 @@ constexpr auto AARCH64_FL_DEFAULT_ISA_MODE ATTRIBUTE_UNUSED
enabled through +gcs. */
#define TARGET_GCS AARCH64_HAVE_ISA (GCS)
+/* Floating Point Absolute Maximum/Minimum extension instructions are
+ enabled through +faminmax. */
+#define TARGET_FAMINMAX AARCH64_HAVE_ISA (FAMINMAX)
+
/* Prefer different predicate registers for the output of a predicated
operation over re-using an existing input predicate. */
#define TARGET_SVE_PRED_CLOBBER (TARGET_SVE \
diff --git a/gcc/config/aarch64/iterators.md b/gcc/config/aarch64/iterators.md
index 95fe8f070f4..8e144c8ee4e 100644
--- a/gcc/config/aarch64/iterators.md
+++ b/gcc/config/aarch64/iterators.md
@@ -4457,3 +4457,11 @@
(UNSPECV_SET_FPCR "fpcr")])
(define_int_attr bits_etype [(8 "b") (16 "h") (32 "s") (64 "d")])
+
+;; Iterators and attributes for faminmax
+
+(define_int_iterator FMAXMIN_ONLY_UNS [UNSPEC_FMAX UNSPEC_FMIN])
+(define_int_attr faminmax
+ [(UNSPEC_FMAX "famax") (UNSPEC_FMIN "famin")])
+
+
diff --git a/gcc/config/arm/types.md b/gcc/config/arm/types.md
index 9527bdb9e87..d8de9dbc9d1 100644
--- a/gcc/config/arm/types.md
+++ b/gcc/config/arm/types.md
@@ -492,6 +492,8 @@
; neon_fp_reduc_minmax_s_q
; neon_fp_reduc_minmax_d
; neon_fp_reduc_minmax_d_q
+; neon_fp_aminmax
+; neon_fp_aminmax_q
; neon_fp_cvt_narrow_s_q
; neon_fp_cvt_narrow_d_q
; neon_fp_cvt_widen_h
@@ -1044,6 +1046,8 @@
neon_fp_reduc_minmax_d,\
neon_fp_reduc_minmax_d_q,\
\
+ neon_fp_aminmax,\
+ neon_fp_aminmax_q,\
neon_fp_cvt_narrow_s_q,\
neon_fp_cvt_narrow_d_q,\
neon_fp_cvt_widen_h,\
@@ -1264,6 +1268,8 @@
neon_fp_reduc_add_d_q, neon_fp_reduc_minmax_s,
neon_fp_reduc_minmax_s_q, neon_fp_reduc_minmax_d,\
neon_fp_reduc_minmax_d_q,\
+ neon_fp_aminmax, neon_fp_aminmax_q,\
+ neon_fp_aminmax, neon_fp_aminmax_q,\
neon_fp_cvt_narrow_s_q, neon_fp_cvt_narrow_d_q,\
neon_fp_cvt_widen_h, neon_fp_cvt_widen_s, neon_fp_to_int_s,\
neon_fp_to_int_s_q, neon_int_to_fp_s, neon_int_to_fp_s_q,\
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index ef2213b4e84..7b800b0bd79 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -21851,6 +21851,8 @@ Enable support for Armv8.9-a/9.4-a translation hardening extension.
Enable the RCpc3 (Release Consistency) extension.
@item fp8
Enable the fp8 (8-bit floating point) extension.
+@item faminmax
+Enable the Floating Point Absolute Maximum/Minimum extension.
@end table
diff --git a/gcc/testsuite/gcc.target/aarch64/simd/faminmax-builtins-no-flag.c b/gcc/testsuite/gcc.target/aarch64/simd/faminmax-builtins-no-flag.c
new file mode 100644
index 00000000000..63ed1508c23
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/simd/faminmax-builtins-no-flag.c
@@ -0,0 +1,10 @@
+/* { dg-do assemble} */
+/* { dg-additional-options "-march=armv9-a" } */
+
+#include "arm_neon.h"
+
+void
+test (float32x4_t a, float32x4_t b)
+{
+ vamaxq_f32 (a, b); /* { dg-error {ACLE function 'vamaxq_f32' requires ISA extension 'faminmax'} } */
+}
diff --git a/gcc/testsuite/gcc.target/aarch64/simd/faminmax-builtins.c b/gcc/testsuite/gcc.target/aarch64/simd/faminmax-builtins.c
new file mode 100644
index 00000000000..f2b5bafb81c
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/simd/faminmax-builtins.c
@@ -0,0 +1,75 @@
+/* { dg-do assemble} */
+/* { dg-additional-options "-march=armv9-a+faminmax" } */
+
+#include "arm_neon.h"
+
+float16x4_t
+test_vamax_f16 (float16x4_t a, float16x4_t b)
+{
+ return vamax_f16 (a, b);
+}
+
+float16x8_t
+test_vamaxq_f16 (float16x8_t a, float16x8_t b)
+{
+ return vamaxq_f16 (a, b);
+}
+
+float32x2_t
+test_vamax_f32 (float32x2_t a, float32x2_t b)
+{
+ return vamax_f32 (a, b);
+}
+
+float32x4_t
+test_vamaxq_f32 (float32x4_t a, float32x4_t b)
+{
+ return vamaxq_f32 (a, b);
+}
+
+float64x2_t
+test_vamaxq_f64 (float64x2_t a, float64x2_t b)
+{
+ return vamaxq_f64 (a, b);
+}
+
+float16x4_t
+test_vamin_f16 (float16x4_t a, float16x4_t b)
+{
+ return vamin_f16 (a, b);
+}
+
+float16x8_t
+test_vaminq_f16 (float16x8_t a, float16x8_t b)
+{
+ return vaminq_f16 (a, b);
+}
+
+float32x2_t
+test_vamin_f32 (float32x2_t a, float32x2_t b)
+{
+ return vamin_f32 (a, b);
+}
+
+float32x4_t
+test_vaminq_f32 (float32x4_t a, float32x4_t b)
+{
+ return vaminq_f32 (a, b);
+}
+
+float64x2_t
+test_vaminq_f64 (float64x2_t a, float64x2_t b)
+{
+ return vaminq_f64 (a, b);
+}
+
+/* { dg-final { scan-assembler-times {\tfamax\tv[0-9]+.4h, v[0-9]+.4h, v[0-9]+.4h} 1 } } */
+/* { dg-final { scan-assembler-times {\tfamax\tv[0-9]+.8h, v[0-9]+.8h, v[0-9]+.8h} 1 } } */
+/* { dg-final { scan-assembler-times {\tfamax\tv[0-9]+.2s, v[0-9]+.2s, v[0-9]+.2s} 1 } } */
+/* { dg-final { scan-assembler-times {\tfamax\tv[0-9]+.4s, v[0-9]+.4s, v[0-9]+.4s} 1 } } */
+/* { dg-final { scan-assembler-times {\tfamax\tv[0-9]+.2d, v[0-9]+.2d, v[0-9]+.2d} 1 } } */
+/* { dg-final { scan-assembler-times {\tfamin\tv[0-9]+.4h, v[0-9]+.4h, v[0-9]+.4h} 1 } } */
+/* { dg-final { scan-assembler-times {\tfamin\tv[0-9]+.8h, v[0-9]+.8h, v[0-9]+.8h} 1 } } */
+/* { dg-final { scan-assembler-times {\tfamin\tv[0-9]+.2s, v[0-9]+.2s, v[0-9]+.2s} 1 } } */
+/* { dg-final { scan-assembler-times {\tfamin\tv[0-9]+.4s, v[0-9]+.4s, v[0-9]+.4s} 1 } } */
+/* { dg-final { scan-assembler-times {\tfamin\tv[0-9]+.2d, v[0-9]+.2d, v[0-9]+.2d} 1 } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/simd/faminmax-codegen-no-flag.c b/gcc/testsuite/gcc.target/aarch64/simd/faminmax-codegen-no-flag.c
new file mode 100644
index 00000000000..545a9468fdc
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/simd/faminmax-codegen-no-flag.c
@@ -0,0 +1,54 @@
+/* { dg-do assemble} */
+/* { dg-additional-options "-O -march=armv9-a" } */
+
+#include "arm_neon.h"
+
+float16x4_t
+test_vamax_f16 (float16x4_t a, float16x4_t b)
+{
+ return vmax_f16 (vabs_f16 (a), vabs_f16 (b));
+}
+
+/* { dg-final { scan-assembler-times {\tfamax\tv[0-9]+.4h, v[0-9]+.4h, v[0-9]+.4h} 0 } } */
+/* { dg-final { scan-assembler-times {\tfmax\tv[0-9]+.4h, v[0-9]+.4h, v[0-9]+.4h} 1 } } */
+/* { dg-final { scan-assembler-times {\tfabs\tv[0-9]+.4h, v[0-9]+.4h} 2 } } */
+
+float16x8_t
+test_vamaxq_f16 (float16x8_t a, float16x8_t b)
+{
+ return vmaxq_f16 (vabsq_f16 (a), vabsq_f16 (b));
+}
+
+/* { dg-final { scan-assembler-times {\tfamax\tv[0-9]+.8h, v[0-9]+.8h, v[0-9]+.8h} 0 } } */
+/* { dg-final { scan-assembler-times {\tfmax\tv[0-9]+.8h, v[0-9]+.8h, v[0-9]+.8h} 1 } } */
+/* { dg-final { scan-assembler-times {\tfabs\tv[0-9]+.8h, v[0-9]+.8h} 2 } } */
+
+float32x2_t
+test_vamax_f32 (float32x2_t a, float32x2_t b)
+{
+ return vmax_f32 (vabs_f32 (a), vabs_f32 (b));
+}
+
+/* { dg-final { scan-assembler-times {\tfamax\tv[0-9]+.2s, v[0-9]+.2s, v[0-9]+.2s} 0 } } */
+/* { dg-final { scan-assembler-times {\tfmax\tv[0-9]+.2s, v[0-9]+.2s, v[0-9]+.2s} 1 } } */
+/* { dg-final { scan-assembler-times {\tfabs\tv[0-9]+.2s, v[0-9]+.2s} 2 } } */
+
+float32x4_t
+test_vamaxq_f32 (float32x4_t a, float32x4_t b)
+{
+ return vmaxq_f32 (vabsq_f32 (a), vabsq_f32 (b));
+}
+
+/* { dg-final { scan-assembler-times {\tfamax\tv[0-9]+.4s, v[0-9]+.4s, v[0-9]+.4s} 0 } } */
+/* { dg-final { scan-assembler-times {\tfmax\tv[0-9]+.4s, v[0-9]+.4s, v[0-9]+.4s} 1 } } */
+/* { dg-final { scan-assembler-times {\tfabs\tv[0-9]+.4s, v[0-9]+.4s} 2 } } */
+
+float64x2_t
+test_vamaxq_f64 (float64x2_t a, float64x2_t b)
+{
+ return vmaxq_f64 (vabsq_f64 (a), vabsq_f64 (b));
+}
+
+/* { dg-final { scan-assembler-times {\tfamax\tv[0-9]+.2d, v[0-9]+.2d, v[0-9]+.2d} 0 } } */
+/* { dg-final { scan-assembler-times {\tfmax\tv[0-9]+.2d, v[0-9]+.2d, v[0-9]+.2d} 1 } } */
+/* { dg-final { scan-assembler-times {\tfabs\tv[0-9]+.2d, v[0-9]+.2d} 2 } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/simd/faminmax-codegen.c b/gcc/testsuite/gcc.target/aarch64/simd/faminmax-codegen.c
new file mode 100644
index 00000000000..e4e079a6f9e
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/simd/faminmax-codegen.c
@@ -0,0 +1,104 @@
+/* { dg-do assemble} */
+/* { dg-additional-options "-O -march=armv9-a+faminmax" } */
+
+#include "arm_neon.h"
+
+float16x4_t
+test_vamax_f16 (float16x4_t a, float16x4_t b)
+{
+ return vmax_f16 (vabs_f16 (a), vabs_f16 (b));
+}
+
+/* { dg-final { scan-assembler-times {\tfamax\tv[0-9]+.4h, v[0-9]+.4h, v[0-9]+.4h} 1 } } */
+/* { dg-final { scan-assembler-times {\tfmax\tv[0-9]+.4h, v[0-9]+.4h, v[0-9]+.4h} 0 } } */
+/* { dg-final { scan-assembler-times {\tfabs\tv[0-9]+.4h, v[0-9]+.4h} 0 } } */
+
+float16x8_t
+test_vamaxq_f16 (float16x8_t a, float16x8_t b)
+{
+ return vmaxq_f16 (vabsq_f16 (a), vabsq_f16 (b));
+}
+
+/* { dg-final { scan-assembler-times {\tfamax\tv[0-9]+.8h, v[0-9]+.8h, v[0-9]+.8h} 1 } } */
+/* { dg-final { scan-assembler-times {\tfmax\tv[0-9]+.8h, v[0-9]+.8h, v[0-9]+.8h} 0 } } */
+/* { dg-final { scan-assembler-times {\tfabs\tv[0-9]+.8h, v[0-9]+.8h} 0 } } */
+
+float32x2_t
+test_vamax_f32 (float32x2_t a, float32x2_t b)
+{
+ return vmax_f32 (vabs_f32 (a), vabs_f32 (b));
+}
+
+/* { dg-final { scan-assembler-times {\tfamax\tv[0-9]+.2s, v[0-9]+.2s, v[0-9]+.2s} 1 } } */
+/* { dg-final { scan-assembler-times {\tfmax\tv[0-9]+.2s, v[0-9]+.2s, v[0-9]+.2s} 0 } } */
+/* { dg-final { scan-assembler-times {\tfabs\tv[0-9]+.2s, v[0-9]+.2s} 0 } } */
+
+float32x4_t
+test_vamaxq_f32 (float32x4_t a, float32x4_t b)
+{
+ return vmaxq_f32 (vabsq_f32 (a), vabsq_f32 (b));
+}
+
+/* { dg-final { scan-assembler-times {\tfamax\tv[0-9]+.4s, v[0-9]+.4s, v[0-9]+.4s} 1 } } */
+/* { dg-final { scan-assembler-times {\tfmax\tv[0-9]+.4s, v[0-9]+.4s, v[0-9]+.4s} 0 } } */
+/* { dg-final { scan-assembler-times {\tfabs\tv[0-9]+.4s, v[0-9]+.4s} 0 } } */
+
+float64x2_t
+test_vamaxq_f64 (float64x2_t a, float64x2_t b)
+{
+ return vmaxq_f64 (vabsq_f64 (a), vabsq_f64 (b));
+}
+
+/* { dg-final { scan-assembler-times {\tfamax\tv[0-9]+.2d, v[0-9]+.2d, v[0-9]+.2d} 1 } } */
+/* { dg-final { scan-assembler-times {\tfmax\tv[0-9]+.2d, v[0-9]+.2d, v[0-9]+.2d} 0 } } */
+/* { dg-final { scan-assembler-times {\tfabs\tv[0-9]+.2d, v[0-9]+.2d} 0 } } */
+
+float16x4_t
+test_vamin_f16 (float16x4_t a, float16x4_t b)
+{
+ return vmin_f16 (vabs_f16 (a), vabs_f16 (b));
+}
+
+/* { dg-final { scan-assembler-times {\tfamin\tv[0-9]+.4h, v[0-9]+.4h, v[0-9]+.4h} 1 } } */
+/* { dg-final { scan-assembler-times {\tfmin\tv[0-9]+.4h, v[0-9]+.4h, v[0-9]+.4h} 0 } } */
+/* { dg-final { scan-assembler-times {\tfabs\tv[0-9]+.4h, v[0-9]+.4h} 0 } } */
+
+float16x8_t
+test_vaminq_f16 (float16x8_t a, float16x8_t b)
+{
+ return vminq_f16 (vabsq_f16 (a), vabsq_f16 (b));
+}
+
+/* { dg-final { scan-assembler-times {\tfamin\tv[0-9]+.8h, v[0-9]+.8h, v[0-9]+.8h} 1 } } */
+/* { dg-final { scan-assembler-times {\tfmin\tv[0-9]+.8h, v[0-9]+.8h, v[0-9]+.8h} 0 } } */
+/* { dg-final { scan-assembler-times {\tfabs\tv[0-9]+.8h, v[0-9]+.8h} 0 } } */
+
+float32x2_t
+test_vamin_f32 (float32x2_t a, float32x2_t b)
+{
+ return vmin_f32 (vabs_f32 (a), vabs_f32 (b));
+}
+
+/* { dg-final { scan-assembler-times {\tfamin\tv[0-9]+.2s, v[0-9]+.2s, v[0-9]+.2s} 1 } } */
+/* { dg-final { scan-assembler-times {\tfmin\tv[0-9]+.2s, v[0-9]+.2s, v[0-9]+.2s} 0 } } */
+/* { dg-final { scan-assembler-times {\tfabs\tv[0-9]+.2s, v[0-9]+.2s} 0 } } */
+
+float32x4_t
+test_vaminq_f32 (float32x4_t a, float32x4_t b)
+{
+ return vminq_f32 (vabsq_f32 (a), vabsq_f32 (b));
+}
+
+/* { dg-final { scan-assembler-times {\tfamin\tv[0-9]+.4s, v[0-9]+.4s, v[0-9]+.4s} 1 } } */
+/* { dg-final { scan-assembler-times {\tfmin\tv[0-9]+.4s, v[0-9]+.4s, v[0-9]+.4s} 0 } } */
+/* { dg-final { scan-assembler-times {\tfabs\tv[0-9]+.4s, v[0-9]+.4s} 0 } } */
+
+float64x2_t
+test_vaminq_f64 (float64x2_t a, float64x2_t b)
+{
+ return vminq_f64 (vabsq_f64 (a), vabsq_f64 (b));
+}
+
+/* { dg-final { scan-assembler-times {\tfamin\tv[0-9]+.2d, v[0-9]+.2d, v[0-9]+.2d} 1 } } */
+/* { dg-final { scan-assembler-times {\tfmin\tv[0-9]+.2d, v[0-9]+.2d, v[0-9]+.2d} 0 } } */
+/* { dg-final { scan-assembler-times {\tfabs\tv[0-9]+.2d, v[0-9]+.2d} 0 } } */