https://gcc.gnu.org/g:aeb9a43bbb2e92d5e4b93250785701d0e37355d9

commit r16-4039-gaeb9a43bbb2e92d5e4b93250785701d0e37355d9
Author: Alfie Richards <[email protected]>
Date:   Thu Feb 13 15:30:45 2025 +0000

    fmv: c++: Change target_version semantics to follow ACLE specification.
    
    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 (maybe_mark_function_versioned): Remove static.
            * cp-tree.h (maybe_mark_function_versioned): New function.
            * decl2.cc (cplus_decl_attributes ): Change to mark and therefore
            mangle all target_version decls in target_version semantics.
            * 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.

Diff:
---
 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/cp-tree.h                                |   1 +
 gcc/cp/decl.cc                                  |   2 +-
 gcc/cp/decl2.cc                                 |   7 +
 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 +
 gcc/testsuite/g++.target/aarch64/mv-symbols10.C |  27 ++++
 gcc/testsuite/g++.target/aarch64/mv-symbols11.C |  30 ++++
 gcc/testsuite/g++.target/aarch64/mv-symbols12.C |  28 ++++
 gcc/testsuite/g++.target/aarch64/mv-symbols13.C |  28 ++++
 gcc/testsuite/g++.target/aarch64/mv-symbols2.C  |  12 +-
 gcc/testsuite/g++.target/aarch64/mv-symbols3.C  |   6 +-
 gcc/testsuite/g++.target/aarch64/mv-symbols4.C  |   6 +-
 gcc/testsuite/g++.target/aarch64/mv-symbols5.C  |   6 +-
 gcc/testsuite/g++.target/aarch64/mv-symbols6.C  |  21 +++
 gcc/testsuite/g++.target/aarch64/mv-symbols7.C  |  48 ++++++
 gcc/testsuite/g++.target/aarch64/mv-symbols8.C  |  46 ++++++
 gcc/testsuite/g++.target/aarch64/mv-symbols9.C  |  43 ++++++
 gcc/testsuite/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 +-
 34 files changed, 583 insertions(+), 133 deletions(-)

