]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
fmv: Support mixing of target_clones and target_version.
authorAlfie Richards <alfie.richards@arm.com>
Wed, 6 Aug 2025 11:22:14 +0000 (11:22 +0000)
committerAlfie Richards <alfie.richards@arm.com>
Tue, 23 Sep 2025 09:47:44 +0000 (09:47 +0000)
Add support for a FMV set defined by a combination of target_clones and
target_version definitions.

Additionally, change is_function_default_version to consider a function
declaration annotated with target_clones containing default to be a
default version.

Lastly, add support for the case that a target_clone has all versions filtered
out and therefore the declaration should be removed. This is relevant as now
the default could be defined in a target_version, so a target_clones no longer
necessarily contains the default.

This takes advantage of refactoring done in previous patches changing how
target_clones are expanded and how conflicting decls are handled.

gcc/ChangeLog:

* attribs.cc (is_function_default_version): Update to handle
target_clones.
* cgraph.h (FOR_EACH_FUNCTION_REMOVABLE): New macro.
* multiple_target.cc (expand_target_clones): Update logic to delete
empty target_clones and modify diagnostic.
(ipa_target_clone): Update to use FOR_EACH_FUNCTION_REMOVABLE.

gcc/c-family/ChangeLog:

* c-attribs.cc: Add support for target_version and target_clone mixing.

gcc/testsuite/ChangeLog:

* g++.target/aarch64/mv-and-mvc1.C: New test.
* g++.target/aarch64/mv-and-mvc2.C: New test.
* g++.target/aarch64/mv-and-mvc3.C: New test.
* g++.target/aarch64/mv-and-mvc4.C: New test.

gcc/attribs.cc
gcc/c-family/c-attribs.cc
gcc/cgraph.h
gcc/multiple_target.cc
gcc/testsuite/g++.target/aarch64/mv-and-mvc1.C [new file with mode: 0644]
gcc/testsuite/g++.target/aarch64/mv-and-mvc2.C [new file with mode: 0644]
gcc/testsuite/g++.target/aarch64/mv-and-mvc3.C [new file with mode: 0644]
gcc/testsuite/g++.target/aarch64/mv-and-mvc4.C [new file with mode: 0644]

index 9efc327553f6762a64d72a01f3955b56ffc4842f..29b88b8d836071f40c12cef16759719e37be0d7f 100644 (file)
@@ -1233,7 +1233,8 @@ make_dispatcher_decl (const tree decl)
    With the target attribute semantics, returns true if the function is marked
    as default with the target version.
    With the target_version attribute semantics, returns true if the function
-   is either not annotated, or annotated as default.  */
+   is either not annotated, annotated as default, or is a target_clone
+   containing the default declaration.  */
 
 bool
 is_function_default_version (const tree decl)
@@ -1250,6 +1251,13 @@ is_function_default_version (const tree decl)
     }
   else
     {
+      if (lookup_attribute ("target_clones", DECL_ATTRIBUTES (decl)))
+       {
+         int num_defaults = 0;
+         get_clone_versions (decl, &num_defaults);
+         return num_defaults > 0;
+       }
+
       attr = lookup_attribute ("target_version", DECL_ATTRIBUTES (decl));
       if (!attr)
        return true;
index 6e11f74de2289b9917455f606ab7e2812a1f0ae3..df9ff9947a890ef493534fdb5e290a7afed53304 100644 (file)
@@ -249,13 +249,6 @@ static const struct attribute_spec::exclusions attr_target_clones_exclusions[] =
   ATTR_EXCL ("always_inline", true, true, true),
   ATTR_EXCL ("target", TARGET_HAS_FMV_TARGET_ATTRIBUTE,
             TARGET_HAS_FMV_TARGET_ATTRIBUTE, TARGET_HAS_FMV_TARGET_ATTRIBUTE),
-  ATTR_EXCL ("target_version", true, true, true),
-  ATTR_EXCL (NULL, false, false, false),
-};
-
-static const struct attribute_spec::exclusions attr_target_version_exclusions[] =
-{
-  ATTR_EXCL ("target_clones", true, true, true),
   ATTR_EXCL (NULL, false, false, false),
 };
 
@@ -543,7 +536,7 @@ const struct attribute_spec c_common_gnu_attributes[] =
                              attr_target_exclusions },
   { "target_version",         1, 1, true, false, false, false,
                              handle_target_version_attribute,
-                             attr_target_version_exclusions },
+                             NULL },
   { "target_clones",          1, -1, true, false, false, false,
                              handle_target_clones_attribute,
                              attr_target_clones_exclusions },
