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
>

Reply via email to