diff --git a/gcc/cgraph.cc b/gcc/cgraph.cc
index 1d33d12bd0ce..07966a63905a 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 ba6b8cbefed1..a7906265d7e5 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 9f4af63b7dc0..a81f685654f8 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 30808a670860..4a42735acc42 100644
--- a/gcc/config/aarch64/aarch64.cc
+++ b/gcc/config/aarch64/aarch64.cc
@@ -21111,42 +21111,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 ())
     {
@@ -21156,7 +21143,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 1f1b92e82fe6..b55cfaad6e16 100644
--- a/gcc/config/riscv/riscv.cc
+++ b/gcc/config/riscv/riscv.cc
@@ -14623,42 +14623,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 ())
     {
@@ -14668,7 +14655,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 550ce5f712be..97c8a48c8400 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -6931,6 +6931,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 cf58f652fc1d..df8d3c9321e3 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;
+
        if (undeduced_auto_decl (fn))
          {
            /* Force instantiation to do return type deduction.  */
@@ -9199,8 +9206,10 @@ resolve_address_of_overloaded_function (tree target_type,
   /* If a pointer to a function that is multi-versioned is requested, the
      pointer to the dispatcher function is returned instead.  This works
      well because indirectly calling the function will dispatch the right
-     function version at run-time.  */
-  if (DECL_FUNCTION_VERSIONED (fn))
+     function version at run-time.
+     This is done at multiple_target.cc for target_version semantics.  */
+
+  if (DECL_FUNCTION_VERSIONED (fn) && TARGET_HAS_FMV_TARGET_ATTRIBUTE)
     {
       fn = get_function_version_dispatcher (fn);
       if (fn == NULL)
diff --git a/gcc/cp/cp-gimplify.cc b/gcc/cp/cp-gimplify.cc
index 39da4ff236ab..2111dcadae7f 100644
--- a/gcc/cp/cp-gimplify.cc
+++ b/gcc/cp/cp-gimplify.cc
@@ -2166,13 +2166,16 @@ cp_genericize_r (tree *stmt_p, int *walk_subtrees, void 
*data)
         returns the function with the highest target priority, that is,
         the version that will checked for dispatching first.  If this
         version is inlinable, a direct call to this version can be made
-        otherwise the call should go through the dispatcher.  */
+        otherwise the call should go through the dispatcher.
+        This is done at multiple_target.cc for target_version semantics.  */
       {
        tree fn = cp_get_callee_fndecl_nofold (stmt);
-       if (fn && DECL_FUNCTION_VERSIONED (fn)
+       if (TARGET_HAS_FMV_TARGET_ATTRIBUTE
+           && fn
+           && DECL_FUNCTION_VERSIONED (fn)
            && (current_function_decl == NULL
-               || !targetm.target_option.can_inline_p (current_function_decl,
-                                                       fn)))
+               || !targetm.target_option.can_inline_p
+                     (current_function_decl, fn)))
          if (tree dis = get_function_version_dispatcher (fn))
            {
              mark_versions_used (dis);
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 54156cb0d237..6ee945f3ebf9 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -7381,6 +7381,7 @@ extern tree build_explicit_specifier              (tree, 
tsubst_flags_t);
 extern bool use_eh_spec_block                  (tree);
 extern void do_push_parm_decls                 (tree, tree, tree *);
 extern tree do_aggregate_paren_init            (tree, tree);
+extern void maybe_mark_function_versioned      (tree);
 
 /* in decl2.cc */
 extern void record_mangling                    (tree, bool);
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index 8f21ea837b13..f75e057c2efe 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -1268,7 +1268,7 @@ decls_match (tree newdecl, tree olddecl, bool 
record_versions /* = true */)
 
 /* Mark DECL as versioned if it isn't already.  */
 
-static void
+void
 maybe_mark_function_versioned (tree decl)
 {
   if (!DECL_FUNCTION_VERSIONED (decl))
diff --git a/gcc/cp/decl2.cc b/gcc/cp/decl2.cc
index 4564fdc45e01..f87d7d685478 100644
--- a/gcc/cp/decl2.cc
+++ b/gcc/cp/decl2.cc
@@ -2026,6 +2026,13 @@ cplus_decl_attributes (tree *decl, tree attributes, int 
flags)
            set_decl_tls_model (*decl, default_model);
        }
     }
+
+  /* For target_version semantics, mark any annotated function as versioned
+     so that it gets mangled even when on its own in a TU.  */
+  if (!TARGET_HAS_FMV_TARGET_ATTRIBUTE
+      && TREE_CODE (*decl) == FUNCTION_DECL
+      && get_target_version (*decl).is_valid ())
+    maybe_mark_function_versioned (*decl);
 }
 
 /* Walks through the namespace- or function-scope anonymous union
diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
index ccfe50d3c370..b876409d8474 100644
--- a/gcc/cp/typeck.cc
+++ b/gcc/cp/typeck.cc
@@ -4495,6 +4495,16 @@ cp_build_function_call_vec (tree function, vec<tree, 
va_gc> **params,
        return error_mark_node;
       fndecl = function;
 
+      /* For target_version semantics, the function set cannot be called
+        if there is no default version in scope.  */
+      if (!TARGET_HAS_FMV_TARGET_ATTRIBUTE
+          && !is_function_default_version (fndecl))
+        {
+          if (complain & tf_error)
+           error ("no default version in scope");
+          return error_mark_node;
+        }
+
       /* Convert anything with function type to a pointer-to-function.  */
       if (DECL_MAIN_P (function))
        {
diff --git a/gcc/ipa.cc b/gcc/ipa.cc
index 8a7b067a526a..dea22ea0b491 100644
--- a/gcc/ipa.cc
+++ b/gcc/ipa.cc
@@ -433,6 +433,17 @@ symbol_table::remove_unreachable_nodes (FILE *file)
                                                       e, &first, &reachable);
                    }
                }
+
+             /* A reference to the default node implies use of all the other
+                versions (they get used in the function resolver made later
+                in multiple_target.cc)  */
+             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, &first, &reachable);
+
              for (e = cnode->callees; e; e = e->next_callee)
                {
                  symtab_node *body = e->callee->function_symbol ();
diff --git a/gcc/multiple_target.cc b/gcc/multiple_target.cc
index dc3b72c8e850..02e868212e46 100644
--- a/gcc/multiple_target.cc
+++ b/gcc/multiple_target.cc
@@ -58,8 +58,15 @@ replace_function_decl (tree *op, int *walk_subtrees, void 
*data)
   return NULL;
 }
 
-/* If the call in NODE has multiple target attribute with multiple fields,
-   replace it with dispatcher call and create dispatcher (once).  */
+/* In target FMV attributes, if the call in NODE has multiple target attribute
+   with multiple fields, replace it with calls to the dispatched symbol and
+   create the dispatcher body (once).
+
+   In target_version semantics, if it is a lone annotated default, then
+   the dispatched symbol is changed to be an alias and no resolver is
+   required.  Otherwise, redirect all calls and references to the dispatched
+   symbol, but only create the resolver body if the default version is
+   implemented.  */
 
 static void
 create_dispatcher_calls (struct cgraph_node *node)
@@ -90,13 +97,48 @@ create_dispatcher_calls (struct cgraph_node *node)
 
   cgraph_node *inode = cgraph_node::get (idecl);
   gcc_assert (inode);
