On 8/26/25 6:33 AM, Alfie Richards wrote:
The 08/22/2025 12:22, Jason Merrill wrote:
On 8/19/25 4:48 AM, Alfie Richards wrote:
This patch changes the semantics of target_version and target_clones attributes
to match the behavior described in the Arm C Language extension.

The changes to behavior are:

- The scope and signature of an FMV function set is now that of the default
    version.
- The FMV resolver is now created at the locations of the default version
    implementation. Previously this was at the first call to an FMV function.
- When a TU has a single annotated function version, it gets mangled.
    - This includes a lone annotated default version.

This only affects targets with TARRGET_HAS_FMV_TARGET_ATTRIBUTE set to false.
Currently that is aarch64 and riscv.

This is achieved by:

- Skipping the existing FMV dispatching code at C++ gimplification and instead
    making use of the target_clones dispatching code in multiple_targets.cc.
    (This fixes PR target/118313 for aarch64 and riscv).
- Splitting target_clones pass in two, an early and late pass, where the early
    pass handles cases where multiple declarations are used to define a version,
    and the late pass handling target semantics targets, and cases where a FMV
    set is defined by a single target_clones decl.
- Changing the logic in add_candidates and resolve_address of overloaded
    function to prevent resolution of any version except a default version.
    (thus making the default version determine scope and signature of the
    versioned function set).
- Adding logic for dispatching a lone annotated default version in
    multiple_targets.cc
    - As as annotated default version gets mangled an alias is created from the
      dispatched symbol to the default version as no ifunc resolution is 
required
      in this case. (ie. an alias from `_Z3foov` to `_Z3foov.default`)
- Adding logic to `symbol_table::remove_unreachable_nodes` and analyze_functions
    that a reference to the default function version also implies a possible
    reference to the other versions (so they shouldnt be deleted and do need to
    be analyzed).

gcc/ChangeLog:

        PR target/118313
        * cgraph.cc (delete_function_version): Made public static member of
        cgraph_node.
        * cgraph.h (delete_function_version): Ditto.
        * cgraphunit.cc (analyze_functions): Add logic for target version
        dependencies.
        * ipa.cc (symbol_table::remove_unreachable_nodes): Ditto.
        * multiple_target.cc (create_dispatcher_calls): Change to support
        target version semantics.
        (ipa_target_clone): Change to dispatch all function sets in
        target_version semantics, and to have early and late pass.
        (expand_target_clones): Add logic for cases of target_clones with no
        defaults.
        (is_simple_target_clones_case): New function.
        (class pass_target_clone): New parameter for early or late pass.
        * config/aarch64/aarch64.cc: (aarch64_get_function_versions_dispatcher):
        Refactor with the assumption that the DECL node will be default.
        * config/riscv/riscv.cc: (riscv_get_function_versions_dispatcher):
        Refactor with the assumption that the DECL node will be default.
        * passes.def: Split target_clones pass into early and late version.

gcc/cp/ChangeLog:

        PR target/118313
        * call.cc (add_candidates): Change to not resolve non-default versions
        in target_version semantics.
        * class.cc (resolve_address_of_overloaded_function): Ditto.
        * cp-gimplify.cc (cp_genericize_r): Change logic to not apply for
        target_version semantics.
        * decl.cc (start_decl): Change to mark and therefore mangle all
        target_version decls in target_version semantics.
        (start_preparsed_function): Ditto.
        * typeck.cc (cp_build_function_call_vec): Add error for calling
        unresolvable non-default node in target_version semantics.

gcc/testsuite/ChangeLog:

        * g++.target/aarch64/mv-1.C: Change for target_version semantics.
        * g++.target/aarch64/mv-symbols2.C: Ditto.
        * g++.target/aarch64/mv-symbols3.C: Ditto.
        * g++.target/aarch64/mv-symbols4.C: Ditto.
        * g++.target/aarch64/mv-symbols5.C: Ditto.
        * g++.target/aarch64/mvc-symbols3.C: Ditto.
        * g++.target/riscv/mv-symbols2.C: Ditto.
        * g++.target/riscv/mv-symbols3.C: Ditto.
        * g++.target/riscv/mv-symbols4.C: Ditto.
        * g++.target/riscv/mv-symbols5.C: Ditto.
        * g++.target/riscv/mvc-symbols3.C: Ditto.
        * g++.target/aarch64/mv-symbols10.C: New test.
        * g++.target/aarch64/mv-symbols11.C: New test.
        * g++.target/aarch64/mv-symbols12.C: New test.
        * g++.target/aarch64/mv-symbols13.C: New test.
        * g++.target/aarch64/mv-symbols6.C: New test.
        * g++.target/aarch64/mv-symbols7.C: New test.
        * g++.target/aarch64/mv-symbols8.C: New test.
        * g++.target/aarch64/mv-symbols9.C: New test.