index a7906265d7e540bd342c14b7a4a9d91fac9873bc..b68a8df87d555eee29f0b75dedeaeb8ded7e5eae 100644 (file)
@@ -3124,6 +3124,13 @@ symbol_table::next_function_with_gimple_body (cgraph_node *node)
    for ((node) = symtab->first_function (); (node); \
        (node) = symtab->next_function ((node)))
 
+/* Walk all functions but precompute so a node can be deleted if needed.  */
+#define FOR_EACH_FUNCTION_REMOVABLE(node) \
+   cgraph_node *next; \
+   for ((node) = symtab->first_function (), \
+       next = (node) ? symtab->next_function ((node)) : NULL; (node); \
+       (node) = next, next = (node) ? symtab->next_function ((node)) : NULL)
+
 /* Return true when callgraph node is a function with Gimple body defined
    in current unit.  Functions can also be define externally or they
    can be thunks with no Gimple representation.
index 02e868212e46adcff934e4cecc5deb1d8c220d95..e85d7e71442d0eb04c5e62b3313117f19c3741b0 100644 (file)
@@ -269,14 +269,28 @@ expand_target_clones (struct cgraph_node *node, bool definition)
   auto_vec<string_slice> attr_list = get_clone_versions (node->decl,
                                                         &num_defaults);
 
+  /* If the target clones list is empty after filtering, remove this node.  */
+  if (!TARGET_HAS_FMV_TARGET_ATTRIBUTE && attr_list.is_empty ())
+    {
+      node->remove ();
+      return false;
+    }
+
   /* No need to clone for 1 target attribute.  */
-  if (attr_list.length () == 1)
+  if (attr_list.length () == 1 && TARGET_HAS_FMV_TARGET_ATTRIBUTE)
     {
       warning_at (DECL_SOURCE_LOCATION (node->decl),
                  0, "single %<target_clones%> attribute is ignored");
       return false;
     }
 
+  /* For target_version semantics, a target clone with just a default version
+     is the same as an unannotated decl, so can ignore.  */
+  if (!TARGET_HAS_FMV_TARGET_ATTRIBUTE
+      && attr_list.length () == 1
+      && num_defaults == 1)
+    return false;
+
   if (node->definition
       && (node->alias || !tree_versionable_function_p (node->decl)))
     {
@@ -304,8 +318,10 @@ expand_target_clones (struct cgraph_node *node, bool definition)
                "multiple %<default%> targets were set");
       return false;
     }