-  tree resolver_decl = targetm.generate_version_dispatcher_body (inode);
-
-  /* Update aliases.  */
-  inode->alias = true;
-  inode->alias_target = resolver_decl;
-  if (!inode->analyzed)
-    inode->resolve_alias (cgraph_node::get (resolver_decl));
+  cgraph_function_version_info *inode_info = inode->function_version ();
+  gcc_assert (inode_info);
+
+  tree resolver_decl = NULL;
+
+  /* For target_version semantics, if there is a lone default declaration
+     it needs to be mangled, with an alias from the dispatched symbol to the
+     default version.  */
+  if (!TARGET_HAS_FMV_TARGET_ATTRIBUTE
+      && TREE_STATIC (node->decl)
+      && inode_info->next
+      && !inode_info->next->next)
+    {
+      inode->alias = true;
+      inode->alias_target = inode_info->next->this_node->decl;
+      inode->externally_visible = true;
+      if (!inode->analyzed)
+       inode->resolve_alias
+         (cgraph_node::get (inode_info->next->this_node->decl));
+
+      DECL_ATTRIBUTES (idecl)
+       = make_attribute ("alias",
+                         IDENTIFIER_POINTER
+                           (DECL_ASSEMBLER_NAME
+                              (inode_info->next->this_node->decl)),
+                         DECL_ATTRIBUTES (node->decl));
+      TREE_USED (idecl) = true;
+      DECL_EXTERNAL (idecl) = false;
+      TREE_STATIC (idecl) = true;
+      return;
+    }
+  /* In target_version semantics, only create the resolver if the
+     default node is implemented.  */
+  else if (TARGET_HAS_FMV_TARGET_ATTRIBUTE || TREE_STATIC (node->decl))
+    {
+      resolver_decl = targetm.generate_version_dispatcher_body (inode);
+      /* Update aliases.  */
+      inode->alias = true;
+      inode->alias_target = resolver_decl;
+      if (!inode->analyzed)
+       inode->resolve_alias (cgraph_node::get (resolver_decl));
+    }
 
   auto_vec<cgraph_edge *> edges_to_redirect;
   /* We need to capture the references by value rather than just pointers to 
them
@@ -299,19 +341,32 @@ expand_target_clones (struct cgraph_node *node, bool 
definition)
   tree assembler_name = node_v->assembler_name;
 
   /* Change the current node into the default node.  */
-  gcc_assert (num_defaults == 1);
-
-  /* Setting new attribute to initial function.  */
-  tree attributes = make_attribute (new_attr_name, "default", attrs);
-  DECL_ATTRIBUTES (node->decl) = attributes;
-  DECL_FUNCTION_VERSIONED (node->decl) = true;
+  if (num_defaults == 1)
+    {
+      /* Setting new attribute to initial function.  */
+      tree attributes = make_attribute (new_attr_name, "default", attrs);
+      DECL_ATTRIBUTES (node->decl) = attributes;
+      DECL_FUNCTION_VERSIONED (node->decl) = true;
 
-  node->is_target_clone = true;
-  node->local = false;
+      node->is_target_clone = true;
+      node->local = false;
 
-  /* Remangle base node after new target version string set.  */
-  tree id = targetm.mangle_decl_assembler_name (node->decl, assembler_name);
-  symtab->change_decl_assembler_name (node->decl, id);
+      /* Remangle base node after new target version string set.  */
+      tree id = targetm.mangle_decl_assembler_name (node->decl, 
assembler_name);
+      symtab->change_decl_assembler_name (node->decl, id);
+    }
+  else
+    {
+      /* Target clones without a default are only allowed for target_version
+        semantics where we can have target_clones/target_version mixing.  */
+      gcc_assert (!TARGET_HAS_FMV_TARGET_ATTRIBUTE);
+
+      /* If there isn't a default version, can safely remove this version.
+        The node itself gets removed after the other versions are created.  */
+      cgraph_function_version_info *temp = node_v;
+      node_v = node_v->next ? node_v->next : node_v->prev;
+      cgraph_node::delete_function_version (temp);
+    }
 
   for (string_slice attr : attr_list)
     {
@@ -344,6 +399,10 @@ expand_target_clones (struct cgraph_node *node, bool 
definition)
       symtab->change_decl_assembler_name (new_node->decl, id);
     }
 
+  /* If there are no default versions in the target_clones, this node is not
+     reused, so can delete this node.  */
+  if (num_defaults == 0)
+    node->remove ();
 
   return true;
 }
@@ -406,15 +465,87 @@ redirect_to_specific_clone (cgraph_node *node)
     }
 }
 
+/* Checks if NODE is in the 'simple' target_clones case, which is where NODE
+   is a declaration annotated with target_clones containing the default, and it
+   is the sole function declaration in the FMV function set.  */
+
+static bool
+is_simple_target_clones_case (cgraph_node *node)
+{
+  /* target attribute semantics doesnt support the complex case,
+     so this is always true.  */
+  if (TARGET_HAS_FMV_TARGET_ATTRIBUTE)
+    return true;
+
+  int num_defaults = 0;
+  auto versions = get_clone_versions (node->decl, &num_defaults);
+  if (versions.is_empty () || num_defaults != 1)
+    return false;
+
+  cgraph_function_version_info *fv = node->function_version ();
+
+  if (fv && (fv->next || fv->prev))
+    return false;
+
+  return true;
+}
+
 static unsigned int