---
   gcc/cgraph.cc                                 |   4 +-
   gcc/cgraph.h                                  |   2 +
   gcc/cgraphunit.cc                             |   9 +
   gcc/config/aarch64/aarch64.cc                 |  43 ++--
   gcc/config/riscv/riscv.cc                     |  43 ++--
   gcc/cp/call.cc                                |  10 +
   gcc/cp/class.cc                               |  13 +-
   gcc/cp/cp-gimplify.cc                         |  11 +-
   gcc/cp/decl.cc                                |  14 ++
   gcc/cp/typeck.cc                              |  10 +
   gcc/ipa.cc                                    |  11 +
   gcc/multiple_target.cc                        | 188 +++++++++++++++---
   gcc/passes.def                                |   3 +-
   gcc/testsuite/g++.target/aarch64/mv-1.C       |   4 +
   .../g++.target/aarch64/mv-symbols10.C         |  27 +++
   .../g++.target/aarch64/mv-symbols11.C         |  30 +++
   .../g++.target/aarch64/mv-symbols12.C         |  28 +++
   .../g++.target/aarch64/mv-symbols13.C         |  28 +++
   .../g++.target/aarch64/mv-symbols2.C          |  12 +-
   .../g++.target/aarch64/mv-symbols3.C          |   6 +-
   .../g++.target/aarch64/mv-symbols4.C          |   6 +-
   .../g++.target/aarch64/mv-symbols5.C          |   6 +-
   .../g++.target/aarch64/mv-symbols6.C          |  21 ++
   .../g++.target/aarch64/mv-symbols7.C          |  48 +++++
   .../g++.target/aarch64/mv-symbols8.C          |  46 +++++
   .../g++.target/aarch64/mv-symbols9.C          |  43 ++++
   .../g++.target/aarch64/mvc-symbols3.C         |  12 +-
   gcc/testsuite/g++.target/riscv/mv-symbols2.C  |  12 +-
   gcc/testsuite/g++.target/riscv/mv-symbols3.C  |   6 +-
   gcc/testsuite/g++.target/riscv/mv-symbols4.C  |   6 +-
   gcc/testsuite/g++.target/riscv/mv-symbols5.C  |   6 +-
   gcc/testsuite/g++.target/riscv/mvc-symbols3.C |  12 +-
   32 files changed, 588 insertions(+), 132 deletions(-)
   create mode 100644 gcc/testsuite/g++.target/aarch64/mv-symbols10.C
   create mode 100644 gcc/testsuite/g++.target/aarch64/mv-symbols11.C
   create mode 100644 gcc/testsuite/g++.target/aarch64/mv-symbols12.C
   create mode 100644 gcc/testsuite/g++.target/aarch64/mv-symbols13.C
   create mode 100644 gcc/testsuite/g++.target/aarch64/mv-symbols6.C
   create mode 100644 gcc/testsuite/g++.target/aarch64/mv-symbols7.C
   create mode 100644 gcc/testsuite/g++.target/aarch64/mv-symbols8.C
   create mode 100644 gcc/testsuite/g++.target/aarch64/mv-symbols9.C

diff --git a/gcc/cgraph.cc b/gcc/cgraph.cc
index c0be16edfcb..1d86bcec67f 100644
--- a/gcc/cgraph.cc
+++ b/gcc/cgraph.cc
@@ -333,8 +333,8 @@ cgraph_node::insert_new_function_version (void)
   }
   /* Remove the cgraph_function_version_info node given by DECL_V.  */
