On Thu, Dec 18, 2025 at 11:41 AM Alfie Richards <[email protected]> wrote:
>
> Adds support for the AArch64 fmv priority syntax.
>
> This allows users to override the default function ordering.
>
> For example:
>
> ```c
> int bar [[gnu::target_version("default")]] (int){
> return 1;
> }
>
> int bar [[gnu::target_version("dotprod;priority=2")]] (int) {
> return 2;
> }
>
> int bar [[gnu::target_version("sve;priority=1")]] (int) {
> return 3;
> }
> ```
>
> gcc/ChangeLog:
>
> * config/aarch64/aarch64.cc (aarch64_parse_fmv_features): Add parsing
> for priority arguments.
> (aarch64_process_target_version_attr): Update call to
> aarch64_parse_fmv_features.
> (get_feature_mask_for_version): Update call to
> aarch64_parse_fmv_features.
> (aarch64_compare_version_priority): Add logic to order by priority if
> present.
> (aarch64_functions_b_resolvable_from_a): Update call to
> aarch64_parse_fmv_features.
> (aarch64_mangle_decl_assembler_name): Update call to
> aarch64_parse_fmv_features.
> (dispatch_function_versions): Add logic to sort by priority.
> (aarch64_same_function_versions): Add diagnostic if invalid use of
> priority syntax.
> (aarch64_merge_decl_attributes): Add logic to make suer priority
> arguments are preserved.
> (aarch64_check_target_clone_version): Update call to
> aarch64_parse_fmv_features.
>
> gcc/testsuite/ChangeLog:
>
> * gcc.target/aarch64/fmv_priority3.c: New test.
> * gcc.target/aarch64/fmv_priority_error1.c: New test.
> * gcc.target/aarch64/fmv_priority_error2.c: New test.
Ok.
Thanks,
Andrew
> ---
> gcc/config/aarch64/aarch64.cc | 146 +++++++++++++++---
> .../gcc.target/aarch64/fmv_priority3.c | 26 ++++
> .../gcc.target/aarch64/fmv_priority_error1.c | 11 ++
> .../gcc.target/aarch64/fmv_priority_error2.c | 8 +
> 4 files changed, 166 insertions(+), 25 deletions(-)
> create mode 100644 gcc/testsuite/gcc.target/aarch64/fmv_priority3.c
> create mode 100644 gcc/testsuite/gcc.target/aarch64/fmv_priority_error1.c
> create mode 100644 gcc/testsuite/gcc.target/aarch64/fmv_priority_error2.c
>
> diff --git a/gcc/config/aarch64/aarch64.cc b/gcc/config/aarch64/aarch64.cc
> index 7baf041ceb3..c56aa24ebfd 100644
> --- a/gcc/config/aarch64/aarch64.cc
> +++ b/gcc/config/aarch64/aarch64.cc
> @@ -20745,25 +20745,29 @@ static aarch64_fmv_feature_datum
> aarch64_fmv_feature_data[] = {
> static enum aarch_parse_opt_result
> aarch64_parse_fmv_features (string_slice str, aarch64_feature_flags
> *isa_flags,
> aarch64_fmv_feature_mask *feature_mask,
> + unsigned int *priority,
> std::string *invalid_extension)
> {
> if (feature_mask)
> *feature_mask = 0ULL;
> + if (priority)
> + *priority = 0;
> +
> + str = str.strip ();
>
> if (str == "default")
> return AARCH_PARSE_OK;
>
> - gcc_assert (str.is_valid ());
> -
> - while (str.is_valid ())
> + /* Parse the architecture part of the string. */
> + string_slice arch_string = string_slice::tokenize (&str, ";");
> + while (arch_string.is_valid ())
> {
> string_slice ext;
>
> - ext = string_slice::tokenize (&str, "+");
> -
> - gcc_assert (ext.is_valid ());
> + ext = string_slice::tokenize (&arch_string, "+");
> + ext = ext.strip ();
>
> - if (!ext.is_valid () || ext.empty ())
> + if (ext.empty ())
> return AARCH_PARSE_MISSING_ARG;
>
> int num_features = ARRAY_SIZE (aarch64_fmv_feature_data);
> @@ -20800,6 +20804,60 @@ aarch64_parse_fmv_features (string_slice str,
> aarch64_feature_flags *isa_flags,
> }
> }
>
> + /* Parse any extra arguments. */
> + unsigned int priority_res = 0;
> + while (str.is_valid ())
> + {
> + string_slice argument = string_slice::tokenize (&str, ";").strip ();
> + /* Save the whole argument for diagnostics. */
> + string_slice arg = argument;
> + string_slice name = string_slice::tokenize (&argument, "=").strip ();
> + argument = argument.strip ();
> + if (!argument.is_valid () || argument.empty ())
> + {
> + *invalid_extension = std::string (arg.begin (), arg.size ());
> + return AARCH_PARSE_INVALID_FEATURE;
> + }
> +
> + /* priority=N argument (only one is allowed). */
> + if (name == "priority" && priority_res == 0)
> + {
> + /* Priority values can only be between 1 and 255, so any greater
> than
> + 3 digits long are invalid. */
> + if (argument.size () > 3)
> + {
> + *invalid_extension = std::string (arg.begin (), arg.size ());
> + return AARCH_PARSE_INVALID_FEATURE;
> + }
> +
> + /* Parse the string value. */
> + for (char c : argument)
> + {
> + if (!ISDIGIT (c))
> + {
> + priority_res = 0;
> + break;
> + }
> + priority_res = 10 * priority_res + c - '0';
> + }
> +
> + /* Check the entire string parsed, and that the value is in range.
> */
> + if (priority_res < 1 || priority_res > 255)
> + {
> + *invalid_extension = std::string (arg.begin (), arg.size ());
> + return AARCH_PARSE_INVALID_FEATURE;
> + }
> + if (priority)
> + *priority = priority_res;
> + }
> + else
> + {
> + /* Unrecognised argument. */
> + *invalid_extension = std::string (arg.begin (), arg.size ());
> + return AARCH_PARSE_INVALID_FEATURE;
> + }
> + }
> +
> return AARCH_PARSE_OK;
> }
>
> @@ -20831,7 +20889,7 @@ aarch64_process_target_version_attr (tree args)
> auto isa_flags = aarch64_asm_isa_flags;
>
> std::string invalid_extension;
> - parse_res = aarch64_parse_fmv_features (str, &isa_flags, NULL,
> + parse_res = aarch64_parse_fmv_features (str, &isa_flags, NULL, NULL,
> &invalid_extension);
>
> if (parse_res == AARCH_PARSE_OK)
> @@ -20920,7 +20978,7 @@ aarch64_option_valid_version_attribute_p (tree
> fndecl, tree, tree args, int)
> add or remove redundant feature requirements. */
>
> static aarch64_fmv_feature_mask
> -get_feature_mask_for_version (tree decl)
> +get_feature_mask_for_version (tree decl, unsigned int *priority)
> {
> tree version_attr = lookup_attribute ("target_version",
> DECL_ATTRIBUTES (decl));
> @@ -20934,7 +20992,7 @@ get_feature_mask_for_version (tree decl)
> aarch64_fmv_feature_mask feature_mask;
>
> parse_res = aarch64_parse_fmv_features (version_string, NULL,
> - &feature_mask, NULL);
> + &feature_mask, priority, NULL);
>
> /* We should have detected any errors before getting here. */
> gcc_assert (parse_res == AARCH_PARSE_OK);
> @@ -20970,9 +21028,13 @@ compare_feature_masks (aarch64_fmv_feature_mask
> mask1,
> int
> aarch64_compare_version_priority (tree decl1, tree decl2)
> {
> - auto mask1 = get_feature_mask_for_version (decl1);
> - auto mask2 = get_feature_mask_for_version (decl2);
> + unsigned int priority_1 = 0;
> + unsigned int priority_2 = 0;
> + auto mask1 = get_feature_mask_for_version (decl1, &priority_1);
> + auto mask2 = get_feature_mask_for_version (decl2, &priority_2);
>
> + if (priority_1 != priority_2)
> + return priority_1 > priority_2 ? 1 : -1;
> return compare_feature_masks (mask1, mask2);
> }
>
> @@ -20989,9 +21051,9 @@ aarch64_functions_b_resolvable_from_a (tree decl_a,
> tree decl_b, tree baseline)
> auto a_version = get_target_version (decl_a);
> auto b_version = get_target_version (decl_b);
> if (a_version.is_valid ())
> - aarch64_parse_fmv_features (a_version, &isa_a, NULL, NULL);
> + aarch64_parse_fmv_features (a_version, &isa_a, NULL, NULL, NULL);
> if (b_version.is_valid ())
> - aarch64_parse_fmv_features (b_version, &isa_b, NULL, NULL);
> + aarch64_parse_fmv_features (b_version, &isa_b, NULL, NULL, NULL);
>
> /* Are there any bits of b that arent in a. */
> if (isa_b & (~isa_a))
> @@ -21069,7 +21131,7 @@ aarch64_mangle_decl_assembler_name (tree decl, tree
> id)
> else if (DECL_FUNCTION_VERSIONED (decl))
> {
> aarch64_fmv_feature_mask feature_mask
> - = get_feature_mask_for_version (decl);
> + = get_feature_mask_for_version (decl, NULL);
>
> if (feature_mask == 0ULL)
> return clone_identifier (id, "default");
> @@ -21372,7 +21434,7 @@ dispatch_function_versions (tree dispatch_decl,
> FOR_EACH_VEC_ELT_REVERSE ((*fndecls), i, version_decl)
> {
> aarch64_fmv_feature_mask feature_mask
> - = get_feature_mask_for_version (version_decl);
> + = get_feature_mask_for_version (version_decl, NULL);
> *empty_bb = add_condition_to_bb (dispatch_decl, version_decl,
> feature_mask, mask_var, *empty_bb);
> }
> @@ -21495,26 +21557,46 @@ aarch64_get_function_versions_dispatcher (void
> *decl)
> return dispatch_decl;
> }
>
> -/* This function returns true if STR1 and STR2 are version strings for the
> same
> - function. */
> +/* This function returns true if OLD_STR and NEW_STR are version strings for
> the
> + same function.
> + Emits a diagnostic if the new version should not be merged with the old
> + version due to a prioriy value mismatch. */
>
> bool
> -aarch64_same_function_versions (string_slice old_str, const_tree,
> - string_slice new_str, const_tree)
> +aarch64_same_function_versions (string_slice old_str, const_tree old_decl,
> + string_slice new_str, const_tree new_decl)
> {
> enum aarch_parse_opt_result parse_res;
> aarch64_fmv_feature_mask old_feature_mask;
> aarch64_fmv_feature_mask new_feature_mask;
> + unsigned int old_priority;
> + unsigned int new_priority;
>
> parse_res = aarch64_parse_fmv_features (old_str, NULL, &old_feature_mask,
> - NULL);
> + &old_priority, NULL);
> gcc_assert (parse_res == AARCH_PARSE_OK);
>
> parse_res = aarch64_parse_fmv_features (new_str, NULL, &new_feature_mask,
> - NULL);
> + &new_priority, NULL);
> gcc_assert (parse_res == AARCH_PARSE_OK);
>
> - return old_feature_mask == new_feature_mask;
> + if (old_feature_mask != new_feature_mask)
> + return false;
> +
> + /* Accept the case where the old version had a defined priority value but
> the
> + new version does not, as we infer the new version inherits the same
> + priority. */
> + if (old_decl && new_decl && old_priority != new_priority
> + && (new_priority != 0))
> + {
> + error ("%q+D has an inconsistent function multi-version priority
> value",
> + new_decl);
> + inform (DECL_SOURCE_LOCATION (old_decl),
> + "%q+D was previously declared here with priority value of %d",
> + old_decl, old_priority);
> + }
> +
> + return true;
> }
>
> /* Implement TARGET_FUNCTION_ATTRIBUTE_INLINABLE_P. Use an opt-out
> @@ -30756,6 +30838,20 @@ aarch64_merge_decl_attributes (tree olddecl, tree
> newdecl)
> aarch64_check_arm_new_against_type (TREE_VALUE (new_new), newdecl);
> }
>
> + /* For target_version and target_clones, make sure we take the old version
> + as priority syntax cannot be added addetively. */
> + tree old_target_version = lookup_attribute ("target_version", old_attrs);
> + tree new_target_version = lookup_attribute ("target_version", new_attrs);
> +
> + if (old_target_version && new_target_version)
> + TREE_VALUE (new_target_version) = TREE_VALUE (old_target_version);
> +
> + tree old_target_clones = lookup_attribute ("target_clones", old_attrs);
> + tree new_target_clones = lookup_attribute ("target_clones", new_attrs);
> +
> + if (old_target_clones && new_target_clones)
> + TREE_VALUE (new_target_clones) = TREE_VALUE (old_target_clones);
> +
> return merge_attributes (old_attrs, new_attrs);
> }
>
> @@ -32771,8 +32867,8 @@ aarch64_check_target_clone_version (string_slice str,
> location_t *loc)
> auto isa_flags = aarch64_asm_isa_flags;
> std::string invalid_extension;
>
> - parse_res
> - = aarch64_parse_fmv_features (str, &isa_flags, NULL, &invalid_extension);
> + parse_res = aarch64_parse_fmv_features (str, &isa_flags, NULL, NULL,
> + &invalid_extension);
>
> if (loc == NULL)
> return parse_res == AARCH_PARSE_OK;
> diff --git a/gcc/testsuite/gcc.target/aarch64/fmv_priority3.c
> b/gcc/testsuite/gcc.target/aarch64/fmv_priority3.c
> new file mode 100644
> index 00000000000..535e99ab6a0
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/aarch64/fmv_priority3.c
> @@ -0,0 +1,26 @@
> +/* { dg-do compile } */
> +/* { dg-require-ifunc "" } */
> +/* { dg-options "-O0 -fdump-ipa-targetclone1-details" } */
> +
> +int foo [[gnu::target_version("dotprod;priority=1")]] (void);
> +int foo [[gnu::target_version("default")]] (void) { return 1; }
> +int foo [[gnu::target_version("dotprod;priority=1")]] (void) { return 2; }
> +int foo [[gnu::target_version("sve")]] (void) { return 3; }
> +
> +// Priority1 dotprod > sve
> +/* { dg-final { scan-ipa-dump-times "Version order for
> foo/\[0-9\]+:\\nfoo\.default/\[0-9\]+\\nfoo\._Msve/\[0-9\]+\\nfoo\._Mdotprod/\[0-9\]+\\n"
> 1 "targetclone1" } } */
> +
> +int bar [[gnu::target_version("dotprod;priority=2")]] (void);
> +int bar [[gnu::target_version("default")]] (void) { return 1; }
> +int bar [[gnu::target_version("dotprod")]] (void) { return 2; }
> +int bar [[gnu::target_version("sve;priority=1")]] (void) { return 3; }
> +
> +// Priority2 dotprod > Priority1 sve
> +/* { dg-final { scan-ipa-dump-times "Version order for
> bar/\[0-9\]+:\\nbar\.default/\[0-9\]+\\nbar\._Msve/\[0-9\]+\\nbar\._Mdotprod/\[0-9\]+\\n"
> 1 "targetclone1" } } */
> +
> +int baz [[gnu::target_version("default")]] (void) { return 1; }
> +int baz [[gnu::target_version("dotprod;priority=1")]] (void) { return 2; }
> +int baz [[gnu::target_version("sve;priority=1")]] (void) { return 3; }
> +
> +// Priority1 sve > Priority1 dotprod
> +/* { dg-final { scan-ipa-dump-times "Version order for
> baz/\[0-9\]+:\\nbaz\.default/\[0-9\]+\\nbaz\._Mdotprod/\[0-9\]+\\nbaz\._Msve/\[0-9\]+\\n"
> 1 "targetclone1" } } */
> diff --git a/gcc/testsuite/gcc.target/aarch64/fmv_priority_error1.c
> b/gcc/testsuite/gcc.target/aarch64/fmv_priority_error1.c
> new file mode 100644
> index 00000000000..7b7665367c1
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/aarch64/fmv_priority_error1.c
> @@ -0,0 +1,11 @@
> +/* { dg-do compile } */
> +/* { dg-require-ifunc } */
> +/* { dg-options "-std=c23 -pedantic-errors" } */
> +
> +// Value was 2, now is 1
> +int foo [[gnu::target_version("dotprod;priority=2")]] (void); /* {
> dg-message ".foo \\\[\\\[target_version\\\(.dotprod;priority=2.\\\)\\\]\\\].
> was previously declared here with priority value of 2" } */
> +int foo [[gnu::target_version("dotprod;priority=1")]] (void) { return 2; }
> /* { dg-error ".foo
> \\\[\\\[target_version\\\(.dotprod;priority=1.\\\)\\\]\\\]. has an
> inconsistent function multi-version priority value" } */
> +
> +// Value was 0, now is 2
> +int bar [[gnu::target_version("dotprod")]] (void); /* { dg-message ".bar
> \\\[\\\[target_version\\\(.dotprod.\\\)\\\]\\\]. was previously declared here
> with priority value of 0" } */
> +int bar [[gnu::target_version("dotprod;priority=2")]] (void) { return 2; }
> /* { dg-error ".bar
> \\\[\\\[target_version\\\(.dotprod;priority=2.\\\)\\\]\\\]. has an
> inconsistent function multi-version priority value" } */
> diff --git a/gcc/testsuite/gcc.target/aarch64/fmv_priority_error2.c
> b/gcc/testsuite/gcc.target/aarch64/fmv_priority_error2.c
> new file mode 100644
> index 00000000000..0f212ff6aae
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/aarch64/fmv_priority_error2.c
> @@ -0,0 +1,8 @@
> +/* { dg-do compile } */
> +/* { dg-require-ifunc } */
> +/* { dg-options "-std=c23 -pedantic-errors" } */
> +
> +int foo [[gnu::target_version("dotprod;priority=0")]] (void); /* { dg-error
> "invalid feature modifier .priority=0. of value .dotprod;priority=0. in
> .target_version. attribute" } */
> +int foo [[gnu::target_version("dotprod;priority=-1")]] (void); /* { dg-error
> "invalid feature modifier .priority=-1. of value .dotprod;priority=-1. in
> .target_version. attribute" } */
> +int foo [[gnu::target_version("dotprod;priority=256")]] (void); /* {
> dg-error "invalid feature modifier .priority=256. of value
> .dotprod;priority=256. in .target_version. attribute" } */
> +int foo [[gnu::target_version("priority=4;dotprod")]] (void); /* { dg-error
> "invalid feature modifier .priority=4. of value .priority=4;dotprod. in
> .target_version. attribute" } */
> --
> 2.34.1
>