-ipa_target_clone (void)
+ipa_target_clone (bool early)
 {
   struct cgraph_node *node;
   auto_vec<cgraph_node *> to_dispatch;
 
+  /* Don't need to do anything early for target attribute semantics.  */
+  if (early && TARGET_HAS_FMV_TARGET_ATTRIBUTE)
+    return 0;
+
+  /* For target attribute semantics, this pass skips the early phase, and in
+     the later stage is only responsible for expanding and dispatching
+     target_clone declarations, as target annotated functions are dispatched
+     in the front end.
+
+     The expanding and dispatching can be done at the late stage as the
+     target_clone functions aren't allowed to be part of a larger FMV set, so
+     all versions will all have the same body, so early optimisations are safe
+     to treat a call to a target_clones set as a call to one function.
+
+     For target_version semantics, this pass is responsible for expanding
+     target_clones and dispatching all FMV function sets, including ones only
+     made up of target_version declarations.
+
+     Cases where there is more than one declaration must be expanded and
+     dispatched at the early stage, as the declarations may have different
+     bodies, and so the early optimisation passes would not be valid.
+
+     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)
-    if (expand_target_clones (node, node->definition))
-      to_dispatch.safe_push (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.  */
+
+      if (early == !is_simple_target_clones_case (node))
+       if (expand_target_clones (node, node->definition)
+           && TARGET_HAS_FMV_TARGET_ATTRIBUTE)
+         /* In non target_version semantics, dispatch all target clones.  */
+         to_dispatch.safe_push (node);
+    }
+
+  /* In target_version semantics dispatch all FMV function sets with a default
+     implementation in the early stage.
+     Also dispatch any default versions generated by expanding target_clones
+     in the late stage.  */
+
+  if (!TARGET_HAS_FMV_TARGET_ATTRIBUTE)
+    FOR_EACH_FUNCTION (node)
+      if (is_function_default_version (node->decl)
+         && DECL_FUNCTION_VERSIONED (node->decl)
+         /* Don't dispatch target clones, as they haven't been expanded so
+            are simple.  */
+         && !lookup_attribute ("target_clones", DECL_ATTRIBUTES (node->decl)))
+       to_dispatch.safe_push (node);
 
   for (unsigned i = 0; i < to_dispatch.length (); i++)
     create_dispatcher_calls (to_dispatch[i]);
@@ -445,14 +576,21 @@ class pass_target_clone : public simple_ipa_opt_pass
 {
 public:
   pass_target_clone (gcc::context *ctxt)
-    : simple_ipa_opt_pass (pass_data_target_clone, ctxt)
+    : simple_ipa_opt_pass (pass_data_target_clone, ctxt), early_p (false)
   {}
+  bool early_p;
 
+  void set_pass_param (unsigned int n, bool param) final override
+    {
+      gcc_assert (n == 0);
+      early_p = param;
+    }
   /* opt_pass methods: */
   bool gate (function *) final override;
+  opt_pass * clone () final override { return new pass_target_clone (m_ctxt); }
   unsigned int execute (function *) final override
   {
-    return ipa_target_clone ();
+    return ipa_target_clone (early_p);
   }
 };
 
diff --git a/gcc/passes.def b/gcc/passes.def
index e9610de9e2f9..3f828477b687 100644
--- a/gcc/passes.def
+++ b/gcc/passes.def
@@ -66,6 +66,7 @@ along with GCC; see the file COPYING3.  If not see
       NEXT_PASS (pass_nothrow);
       NEXT_PASS (pass_rebuild_cgraph_edges);
   POP_INSERT_PASSES ()
+  NEXT_PASS (pass_target_clone, true);
 
   NEXT_PASS (pass_local_optimization_passes);
   PUSH_INSERT_PASSES_WITHIN (pass_local_optimization_passes)
@@ -143,7 +144,7 @@ along with GCC; see the file COPYING3.  If not see
       POP_INSERT_PASSES ()
   POP_INSERT_PASSES ()
 
-  NEXT_PASS (pass_target_clone);
+  NEXT_PASS (pass_target_clone, false);
   NEXT_PASS (pass_ipa_auto_profile);
   PUSH_INSERT_PASSES_WITHIN (pass_ipa_auto_profile)
       NEXT_PASS (pass_feedback_split_functions);
