Hi Alfie,
On 10/8/25 14:57, [email protected] wrote:
From: Alfie Richards <[email protected]>
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/config/aarch64/aarch64.cc | 126 +++++++++++++++---
.../gcc.target/aarch64/fmv_priority3.c | 24 ++++
2 files changed, 130 insertions(+), 20 deletions(-)
create mode 100644 gcc/testsuite/gcc.target/aarch64/fmv_priority3.c
diff --git a/gcc/config/aarch64/aarch64.cc b/gcc/config/aarch64/aarch64.cc
index 3f341f33f0a..6cfd269c07b 100644
--- a/gcc/config/aarch64/aarch64.cc
+++ b/gcc/config/aarch64/aarch64.cc
@@ -20403,21 +20403,27 @@ 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, ";");
+ gcc_assert (arch_string.is_valid ());
Redundant assert - it's always valid even if empty, and we're testing it
immediately below...
+ while (arch_string.is_valid ())
{
string_slice ext;
- ext = string_slice::tokenize (&str, "+");
+ ext = string_slice::tokenize (&arch_string, "+");
gcc_assert (ext.is_valid ());
@@ -20458,6 +20464,60 @@ aarch64_parse_fmv_features (string_slice str, aarch64_feature_flags *isa_flags,
}
}
+ /* Parse any extra arguments. */
+ while (str.is_valid ())
+ {
+ string_slice argument = string_slice::tokenize (&str, ";");
+ string_slice arg = argument;
+ char n[4];
+ argument = argument.strip ();
Wouldn't it make sense to strip spaces before arg = argument?
+ string_slice name = string_slice::tokenize (&argument, "=").strip ();
+ unsigned int priority_res = 0;
+
+ /* priority=N argument (only one is allowed). */
+ if (name == "priority" && priority_res == 0)
+ {
+ if (!argument.is_valid ())
+ {
+ *invalid_extension = std::string (arg.begin (), arg.size ());
+ return AARCH_PARSE_INVALID_FEATURE;
+ }
+ argument = argument.strip ();
+ if (!argument.is_valid ())
+ {
+ *invalid_extension = std::string (arg.begin (), arg.size ());
+ return AARCH_PARSE_INVALID_FEATURE;
+ }
This is dead code, it if was valid, it remains valid after stripping spaces.
+ if (argument.size () > 3)
Since the slice can be empty, we should also check argument.size () > 0.
+ {
+ *invalid_extension = std::string (arg.begin (), arg.size ());
+ return AARCH_PARSE_INVALID_FEATURE;
+ }
+ memcpy (n, argument.begin (),
+ std::min (argument.size (), (unsigned int) 3));
What's the idea behind the ::min? We already know size() <= 3...
+
+ int res = sscanf (n, "%ud", &priority_res);
This will return random results and can even read beyond the size of the
array. You need to terminate the string. Also "%ud" will math "+1" which
you probably don't want to match.
+ if (res != 1)
+ {
+ *invalid_extension = std::string (arg.begin (), arg.size ());
+ return AARCH_PARSE_INVALID_FEATURE;
+ }
+ if (priority_res < 1 || priority_res > 255)
+ {
+ *invalid_extension = std::string (arg.begin (), arg.size ());
+ return AARCH_PARSE_INVALID_FEATURE;
+ }
I'd merge this if into the previous if and check all 3 conditions in one go.
+ 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;
}
@@ -20489,7 +20549,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)
@@ -20578,7 +20638,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));
@@ -20592,7 +20652,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);
@@ -20628,9 +20688,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);
}
@@ -20647,9 +20711,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))
@@ -20727,7 +20791,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");
@@ -21157,19 +21221,27 @@ aarch64_get_function_versions_dispatcher (void *decl)
function. */
bool
-aarch64_same_function_versions (string_slice str1, string_slice str2)
+aarch64_same_function_versions (string_slice old_str, string_slice new_str)
{
enum aarch_parse_opt_result parse_res;
aarch64_fmv_feature_mask feature_mask1;
aarch64_fmv_feature_mask feature_mask2;
- parse_res = aarch64_parse_fmv_features (str1, NULL,
- &feature_mask1, NULL);
+ unsigned int priority1;
+ unsigned int priority2;
+ parse_res = aarch64_parse_fmv_features (old_str, NULL,
+ &feature_mask1, &priority1, NULL);
gcc_assert (parse_res == AARCH_PARSE_OK);
- parse_res = aarch64_parse_fmv_features (str2, NULL,
- &feature_mask2, NULL);
+ parse_res = aarch64_parse_fmv_features (new_str, NULL,
+ &feature_mask2, &priority2, NULL);
gcc_assert (parse_res == AARCH_PARSE_OK);
- return feature_mask1 == feature_mask2;
+ if (feature_mask1 != feature_mask2)
+ return false;
+
+ if (priority1 != priority2 && priority1 != 0)
+ error ("inconsistent priority values");
Is it correct that this is asymmetric like that?
+
+ return true;
}
/* Implement TARGET_FUNCTION_ATTRIBUTE_INLINABLE_P. Use an opt-out
@@ -30276,6 +30348,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);
}
@@ -31944,8 +32030,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..0f24bf7c798
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/fmv_priority3.c
@@ -0,0 +1,24 @@
+/* { dg-do compile } */
+/* { dg-require-ifunc "" } */
+/* { dg-options "-O0 -fdump-ipa-targetclone1-details" } */
+
+int foo [[gnu::target_version("default")]] (int) { return 1; }
+int foo [[gnu::target_version("dotprod;priority=1")]] (int) { return 2; }
+int foo [[gnu::target_version("sve")]] (int) { 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("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; }
+
+// 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")]] (int) { return 1; }
+int baz [[gnu::target_version("dotprod;priority=1")]] (int) { return 2; }
+int baz [[gnu::target_version("sve;priority=1")]] (int) { 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" } } */