-static void
-delete_function_version (cgraph_function_version_info *decl_v)
+void
+cgraph_node::delete_function_version (cgraph_function_version_info *decl_v)
   {
     if (decl_v == NULL)
       return;
diff --git a/gcc/cgraph.h b/gcc/cgraph.h
index 21f89112769..a719321a538 100644
--- a/gcc/cgraph.h
+++ b/gcc/cgraph.h
@@ -1352,6 +1352,8 @@ struct GTY((tag ("SYMTAB_FUNCTION"))) cgraph_node : 
public symtab_node
        DECL is a duplicate declaration.  */
     static void delete_function_version_by_decl (tree decl);
+  static void delete_function_version (cgraph_function_version_info *);
+
     /* Add the function FNDECL to the call graph.
        Unlike finalize_function, this function is intended to be used
        by middle end and allows insertion of new function at arbitrary point
diff --git a/gcc/cgraphunit.cc b/gcc/cgraphunit.cc
index 9f4af63b7dc..a81f685654f 100644
--- a/gcc/cgraphunit.cc
+++ b/gcc/cgraphunit.cc
@@ -1264,6 +1264,15 @@ analyze_functions (bool first_time)
              if (!cnode->analyzed)
                cnode->analyze ();
+             /* A reference to a default node in a function set implies a
+                reference to all versions in the set.  */
+             cgraph_function_version_info *node_v = cnode->function_version ();
+             if (node_v && is_function_default_version (node->decl))
+               for (cgraph_function_version_info *fvi = node_v->next;
+                    fvi;
+                    fvi = fvi->next)
+                 enqueue_node (fvi->this_node);
+
              for (edge = cnode->callees; edge; edge = edge->next_callee)
                if (edge->callee->definition
                    && (!DECL_EXTERNAL (edge->callee->decl)
diff --git a/gcc/config/aarch64/aarch64.cc b/gcc/config/aarch64/aarch64.cc
index 5415af2a73b..441a5cf9b42 100644
--- a/gcc/config/aarch64/aarch64.cc
+++ b/gcc/config/aarch64/aarch64.cc
@@ -21052,42 +21052,29 @@ aarch64_generate_version_dispatcher_body (void 
*node_p)
     return resolver_decl;
   }
-/* Make a dispatcher declaration for the multi-versioned function DECL.
-   Calls to DECL function will be replaced with calls to the dispatcher
-   by the front-end.  Returns the decl of the dispatcher function.  */
+/* Make a dispatcher declaration for the multi-versioned default function DECL.
+   Calls to DECL function will be replaced with calls to the dispatcher by
+   the target_clones pass.  Returns the decl of the dispatcher function.  */
   tree
   aarch64_get_function_versions_dispatcher (void *decl)
   {
-  tree fn = (tree) decl;
-  struct cgraph_node *node = NULL;
-  struct cgraph_node *default_node = NULL;
-  struct cgraph_function_version_info *node_v = NULL;
-
+  tree default_decl = (tree) decl;
     tree dispatch_decl = NULL;
-  struct cgraph_function_version_info *default_version_info = NULL;
-
-  gcc_assert (fn != NULL && DECL_FUNCTION_VERSIONED (fn));
-
-  node = cgraph_node::get (fn);
-  gcc_assert (node != NULL);
+  gcc_assert (decl != NULL
+             && DECL_FUNCTION_VERSIONED (default_decl)
+             && is_function_default_version (default_decl));
-  node_v = node->function_version ();
-  gcc_assert (node_v != NULL);
+  struct cgraph_node *default_node = cgraph_node::get (default_decl);
+  gcc_assert (default_node != NULL);
-  if (node_v->dispatcher_resolver != NULL)
-    return node_v->dispatcher_resolver;
+  struct cgraph_function_version_info *default_node_v
+    = default_node->function_version ();
+  gcc_assert (default_node_v != NULL && !default_node_v->prev);
-  /* The default node is always the beginning of the chain.  */
-  default_version_info = node_v;
-  while (default_version_info->prev)
-    default_version_info = default_version_info->prev;
-  default_node = default_version_info->this_node;
-
-  /* If there is no default node, just return NULL.  */
-  if (!is_function_default_version (default_node->decl))
-    return NULL;
+  if (default_node_v->dispatcher_resolver != NULL)
+    return default_node_v->dispatcher_resolver;
     if (targetm.has_ifunc_p ())
       {
@@ -21097,7 +21084,7 @@ aarch64_get_function_versions_dispatcher (void *decl)
         dispatch_decl = make_dispatcher_decl (default_node->decl);
         /* Set the dispatcher for all the versions.  */
-      it_v = default_version_info;
+      it_v = default_node_v;
         while (it_v != NULL)
        {
          it_v->dispatcher_resolver = dispatch_decl;
diff --git a/gcc/config/riscv/riscv.cc b/gcc/config/riscv/riscv.cc
index 419eec7ed16..3c953804804 100644
--- a/gcc/config/riscv/riscv.cc
+++ b/gcc/config/riscv/riscv.cc
@@ -14573,42 +14573,29 @@ riscv_generate_version_dispatcher_body (void *node_p)
     return resolver_decl;
   }
-/* Make a dispatcher declaration for the multi-versioned function DECL.
-   Calls to DECL function will be replaced with calls to the dispatcher
-   by the front-end.  Returns the decl of the dispatcher function.  */
+/* Make a dispatcher declaration for the multi-versioned default function DECL.
+   Calls to DECL function will be replaced with calls to the dispatcher by
+   the target_clones pass.  Returns the decl of the dispatcher function.  */
   tree
   riscv_get_function_versions_dispatcher (void *decl)
   {
-  tree fn = (tree) decl;
-  struct cgraph_node *node = NULL;
-  struct cgraph_node *default_node = NULL;
-  struct cgraph_function_version_info *node_v = NULL;
-
+  tree default_decl = (tree) decl;
     tree dispatch_decl = NULL;
-  struct cgraph_function_version_info *default_version_info = NULL;
-
-  gcc_assert (fn != NULL && DECL_FUNCTION_VERSIONED (fn));
-
-  node = cgraph_node::get (fn);
-  gcc_assert (node != NULL);
-
-  node_v = node->function_version ();
-  gcc_assert (node_v != NULL);
+  gcc_assert (decl != NULL
+             && DECL_FUNCTION_VERSIONED (default_decl)
+             && is_function_default_version (default_decl));
-  if (node_v->dispatcher_resolver != NULL)
-    return node_v->dispatcher_resolver;
+  struct cgraph_node *default_node = cgraph_node::get (default_decl);
+  gcc_assert (default_node != NULL);
-  /* The default node is always the beginning of the chain.  */
-  default_version_info = node_v;
-  while (default_version_info->prev)
-    default_version_info = default_version_info->prev;
-  default_node = default_version_info->this_node;
+  struct cgraph_function_version_info *default_node_v
+    = default_node->function_version ();
+  gcc_assert (default_node_v != NULL && !default_node_v->prev);
-  /* If there is no default node, just return NULL.  */
-  if (!is_function_default_version (default_node->decl))
-    return NULL;
+  if (default_node_v->dispatcher_resolver != NULL)
+    return default_node_v->dispatcher_resolver;
     if (targetm.has_ifunc_p ())
       {
@@ -14618,7 +14605,7 @@ riscv_get_function_versions_dispatcher (void *decl)
         dispatch_decl = make_dispatcher_decl (default_node->decl);
         /* Set the dispatcher for all the versions.  */
-      it_v = default_version_info;
+      it_v = default_node_v;
         while (it_v != NULL)
        {
          it_v->dispatcher_resolver = dispatch_decl;
diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index 63cad2aca4c..a6a3fd0d6bb 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -6927,6 +6927,16 @@ add_candidates (tree fns, tree first_arg, const vec<tree, 
va_gc> *args,
            continue;
        }
+      /* Do not resolve any non-default function.  Only the default version
+        is resolvable (for the target_version attribute semantics.)  */
+      if (!TARGET_HAS_FMV_TARGET_ATTRIBUTE
+         && TREE_CODE (fn) == FUNCTION_DECL
+         && !is_function_default_version (fn))
+       {
+         add_ignored_candidate (candidates, fn);
+         continue;
+       }
+
         if (TREE_CODE (fn) == TEMPLATE_DECL)
        add_template_candidate (candidates,
                                fn,
diff --git a/gcc/cp/class.cc b/gcc/cp/class.cc
index 14acb9c23c0..5e9ad1724d5 100644
--- a/gcc/cp/class.cc
+++ b/gcc/cp/class.cc
@@ -8974,6 +8974,13 @@ resolve_address_of_overloaded_function (tree target_type,
        if (!constraints_satisfied_p (fn))
          continue;
+       /* For target_version semantics, never resolve a non-default
+          version.  */
+       if (!TARGET_HAS_FMV_TARGET_ATTRIBUTE
+           && TREE_CODE (fn) == FUNCTION_DECL
+           && !is_function_default_version (fn))
+         continue;

Instead of these I wonder about changing update_binding to drop non-default
versions from the name binding?  But the C++ changes are OK as is if you'd
rather not explore that direction.

Jason

Hi Jason,

I'd be happy to explore that directions, however I'm not sure what that would
look like?

We need to be able to detect symbol conflicts still in order to build the
FMV set. In the below case, for instance, we need to be able to find the SVE
decl at the point of parsing the default decl, so we would still need to
attach something (either the sve decl or the generated dispatched decl) to the
binding?

```c++
[[gnu::target_version("sve")]] int foo();

int foo();
```

However, the FMV funciton set itself isn't resolvable until the default version
is declared. So the below example needs to be invalid.

```c++
[[gnu::target_version("sve")]] int foo();

int bar () {return foo();}

int foo();
```

So I think we would still need some check to make FMV bindihngs without a
default non-resolvable?

Hmm, good point.  So never mind.

Jason

Reply via email to