diff --git a/gcc/testsuite/g++.target/aarch64/mv-1.C 
b/gcc/testsuite/g++.target/aarch64/mv-1.C
index b10037f1b9ba..93b8a1365873 100644
--- a/gcc/testsuite/g++.target/aarch64/mv-1.C
+++ b/gcc/testsuite/g++.target/aarch64/mv-1.C
@@ -37,3 +37,7 @@ int bar()
 /* { dg-final { scan-assembler-times "\n_Z3foov\._Mrng:\n" 1 } } */
 /* { dg-final { scan-assembler-times "\n_Z3foov\._MrngMflagm:\n" 1 } } */
 /* { dg-final { scan-assembler-times "\n_Z3foov\._Mflagm:\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-symbols10.C 
b/gcc/testsuite/g++.target/aarch64/mv-symbols10.C
new file mode 100644
index 000000000000..92d4ab617d85
--- /dev/null
+++ b/gcc/testsuite/g++.target/aarch64/mv-symbols10.C
@@ -0,0 +1,27 @@
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+/* { dg-additional-options "-Wno-experimental-fmv-target" } */
+
+__attribute__ ((target_version ("default"))) void
+foo (int a = 3);
+
+__attribute__ ((target_version ("sve"))) void
+foo (int a = 4);
+
+void bar() {
+  foo();
+}
+
+__attribute__ ((target_version ("sve"))) void
+foo2 (int a = 6);
+
+__attribute__ ((target_version ("default"))) void
+foo2 (int a = 5);
+
+void bar2() {
+  foo2();
+}
+
+
+/* { dg-final { scan-assembler-times "\n\tmov\tw\[0-9\]\+, 3\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tmov\tw\[0-9\]\+, 5\n" 1 } } */
diff --git a/gcc/testsuite/g++.target/aarch64/mv-symbols11.C 
b/gcc/testsuite/g++.target/aarch64/mv-symbols11.C
new file mode 100644
index 000000000000..dadde22622ea
--- /dev/null
+++ b/gcc/testsuite/g++.target/aarch64/mv-symbols11.C
@@ -0,0 +1,30 @@
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+/* { dg-additional-options "-Wno-experimental-fmv-target" } */
+
+__attribute__ ((target_version ("default"))) int
+foo () { return 1; }
+
+__attribute__ ((target_version ("dotprod"))) int
+foo () { return 3; }
+
+int (*test)();
+
+void bar ()
+{
+  test = foo;
+}
+
+__attribute__ ((target_version ("default"))) int
+foo2 ();
+
+__attribute__ ((target_version ("dotprod"))) int
+foo2 ();
+
+void bar2 ()
+{
+  test = foo2;
+}
+
+/* { dg-final { scan-assembler-times "\n\tadrp\tx\[0-9\]+, _Z3foov\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx\[0-9\]+, _Z4foo2v\n" 1 } } */
diff --git a/gcc/testsuite/g++.target/aarch64/mv-symbols12.C 
b/gcc/testsuite/g++.target/aarch64/mv-symbols12.C
new file mode 100644
index 000000000000..d78ee4b91c5c
--- /dev/null
+++ b/gcc/testsuite/g++.target/aarch64/mv-symbols12.C
@@ -0,0 +1,28 @@
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+/* { dg-additional-options "-Wno-experimental-fmv-target" } */
+
+int foo () {
+  return 1;
+}
+
+void
+bar ()
+{
+  foo ();
+}
+
+__attribute__ ((target_version ("dotprod"))) int
+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\.resolver:\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\.default\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-symbols13.C 
b/gcc/testsuite/g++.target/aarch64/mv-symbols13.C
new file mode 100644
index 000000000000..997b9bad6d6c
--- /dev/null
+++ b/gcc/testsuite/g++.target/aarch64/mv-symbols13.C
@@ -0,0 +1,28 @@
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+/* { dg-additional-options "-Wno-experimental-fmv-target" } */
+
+int foo () {
+  return 1;
+}
+
+void bar ()
+{
+  int (*test)() = foo;
+
+  test();
+}
+
+__attribute__ ((target_version ("dotprod"))) int 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\.resolver:\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\.default\n" 
1 } } */
+
+/* { dg-final { scan-assembler-times "\n\tadrp\tx\[0-9\]+, _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-symbols2.C 
b/gcc/testsuite/g++.target/aarch64/mv-symbols2.C
index 6da88ddfb48d..55f2d48f5e47 100644
--- a/gcc/testsuite/g++.target/aarch64/mv-symbols2.C
+++ b/gcc/testsuite/g++.target/aarch64/mv-symbols2.C
@@ -41,13 +41,13 @@ int foo (int)
 /* { 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\.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 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\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_Z3fooi\.default:\n" 1 } } */
 /* { dg-final { scan-assembler-times "\n_Z3fooi\._Mdotprod:\n" 1 } } */
 /* { dg-final { scan-assembler-times "\n_Z3fooi\._MsveMsve2:\n" 1 } } */
-/* { dg-final { scan-assembler-times "\n_Z3fooi\.resolver:\n" 0 } } */
-/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3fooi, 
%gnu_indirect_function\n" 0 } } */
-/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3fooi,_Z3fooi\.resolver\n" 
0 } } */
+/* { dg-final { scan-assembler-times "\n_Z3fooi\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3fooi, 
%gnu_indirect_function\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3fooi,_Z3fooi\.resolver\n" 
1 } } */
diff --git a/gcc/testsuite/g++.target/aarch64/mv-symbols3.C 
b/gcc/testsuite/g++.target/aarch64/mv-symbols3.C
index 5dd7b49be2a1..6ba02a2aae9f 100644
--- a/gcc/testsuite/g++.target/aarch64/mv-symbols3.C
+++ b/gcc/testsuite/g++.target/aarch64/mv-symbols3.C
@@ -29,10 +29,10 @@ int bar()
 /* { dg-final { scan-assembler-times "\n_Z3foov\.default:\n" 0 } } */
 /* { 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\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 0 } } */
 /* { 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\t\.type\t_Z3foov, 
%gnu_indirect_function\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3foov,_Z3foov\.resolver\n" 
0 } } */
 
 /* { dg-final { scan-assembler-times "\n_Z3fooi\.default:\n" 0 } } */
 /* { dg-final { scan-assembler-times "\n_Z3fooi\._Mdotprod:\n" 0 } } */