-  /* Disallow target clones with no defaults.  */
-  if (num_defaults == 0)
+
+  /* For target FMV semantics, where target and target_clone mixing
+     is not supported, disallow target clones with no defaults.  */
+  if (TARGET_HAS_FMV_TARGET_ATTRIBUTE && num_defaults == 0)
     {
       error_at (DECL_SOURCE_LOCATION (node->decl),
                "%<default%> target was not set");
@@ -521,7 +537,7 @@ ipa_target_clone (bool early)
      The late stage is only used for the expansion and dispatching of the simple
      case where the FMV set is defined by a single target_clone attribute.  */
 
-  FOR_EACH_FUNCTION (node)
+  FOR_EACH_FUNCTION_REMOVABLE (node)
     {
       /* In the early stage, we need to expand any target clone that is not
         the simple case.  Simple cases are dispatched in the later stage.  */
diff --git a/gcc/testsuite/g++.target/aarch64/mv-and-mvc1.C b/gcc/testsuite/g++.target/aarch64/mv-and-mvc1.C
new file mode 100644 (file)
index 0000000..24b81f5
--- /dev/null
@@ -0,0 +1,38 @@
+/* { dg-do compile } */
+/* { dg-require-ifunc "" } */
+/* { dg-options "-O0" } */
+/* { dg-additional-options "-Wno-experimental-fmv-target" } */
+
+__attribute__((target_version("default")))
+int foo ()
+{
+  return 0;
+}
+
+__attribute__((target_clones("dotprod", "sve+sve2")))
+int foo ()
+{
+  return 1;
+}
+
+__attribute__((target_clones("sve2+sme", "sve2+sme2")))
+int foo ()
+{
+  return 2;
+}
+
+int bar()
+{
+  return foo ();
+}
+
+
+/* { dg-final { scan-assembler-times "\n_Z3foov\.default:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\._Mdotprod:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\._MsveMsve2:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\._Msve2Msme:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\._Msve2Msme2:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tbl\t_Z3foov\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3foov, %gnu_indirect_function\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3foov,_Z3foov\.resolver\n" 1 } } */
diff --git a/gcc/testsuite/g++.target/aarch64/mv-and-mvc2.C b/gcc/testsuite/g++.target/aarch64/mv-and-mvc2.C
new file mode 100644 (file)
index 0000000..5939353
--- /dev/null
@@ -0,0 +1,29 @@
+/* { dg-do compile } */
+/* { dg-require-ifunc "" } */
+/* { dg-options "-O0" } */
+/* { dg-additional-options "-Wno-experimental-fmv-target" } */
+
+__attribute__((target_version("default")))
+int foo ();
+
+__attribute__((target_clones("dotprod", "sve+sve2")))
+int foo ()
+{
+  return 1;
+}
+
+__attribute__((target_clones("sve2+sme", "sve2+sme2")))
+int foo ()
+{
+  return 2;
+}
+
+/* { dg-final { scan-assembler-times "\n_Z3foov\.default:\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov:\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\._Mdotprod:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\._MsveMsve2:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\._Msve2Msme:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\._Msve2Msme2:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3foov, %gnu_indirect_function\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3foov,_Z3foov\.resolver\n" 0 } } */
diff --git a/gcc/testsuite/g++.target/aarch64/mv-and-mvc3.C b/gcc/testsuite/g++.target/aarch64/mv-and-mvc3.C
new file mode 100644 (file)
index 0000000..fb1c596
--- /dev/null
@@ -0,0 +1,41 @@
+/* { dg-do compile } */
+/* { dg-require-ifunc "" } */
+/* { dg-options "-O0" } */
+/* { dg-additional-options "-Wno-experimental-fmv-target" } */
+
+__attribute__((target_clones("dotprod", "sve+sve2")))
+int foo ();
+
+__attribute__((target_version("default")))
+int foo ()
+{
+  return 0;
+}
+
+__attribute__((target_clones("sve2+sme", "sve2+sme2")))
+int foo ()
+{
+  return 2;
+}
+
+int bar()
+{
+  return foo ();
+}
+
+
+/* { dg-final { scan-assembler-times "\n_Z3foov\.default:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\._Mdotprod:\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\._MsveMsve2:\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\._Msve2Msme:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\._Msve2Msme2:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tbl\t_Z3foov\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3foov, %gnu_indirect_function\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3foov,_Z3foov\.resolver\n" 1 } } */
+// { dg-final { scan-assembler-times "\n\tadrp\tx\[0-9\]+, _Z3foov\.default\n" 1 } }
+/* { dg-final { scan-assembler-times "\n\tadrp\tx\[0-9\]+, _Z3foov\._Mdotprod\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx\[0-9\]+, _Z3foov\._MsveMsve2\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx\[0-9\]+, _Z3foov\._Msve2Msme\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx\[0-9\]+, _Z3foov\._Msve2Msme2\n" 1 } } */
+
diff --git a/gcc/testsuite/g++.target/aarch64/mv-and-mvc4.C b/gcc/testsuite/g++.target/aarch64/mv-and-mvc4.C
new file mode 100644 (file)
index 0000000..e198fec
--- /dev/null
@@ -0,0 +1,38 @@
+/* { dg-do compile } */
+/* { dg-require-ifunc "" } */
+/* { dg-options "-O0" } */
+/* { dg-additional-options "-Wno-experimental-fmv-target" } */
+
+__attribute__((target_version("dotprod")))
+int foo ()
+{
+  return 0;
+}
+
+__attribute__((target_clones("default", "sve+sve2")))
+int foo ()
+{
+  return 1;
+}
+
+__attribute__((target_clones("sve2+sme", "sve2+sme2")))
+int foo ()
+{
+  return 2;
+}
+
+int bar()
+{
+  return foo ();
+}
+
+
+/* { dg-final { scan-assembler-times "\n_Z3foov\.default:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\._Mdotprod:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\._MsveMsve2:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\._Msve2Msme:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\._Msve2Msme2:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tbl\t_Z3foov\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3foov, %gnu_indirect_function\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3foov,_Z3foov\.resolver\n" 1 } } */