aarch64: Add support for fmv priority syntax.

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.
This commit is contained in:
Alfie Richards
2025-10-07 14:16:16 +00:00
parent 3ecc4d571f
commit 558918d6b2
4 changed files with 166 additions and 25 deletions

View File

@@ -20916,25 +20916,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, "+");
ext = string_slice::tokenize (&arch_string, "+");
ext = ext.strip ();
gcc_assert (ext.is_valid ());
if (!ext.is_valid () || ext.empty ())
if (ext.empty ())
return AARCH_PARSE_MISSING_ARG;
int num_features = ARRAY_SIZE (aarch64_fmv_feature_data);
@@ -20971,6 +20975,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;
}
@@ -21002,7 +21060,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)
@@ -21091,7 +21149,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));
@@ -21105,7 +21163,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);
@@ -21141,9 +21199,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);
}
@@ -21160,9 +21222,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))
@@ -21240,7 +21302,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");
@@ -21543,7 +21605,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);
}
@@ -21666,26 +21728,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
@@ -30927,6 +31009,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);
}
@@ -32942,8 +33038,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;

View File

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

View File

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

View File

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