diff --git a/gcc/testsuite/g++.target/aarch64/mv-symbols4.C 
b/gcc/testsuite/g++.target/aarch64/mv-symbols4.C
index 4b25d17cc15a..cc013c478489 100644
--- a/gcc/testsuite/g++.target/aarch64/mv-symbols4.C
+++ b/gcc/testsuite/g++.target/aarch64/mv-symbols4.C
@@ -44,6 +44,6 @@ int bar()
 /* { dg-final { scan-assembler-times "\n_Z3fooi\.default:\n" 1 } } */
 /* { dg-final { scan-assembler-times "\n_Z3fooi\._Mdotprod:\n" 0 } } */
 /* { dg-final { scan-assembler-times "\n_Z3fooi\._MsveMsve2:\n" 0 } } */
-/* { dg-final { scan-assembler-times "\n_Z3fooi\.resolver:\n" 0 } } */
-/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3fooi, 
%gnu_indirect_function\n" 0 } } */
-/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3fooi,_Z3fooi\.resolver\n" 
0 } } */
+/* { dg-final { scan-assembler-times "\n_Z3fooi\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3fooi, 
%gnu_indirect_function\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3fooi,_Z3fooi\.resolver\n" 
1 } } */
diff --git a/gcc/testsuite/g++.target/aarch64/mv-symbols5.C 
b/gcc/testsuite/g++.target/aarch64/mv-symbols5.C
index fac00b203135..1396ca379e48 100644
--- a/gcc/testsuite/g++.target/aarch64/mv-symbols5.C
+++ b/gcc/testsuite/g++.target/aarch64/mv-symbols5.C
@@ -44,10 +44,10 @@ int bar()
 /* { dg-final { scan-assembler-times "\n_Z3foov\.default:\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\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 0 } } */
 /* { 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\t\.type\t_Z3foov, 
%gnu_indirect_function\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3foov,_Z3foov\.resolver\n" 
0 } } */
 
 /* { dg-final { scan-assembler-times "\n_Z3fooi\.default:\n" 0 } } */
 /* { dg-final { scan-assembler-times "\n_Z3fooi\._Mdotprod:\n" 1 } } */
diff --git a/gcc/testsuite/g++.target/aarch64/mv-symbols6.C 
b/gcc/testsuite/g++.target/aarch64/mv-symbols6.C
new file mode 100644
index 000000000000..2b67bcb47a4e
--- /dev/null
+++ b/gcc/testsuite/g++.target/aarch64/mv-symbols6.C
@@ -0,0 +1,21 @@
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+/* { dg-additional-options "-Wno-experimental-fmv-target" } */
+
+__attribute__ ((target_version ("default"))) int
+foo ()
+{
+  return 1;
+}
+
+int bar()
+{
+  return foo();
+}
+
+/* { dg-final { scan-assembler-times "\n_Z3foov:\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\.default:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 0 } } */
+/* { dg-final { scan-assembler-times "bl\t_Z3foov.default\n" 1 } } */
+/* { dg-final { scan-assembler-times ".global\t_Z3foov\n" 1 } } */
+/* { dg-final { scan-assembler-times ".set\t_Z3foov,_Z3foov.default\n" 1 } } */
diff --git a/gcc/testsuite/g++.target/aarch64/mv-symbols7.C 
b/gcc/testsuite/g++.target/aarch64/mv-symbols7.C
new file mode 100644
index 000000000000..3998adb54a74
--- /dev/null
+++ b/gcc/testsuite/g++.target/aarch64/mv-symbols7.C
@@ -0,0 +1,48 @@
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+/* { dg-additional-options "-Wno-experimental-fmv-target" } */
+
+__attribute__ ((target_version ("dotprod"))) int
+foo ();
+
+__attribute__ ((target_version ("sve+sve2"))) int
+foo ();
+
+__attribute__ ((target_version ("default"))) int
+foo ();
+
+__attribute__ ((target_version ("sve+sve2"))) int
+foo ()
+{
+  return 5;
+}
+__attribute__ ((target_version ("dotprod"))) int
+foo ()
+{
+  return 3;
+}
+__attribute__ ((target_version ("default"))) int
+foo ()
+{
+  return 1;
+}
+
+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\.resolver:\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\._Mdotprod\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx\[0-9\]+, _Z3foov\.default\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-symbols8.C 
b/gcc/testsuite/g++.target/aarch64/mv-symbols8.C
new file mode 100644
index 000000000000..5983bbd69251
--- /dev/null
+++ b/gcc/testsuite/g++.target/aarch64/mv-symbols8.C
@@ -0,0 +1,46 @@
+/* { dg-do compile } */
+/* { dg-require-ifunc "" } */
+/* { dg-options "-O0" } */
+/* { dg-additional-options "-Wno-experimental-fmv-target" } */
+
+__attribute__ ((target_version ("dotprod"))) int
+foo ();
+__attribute__ ((target_version ("sve+sve2"))) int
+foo ();
+
+int
+foo ()
+{
+  return 1;
+}
+
+__attribute__ ((target_version ("dotprod"))) int
+foo ()
+{
+  return 3;
+}
+__attribute__ ((target_version ("sve+sve2"))) int
+foo ()
+{
+  return 5;
+}
+
+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\.resolver:\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\._Mdotprod\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx\[0-9\]+, _Z3foov\.default\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-symbols9.C 
b/gcc/testsuite/g++.target/aarch64/mv-symbols9.C
new file mode 100644
index 000000000000..bfad9bb5850b
--- /dev/null
+++ b/gcc/testsuite/g++.target/aarch64/mv-symbols9.C
@@ -0,0 +1,43 @@
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+/* { dg-additional-options "-Wno-experimental-fmv-target" } */
+
+int
+foo ();
+
+int
+foo ()
+{
+  return 1;
+}
+
+__attribute__ ((target_version ("dotprod"))) int
+foo ()
+{
+  return 3;
+}
+__attribute__ ((target_version ("sve+sve2"))) int
+foo ()
+{
+  return 5;
+}
+
+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\.resolver:\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\._Mdotprod\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx\[0-9\]+, _Z3foov\.default\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/mvc-symbols3.C 
b/gcc/testsuite/g++.target/aarch64/mvc-symbols3.C
index 350a5586643f..2a315d2db5cf 100644
--- a/gcc/testsuite/g++.target/aarch64/mvc-symbols3.C
+++ b/gcc/testsuite/g++.target/aarch64/mvc-symbols3.C
@@ -22,15 +22,15 @@ int bar(int x)
 /* { dg-final { scan-assembler-times "\n_Z3foov\.default:\n" 0 } } */
 /* { 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\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 0 } } */
 /* { 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\t\.type\t_Z3foov, 
%gnu_indirect_function\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3foov,_Z3foov\.resolver\n" 
0 } } */
 
 /* { dg-final { scan-assembler-times "\n_Z3fooi\.default:\n" 0 } } */
 /* { dg-final { scan-assembler-times "\n_Z3fooi\._Mdotprod:\n" 0 } } */
 /* { dg-final { scan-assembler-times "\n_Z3fooi\._MsveMsve2:\n" 0 } } */
