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

Reply via email to