-/* { dg-final { scan-assembler-times "\n_Z3fooi\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3fooi\.resolver:\n" 0 } } */
 /* { dg-final { scan-assembler-times "\n\tbl\t_Z3fooi\n" 1 } } */
-/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3fooi, 
%gnu_indirect_function\n" 1 } } */
-/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3fooi,_Z3fooi\.resolver\n" 
1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3fooi, 
%gnu_indirect_function\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3fooi,_Z3fooi\.resolver\n" 
0 } } */
diff --git a/gcc/testsuite/g++.target/riscv/mv-symbols2.C 
b/gcc/testsuite/g++.target/riscv/mv-symbols2.C
index 43fa1502b7d1..8a5c5a0bc9f4 100644
--- a/gcc/testsuite/g++.target/riscv/mv-symbols2.C
+++ b/gcc/testsuite/g++.target/riscv/mv-symbols2.C
@@ -47,15 +47,15 @@ int foo (int)
 /* { dg-final { scan-assembler-times "\n_Z3foov\.default:\n" 1 } } */
 /* { dg-final { scan-assembler-times "\n_Z3foov\.arch__v:\n" 1 } } */
 /* { dg-final { scan-assembler-times "\n_Z3foov\.arch__zba__zbb:\n" 1 } } */
-/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 1 } } */
 /* { dg-final { scan-assembler-times "\n\tcall\t_Z3foov\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 } } */
+/* { 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_Z3fooi\.default:\n" 1 } } */
 /* { dg-final { scan-assembler-times "\n_Z3fooi\.arch__v:\n" 1 } } */
 /* { dg-final { scan-assembler-times "\n_Z3fooi\.arch__zba__zbb:\n" 1 } } */
-/* { dg-final { scan-assembler-times "\n_Z3fooi\.resolver:\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n_Z3fooi\.resolver:\n" 1 } } */
 /* { dg-final { scan-assembler-times "\n\tcall\t_Z3fooi\n" 0 } } */
-/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3fooi, 
@gnu_indirect_function\n" 0 } } */
-/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3fooi,_Z3fooi\.resolver\n" 
0 } } */
+/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3fooi, 
@gnu_indirect_function\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3fooi,_Z3fooi\.resolver\n" 
1 } } */
diff --git a/gcc/testsuite/g++.target/riscv/mv-symbols3.C 
b/gcc/testsuite/g++.target/riscv/mv-symbols3.C
index 6596a277f6a9..2a7b2bc8e429 100644
--- a/gcc/testsuite/g++.target/riscv/mv-symbols3.C
+++ b/gcc/testsuite/g++.target/riscv/mv-symbols3.C
@@ -36,10 +36,10 @@ int bar()
 /* { dg-final { scan-assembler-times "\n_Z3foov\.default:\n" 0 } } */
 /* { dg-final { scan-assembler-times "\n_Z3foov\.arch__v:\n" 0 } } */
 /* { dg-final { scan-assembler-times "\n_Z3foov\.arch__zba__zbb:\n" 0 } } */
-/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 0 } } */
 /* { dg-final { scan-assembler-times "\n\tcall\t_Z3foov(?:@plt)?\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\t\.type\t_Z3foov, 
@gnu_indirect_function\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3foov,_Z3foov\.resolver\n" 
0 } } */
 
 /* { dg-final { scan-assembler-times "\n_Z3fooi\.default:\n" 0 } } */
 /* { dg-final { scan-assembler-times "\n_Z3fooi\.arch__v:\n" 0 } } */
diff --git a/gcc/testsuite/g++.target/riscv/mv-symbols4.C 
b/gcc/testsuite/g++.target/riscv/mv-symbols4.C
index 83d51e337066..7665e775239b 100644
--- a/gcc/testsuite/g++.target/riscv/mv-symbols4.C
+++ b/gcc/testsuite/g++.target/riscv/mv-symbols4.C
@@ -50,7 +50,7 @@ int bar()
 /* { dg-final { scan-assembler-times "\n_Z3fooi\.default:\n" 1 } } */
 /* { dg-final { scan-assembler-times "\n_Z3fooi\.arch__v:\n" 0 } } */
 /* { dg-final { scan-assembler-times "\n_Z3fooi\.arch__zba__zbb:\n" 0 } } */
-/* { dg-final { scan-assembler-times "\n_Z3fooi\.resolver:\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n_Z3fooi\.resolver:\n" 1 } } */
 /* { dg-final { scan-assembler-times "\n\tcall\t_Z3fooi(?:@plt)?\n" 0 } } */
-/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3fooi, 
@gnu_indirect_function\n" 0 } } */
-/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3fooi,_Z3fooi\.resolver\n" 
0 } } */
+/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3fooi, 
@gnu_indirect_function\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3fooi,_Z3fooi\.resolver\n" 
1 } } */
diff --git a/gcc/testsuite/g++.target/riscv/mv-symbols5.C 
b/gcc/testsuite/g++.target/riscv/mv-symbols5.C
index c92421ab0fb3..5ca31851618d 100644
--- a/gcc/testsuite/g++.target/riscv/mv-symbols5.C
+++ b/gcc/testsuite/g++.target/riscv/mv-symbols5.C
@@ -48,10 +48,10 @@ int bar()
 /* { dg-final { scan-assembler-times "\n_Z3foov\.default:\n" 0 } } */
 /* { dg-final { scan-assembler-times "\n_Z3foov\.arch__v:\n" 1 } } */
 /* { dg-final { scan-assembler-times "\n_Z3foov\.arch__zba__zbb:\n" 1 } } */
-/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 0 } } */
 /* { dg-final { scan-assembler-times "\n\tcall\t_Z3foov(?:@plt)?\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\t\.type\t_Z3foov, 
@gnu_indirect_function\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3foov,_Z3foov\.resolver\n" 
0 } } */
 
 /* { dg-final { scan-assembler-times "\n_Z3fooi\.default:\n" 0 } } */
 /* { dg-final { scan-assembler-times "\n_Z3fooi\.arch__v:\n" 1 } } */
diff --git a/gcc/testsuite/g++.target/riscv/mvc-symbols3.C 
b/gcc/testsuite/g++.target/riscv/mvc-symbols3.C
index 78f027b0457e..e3987eaa75a5 100644
--- a/gcc/testsuite/g++.target/riscv/mvc-symbols3.C
+++ b/gcc/testsuite/g++.target/riscv/mvc-symbols3.C
@@ -28,15 +28,15 @@ int bar(int x)
 /* { dg-final { scan-assembler-times "\n_Z3foov\.default:\n" 0 } } */
 /* { dg-final { scan-assembler-times "\n_Z3foov\.arch__v:\n" 0 } } */
 /* { dg-final { scan-assembler-times "\n_Z3foov\.arch__zba__zbb:\n" 0 } } */
-/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 0 } } */
 /* { dg-final { scan-assembler-times "\n\tcall\t_Z3foov(?:@plt)?\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\t\.type\t_Z3foov, 
@gnu_indirect_function\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3foov,_Z3foov\.resolver\n" 
0 } } */
 
 /* { dg-final { scan-assembler-times "\n_Z3fooi\.default:\n" 0 } } */
 /* { dg-final { scan-assembler-times "\n_Z3fooi\.arch__v:\n" 0 } } */
 /* { dg-final { scan-assembler-times "\n_Z3fooi\.arch__zba__zbb:\n" 0 } } */
-/* { dg-final { scan-assembler-times "\n_Z3fooi\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3fooi\.resolver:\n" 0 } } */
 /* { dg-final { scan-assembler-times "\n\tcall\t_Z3fooi(?:@plt)?\n" 1 } } */
-/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3fooi, 
@gnu_indirect_function\n" 1 } } */
-/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3fooi,_Z3fooi\.resolver\n" 
1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3fooi, 
@gnu_indirect_function\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3fooi,_Z3fooi\.resolver\n" 
0 } } */

Reply via email to