diff mbox series

C and CPP: Add flag to generate resolver at default version implementation.

Message ID 20241030143734.2201072-1-alfie.richards@arm.com
State New
Headers show
Series C and CPP: Add flag to generate resolver at default version implementation. | expand

Commit Message

Alfie Richards Oct. 30, 2024, 2:37 p.m. UTC
Hi all,

This patch adds the TARGET_CREATE_FMV_DISPATCHER_AT_DEFAULT_IMPL hook
which changes FMV behavior for target_version functions to match the Arm
C Language Extension.

The functional differences consist of:
1. Generating the resolver for the dispatched symbol at the site of the
   default version definition.
2. Mangling non-default FMV annotated functions even when no other
   versions are present.

This allows for better behavior when definitions are spread across
different TU's as one resolver will be created and the
[ACLE](https://github.com/ARM-software/acle/blob/main/main/acle.md#function-multi-versioning)
stipulates that the default implementation must have visibility of all
other versions so that resolver will be complete. This also matches
Clangs behavior.

The only remaining discrepancy I'm aware of when targeting AArch64 is we
do not allow the coexistence of target_version and target_clone, which
is specified as supported in the ACLE but I will look at later.

This patch requires my previous patch enabling FMV in C.

Note this hook is only true on Aarch64, so other targets will not be
affected.

To enable these functionality changes I added mangling to the initial
processing of functions in the C and CPP frontends, and changed the
logic for the creation of resolver bodies to create at default
declaration implementations.

Additionally, the previous naming logic relied on the fact that if there
existed a call to a dispatched function the resolver would also be
created which would do some renaming. As that no longer is guaranteed
this patch hacks on the assembler names to make them correct.

Reg tested on AArch64 and X86_64.
Bootstrapped tested on aarch64-none-linux-gnu and
x86_64-unknown-linux-gnu.

Ok for master?

gcc/ChangeLog:

	* attribs.cc (make_dispatcher_decl): Add optional name parameter.
	(is_function_default_version): Add check_versioned parameter.
	* attribs.h (make_dispatcher_decl): Add optional name parameter.
	(is_function_default_version): Add optional check_versioned parameter.
	* cgraphunit.cc (cgraph_node::analyze): Change dispatcher creation logic.
	* config/aarch64/aarch64.cc (get_assembler_name_without_default): New.
	(get_suffixed_assembler_name): Change to use get_assembler_name_without_default.
	(aarch64_get_function_versions_dispatcher): Change to use get_assembler_name_without_default.
	* config/aarch64/aarch64.h (TARGET_CREATE_FMV_DISPATCHER_AT_DEFAULT_IMPL): New.
	* defaults.h (TARGET_CREATE_FMV_DISPATCHER_AT_DEFAULT_IMPL): New.

gcc/c/ChangeLog:

	* c-decl.cc (diagnose_mismatched_decls): Simplify logic.
	(start_function): Add mark for versioned functions.

gcc/cp/ChangeLog:

	* class.cc (add_method): Remove argument for maybe_version_functions.
	* cp-tree.h (maybe_version_functions): Remove parameter for maybe_version_functions.
	* decl.cc (decls_match): Simplify logic.
	(maybe_version_functions): Remove parameter.
	(start_preparsed_function): Add mark for versioned functions.

gcc/testsuite/ChangeLog:

	* g++.target/aarch64/mv-1.C: Update.
	* g++.target/aarch64/mv-symbols2.C: Update.
	* g++.target/aarch64/mv-symbols3.C: Update.
	* g++.target/aarch64/mv-symbols4.C: Update.
	* g++.target/aarch64/mv-symbols5.C: Update.
	* g++.target/aarch64/mv-symbols6.C: Update.
	* gcc.target/aarch64/mv-symbols1.c: Update.
	* gcc.target/aarch64/mv-symbols2.c: Update.
	* gcc.target/aarch64/mv-symbols3.c: Update.
	* gcc.target/aarch64/mv-symbols5.c: Update.
	* gcc.target/aarch64/mv-symbols6.c: Update.
---
 gcc/attribs.cc                                | 30 ++++++++++-----
 gcc/attribs.h                                 |  5 ++-
 gcc/c/c-decl.cc                               | 16 +++++---
 gcc/cgraphunit.cc                             | 38 ++++++++++++++-----
 gcc/config/aarch64/aarch64.cc                 | 31 ++++++++++++---
 gcc/config/aarch64/aarch64.h                  |  2 +
 gcc/cp/class.cc                               |  2 +-
 gcc/cp/cp-tree.h                              |  2 +-
 gcc/cp/decl.cc                                | 18 ++++++---
 gcc/defaults.h                                |  9 +++++
 gcc/testsuite/g++.target/aarch64/mv-1.C       |  4 ++
 .../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          |  6 +++
 .../gcc.target/aarch64/mv-symbols1.c          |  1 -
 .../gcc.target/aarch64/mv-symbols2.c          |  6 +--
 .../gcc.target/aarch64/mv-symbols3.c          |  6 +--
 .../gcc.target/aarch64/mv-symbols5.c          |  6 +--
 .../gcc.target/aarch64/mv-symbols6.c          |  4 ++
 21 files changed, 150 insertions(+), 66 deletions(-)
diff mbox series

Patch

diff --git a/gcc/attribs.cc b/gcc/attribs.cc
index 0be7b83b264..077493073e2 100644
--- a/gcc/attribs.cc
+++ b/gcc/attribs.cc
@@ -1246,13 +1246,17 @@  common_function_versions (tree fn1, tree fn2)
    by the front-end.  Return the decl created.  */
 
 tree
-make_dispatcher_decl (const tree decl)
+make_dispatcher_decl (const tree decl, const char *name)
 {
   tree func_decl;
-  char *func_name;
   tree fn_type, func_type;
 
-  func_name = xstrdup (IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (decl)));
+  char *func_name;
+
+  if (name)
+    func_name = xstrdup (name);
+  else
+    func_name = xstrdup (IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (decl)));
 
   fn_type = TREE_TYPE (decl);
   func_type = build_function_type (TREE_TYPE (fn_type),
@@ -1273,17 +1277,25 @@  make_dispatcher_decl (const tree decl)
   return func_decl;  
 }
 
-/* Returns true if DECL is multi-versioned using the target attribute, and this
-   is the default version.  This function can only be used for targets that do
-   not support the "target_version" attribute.  */
+/* If allow_unversioned is false, returns true if DECL has been marked as
+   multiversioned, is multi-versioned using the an attribute, and this is
+   the default version.
+   If allow_unversioned is true, then does not require the DECL is marked as
+   versioned and returns true if the function could be a default function,
+   ie could be unannotated.  */
 
 bool
-is_function_default_version (const tree decl)
+is_function_default_version (const tree decl, bool allow_unversioned)
 {
   if (TREE_CODE (decl) != FUNCTION_DECL
-      || !DECL_FUNCTION_VERSIONED (decl))
+      || (allow_unversioned && !DECL_FUNCTION_VERSIONED (decl)))
     return false;
-  tree attr = lookup_attribute ("target", DECL_ATTRIBUTES (decl));
+  tree attr = lookup_attribute (TARGET_HAS_FMV_TARGET_ATTRIBUTE
+				? "target" : "target_version",
+				DECL_ATTRIBUTES (decl));
+  /* An unannotated function can be default  */
+  if (!allow_unversioned && !attr)
+    return true;
   gcc_assert (attr);
   attr = TREE_VALUE (TREE_VALUE (attr));
   return (TREE_CODE (attr) == STRING_CST
diff --git a/gcc/attribs.h b/gcc/attribs.h
index 00a83a785b4..390e6165e22 100644
--- a/gcc/attribs.h
+++ b/gcc/attribs.h
@@ -55,8 +55,9 @@  extern struct scoped_attributes *
 
 extern char *sorted_attr_string (tree);
 extern bool common_function_versions (tree, tree);
-extern tree make_dispatcher_decl (const tree);
-extern bool is_function_default_version (const tree);
+extern tree
+make_dispatcher_decl (const tree, const char * = NULL);
+extern bool is_function_default_version (const tree, bool check_versioned = true);
 extern void handle_ignored_attributes_option (vec<char *> *);
 
 /* Return a type like TTYPE except that its TYPE_ATTRIBUTES
diff --git a/gcc/c/c-decl.cc b/gcc/c/c-decl.cc
index 686283cced6..01c69b95dc0 100644
--- a/gcc/c/c-decl.cc
+++ b/gcc/c/c-decl.cc
@@ -2455,16 +2455,11 @@  diagnose_mismatched_decls (tree newdecl, tree olddecl,
 	      locate_old_decl (olddecl);
 	      return false;
 	    }
-	  /* Only record if we are seeing either of these for the first
-	     time.  */
-	  bool record = (!DECL_FUNCTION_VERSIONED (newdecl)
-			 || !DECL_FUNCTION_VERSIONED (olddecl));
 
 	  maybe_mark_function_versioned (newdecl);
 	  maybe_mark_function_versioned (olddecl);
 
-	  if (record)
-	    cgraph_node::record_function_versions (olddecl, newdecl);
+	  cgraph_node::record_function_versions (olddecl, newdecl);
 	}
 
       else if (DECL_INITIAL (newdecl))
@@ -10926,6 +10921,15 @@  start_function (struct c_declspecs *declspecs, struct c_declarator *declarator,
       warn_parm_array_mismatch (origloc, old_decl, parms);
     }
 
+  /* To enable versions to be created across TU's we mark and mangle all
+     non-default versioned functions.  */
+  if (TARGET_CREATE_FMV_DISPATCHER_AT_DEFAULT_IMPL
+      && lookup_attribute (TARGET_HAS_FMV_TARGET_ATTRIBUTE
+			   ? "target" : "target_version",
+			   DECL_ATTRIBUTES (decl1)))
+    if (!is_function_default_version (decl1, false))
+      maybe_mark_function_versioned (decl1);
+
   /* Record the decl so that the function name is defined.
      If we already have a decl for this name, and it is a FUNCTION_DECL,
      use the old decl.  */
diff --git a/gcc/cgraphunit.cc b/gcc/cgraphunit.cc
index 2bd0289ffba..0744a0ea0e0 100644
--- a/gcc/cgraphunit.cc
+++ b/gcc/cgraphunit.cc
@@ -660,21 +660,39 @@  cgraph_node::analyze (void)
     resolve_alias (cgraph_node::get (alias_target), transparent_alias);
   else if (dispatcher_function)
     {
-      /* Generate the dispatcher body of multi-versioned functions.  */
-      cgraph_function_version_info *dispatcher_version_info
-	= function_version ();
-      if (dispatcher_version_info != NULL
-          && (dispatcher_version_info->dispatcher_resolver
-	      == NULL_TREE))
+      if (!TARGET_CREATE_FMV_DISPATCHER_AT_DEFAULT_IMPL)
 	{
-	  tree resolver = NULL_TREE;
-	  gcc_assert (targetm.generate_version_dispatcher_body);
-	  resolver = targetm.generate_version_dispatcher_body (this);
-	  gcc_assert (resolver != NULL_TREE);
+	  /* Generate the dispatcher body of multi-versioned functions at
+	     the first point where the dispatched symbol has been called.  */
+	  cgraph_function_version_info *dispatcher_version_info
+	    = function_version ();
+	  if (dispatcher_version_info != NULL
+	      && (dispatcher_version_info->dispatcher_resolver
+	          == NULL_TREE))
+	    {
+	      tree resolver = NULL_TREE;
+	      gcc_assert (targetm.generate_version_dispatcher_body);
+	      resolver = targetm.generate_version_dispatcher_body (this);
+	      gcc_assert (resolver != NULL_TREE);
+	    }
 	}
     }
   else
     {
+      /* Create the dispatcher at the default version implementation.  */
+      if (TARGET_CREATE_FMV_DISPATCHER_AT_DEFAULT_IMPL
+	  && DECL_FUNCTION_VERSIONED (decl)
+	  &&  is_function_default_version(decl, false) &&  definition)
+	{
+	  tree idecl = targetm.get_function_versions_dispatcher (decl);
+	  cgraph_node *inode = cgraph_node::get (idecl);
+
+	  tree resolver = NULL_TREE;
+	  gcc_assert (targetm.generate_version_dispatcher_body);
+	  resolver = targetm.generate_version_dispatcher_body (inode);
+	  gcc_assert (resolver != NULL_TREE);
+	}
+
       push_cfun (DECL_STRUCT_FUNCTION (decl));
 
       assign_assembler_name_if_needed (decl);
diff --git a/gcc/config/aarch64/aarch64.cc b/gcc/config/aarch64/aarch64.cc
index 3ab550acc7c..754e0bbdbaa 100644
--- a/gcc/config/aarch64/aarch64.cc
+++ b/gcc/config/aarch64/aarch64.cc
@@ -20202,6 +20202,18 @@  aarch64_mangle_decl_assembler_name (tree decl, tree id)
   return id;
 }
 
+static std::string
+get_assembler_name_without_default (tree default_decl)
+{
+  std::string name = IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (default_decl));
+
+  auto size = name.size ();
+  if (size >= 8 && name.compare (size - 8, 8, ".default") == 0)
+    name.resize (size - 8);
+
+  return name;
+}
+
 /* Return an identifier for the base assembler name of a versioned function.
    This is computed by taking the default version's assembler name, and
    stripping off the ".default" suffix if it's already been appended.  */
@@ -20209,11 +20221,7 @@  aarch64_mangle_decl_assembler_name (tree decl, tree id)
 static tree
 get_suffixed_assembler_name (tree default_decl, const char *suffix)
 {
-  std::string name = IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (default_decl));
-
-  auto size = name.size ();
-  if (size >= 8 && name.compare (size - 8, 8, ".default") == 0)
-    name.resize (size - 8);
+  std::string name = get_assembler_name_without_default(default_decl);
   name += suffix;
   return get_identifier (name.c_str());
 }
@@ -20670,8 +20678,19 @@  aarch64_get_function_versions_dispatcher (void *decl)
       struct cgraph_node *dispatcher_node = NULL;
       struct cgraph_function_version_info *dispatcher_version_info = NULL;
 
+      /* Strip the suffix from the default version of the function to get the
+	 dispatcher name. */
+
+      std::string name =
+	get_assembler_name_without_default(default_node->decl);
+
+      dispatch_decl = make_dispatcher_decl (default_node->decl, name.c_str ());
+
+      /* Mark the assembler name as set to prevent it getting mangled again .*/
+      SET_DECL_ASSEMBLER_NAME (dispatch_decl,
+			       DECL_ASSEMBLER_NAME (dispatch_decl));
+
       /* Right now, the dispatching is done via ifunc.  */
-      dispatch_decl = make_dispatcher_decl (default_node->decl);
       TREE_NOTHROW (dispatch_decl) = TREE_NOTHROW (fn);
 
       dispatcher_node = cgraph_node::get_create (dispatch_decl);
diff --git a/gcc/config/aarch64/aarch64.h b/gcc/config/aarch64/aarch64.h
index 593319fd472..a674b83fa96 100644
--- a/gcc/config/aarch64/aarch64.h
+++ b/gcc/config/aarch64/aarch64.h
@@ -1393,6 +1393,8 @@  extern enum aarch64_code_model aarch64_cmodel;
 
 #define TARGET_HAS_FMV_TARGET_ATTRIBUTE 0
 
+#define TARGET_CREATE_FMV_DISPATCHER_AT_DEFAULT_IMPL 1
+
 #define TARGET_SUPPORTS_WIDE_INT 1
 
 /* Modes valid for AdvSIMD D registers, i.e. that fit in half a Q register.  */
diff --git a/gcc/cp/class.cc b/gcc/cp/class.cc
index 8c39bb4a76b..7dd2075dd3c 100644
--- a/gcc/cp/class.cc
+++ b/gcc/cp/class.cc
@@ -1402,7 +1402,7 @@  add_method (tree type, tree method, bool via_using)
       /* If these are versions of the same function, process and
 	 move on.  */
       if (TREE_CODE (fn) == FUNCTION_DECL
-	  && maybe_version_functions (method, fn, true))
+	  && maybe_version_functions (method, fn))
 	continue;
 
       if (DECL_INHERITED_CTOR (method))
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 94ee550bd9c..182c946fcc7 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -6998,7 +6998,7 @@  extern void determine_local_discriminator	(tree, tree = NULL_TREE);
 extern bool member_like_constrained_friend_p	(tree);
 extern bool fns_correspond			(tree, tree);
 extern int decls_match				(tree, tree, bool = true);
-extern bool maybe_version_functions		(tree, tree, bool);
+extern bool maybe_version_functions		(tree, tree);
 extern bool validate_constexpr_redeclaration	(tree, tree);
 extern bool merge_default_template_args		(tree, tree, bool);
 extern tree duplicate_decls			(tree, tree,
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index 0c5b5c06a12..1ee09b9b739 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -1213,9 +1213,7 @@  decls_match (tree newdecl, tree olddecl, bool record_versions /* = true */)
 	  && targetm.target_option.function_versions (newdecl, olddecl))
 	{
 	  if (record_versions)
-	    maybe_version_functions (newdecl, olddecl,
-				     (!DECL_FUNCTION_VERSIONED (newdecl)
-				      || !DECL_FUNCTION_VERSIONED (olddecl)));
+	    maybe_version_functions (newdecl, olddecl);
 	  return 0;
 	}
     }
@@ -1286,7 +1284,7 @@  maybe_mark_function_versioned (tree decl)
    If RECORD is set to true, record function versions.  */
 
 bool
-maybe_version_functions (tree newdecl, tree olddecl, bool record)
+maybe_version_functions (tree newdecl, tree olddecl)
 {
   if (!targetm.target_option.function_versions (newdecl, olddecl))
     return false;
@@ -1309,8 +1307,7 @@  maybe_version_functions (tree newdecl, tree olddecl, bool record)
       maybe_mark_function_versioned (newdecl);
     }
 
-  if (record)
-    cgraph_node::record_function_versions (olddecl, newdecl);
+  cgraph_node::record_function_versions (olddecl, newdecl);
 
   return true;
 }
@@ -18378,6 +18375,15 @@  start_preparsed_function (tree decl1, tree attrs, int flags)
   if (!DECL_OMP_DECLARE_REDUCTION_P (decl1))
     start_lambda_scope (decl1);
 
+  /* To enable versions to be created across TU's we mark and mangle all
+     non-default versioned functions.  */
+  if (TARGET_CREATE_FMV_DISPATCHER_AT_DEFAULT_IMPL
+      && lookup_attribute (TARGET_HAS_FMV_TARGET_ATTRIBUTE
+			   ? "target" : "target_version",
+			   DECL_ATTRIBUTES (decl1)))
+    if (!is_function_default_version (decl1, false))
+      maybe_mark_function_versioned (decl1);
+
   return true;
 }
 
diff --git a/gcc/defaults.h b/gcc/defaults.h
index ac2d25852ab..959a4ac8301 100644
--- a/gcc/defaults.h
+++ b/gcc/defaults.h
@@ -874,6 +874,15 @@  see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
 #define TARGET_HAS_FMV_TARGET_ATTRIBUTE 1
 #endif
 
+/* Indicates if the target should generate FMV dispatchers at the
+   site of the default version implementation rather than at the call sites
+   to the function.
+   The creation at default dispatcher is as defined in the Arm C language
+   extension and esures batter behavior when defining function versions
+   accross translation units.  */
+#ifndef TARGET_CREATE_FMV_DISPATCHER_AT_DEFAULT_IMPL
+#define TARGET_CREATE_FMV_DISPATCHER_AT_DEFAULT_IMPL 0
+#endif
 
 /* Select a format to encode pointers in exception handling data.  We
    prefer those that result in fewer dynamic relocations.  Assume no
diff --git a/gcc/testsuite/g++.target/aarch64/mv-1.C b/gcc/testsuite/g++.target/aarch64/mv-1.C
index b4b0e5e3fea..1acd7023394 100644
--- a/gcc/testsuite/g++.target/aarch64/mv-1.C
+++ b/gcc/testsuite/g++.target/aarch64/mv-1.C
@@ -36,3 +36,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-symbols2.C b/gcc/testsuite/g++.target/aarch64/mv-symbols2.C
index f0c7967a97a..7c8e1bcbed1 100644
--- a/gcc/testsuite/g++.target/aarch64/mv-symbols2.C
+++ b/gcc/testsuite/g++.target/aarch64/mv-symbols2.C
@@ -40,13 +40,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 3d30e27deb8..2e8a27f0522 100644
--- a/gcc/testsuite/g++.target/aarch64/mv-symbols3.C
+++ b/gcc/testsuite/g++.target/aarch64/mv-symbols3.C
@@ -28,10 +28,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 73e3279ec31..04cac9d118a 100644
--- a/gcc/testsuite/g++.target/aarch64/mv-symbols4.C
+++ b/gcc/testsuite/g++.target/aarch64/mv-symbols4.C
@@ -43,6 +43,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 05d1379f53e..faa67909c64 100644
--- a/gcc/testsuite/g++.target/aarch64/mv-symbols5.C
+++ b/gcc/testsuite/g++.target/aarch64/mv-symbols5.C
@@ -43,10 +43,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
index 52105449e4a..300ae357a77 100644
--- a/gcc/testsuite/g++.target/aarch64/mv-symbols6.C
+++ b/gcc/testsuite/g++.target/aarch64/mv-symbols6.C
@@ -7,6 +7,11 @@  int foo ()
   return 1;
 }
 
+int bar()
+{
+  return foo();
+}
+
 /* It is not overly clear what the correct behaviour is in this case.
    This test serves more as a test of consistency for this case rather
    than a test of correctness.  */
@@ -14,3 +19,4 @@  int foo ()
 /* { dg-final { scan-assembler-times "\n_Z3foov:\n" 1 } } */
 /* { dg-final { scan-assembler-times "\n_Z3foov\.default:\n" 0 } } */
 /* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 0 } } */
+/* { dg-final { scan-assembler-times "bl\t_Z3foov\n" 1 } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/mv-symbols1.c b/gcc/testsuite/gcc.target/aarch64/mv-symbols1.c
index b7a7bc2147a..437f04cb358 100644
--- a/gcc/testsuite/gcc.target/aarch64/mv-symbols1.c
+++ b/gcc/testsuite/gcc.target/aarch64/mv-symbols1.c
@@ -3,7 +3,6 @@ 
 
 // Basic case of fmv correctness with all functions and use in one TU. 
 
-__attribute__((target_version("default")))
 int foo ()
 {
   return 1;
diff --git a/gcc/testsuite/gcc.target/aarch64/mv-symbols2.c b/gcc/testsuite/gcc.target/aarch64/mv-symbols2.c
index dfa9570da65..7debb6e26f3 100644
--- a/gcc/testsuite/gcc.target/aarch64/mv-symbols2.c
+++ b/gcc/testsuite/gcc.target/aarch64/mv-symbols2.c
@@ -23,6 +23,6 @@  int foo ()
 /* { dg-final { scan-assembler-times "\nfoo\.default:\n" 1 } } */
 /* { dg-final { scan-assembler-times "\nfoo\._Mdotprod:\n" 1 } } */
 /* { dg-final { scan-assembler-times "\nfoo\._MsveMsve2:\n" 1 } } */
-/* { dg-final { scan-assembler-times "\nfoo\.resolver:\n" 0 } } */
-/* { dg-final { scan-assembler-times "\n\t\.type\tfoo, %gnu_indirect_function\n" 0 } } */
-/* { dg-final { scan-assembler-times "\n\t\.set\tfoo,foo\.resolver\n" 0 } } */
+/* { dg-final { scan-assembler-times "\nfoo\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.type\tfoo, %gnu_indirect_function\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\tfoo,foo\.resolver\n" 1 } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/mv-symbols3.c b/gcc/testsuite/gcc.target/aarch64/mv-symbols3.c
index c94610ab2da..5b8c16b86a7 100644
--- a/gcc/testsuite/gcc.target/aarch64/mv-symbols3.c
+++ b/gcc/testsuite/gcc.target/aarch64/mv-symbols3.c
@@ -20,7 +20,7 @@  int bar()
 /* { dg-final { scan-assembler-times "\nfoo\.default:\n" 0 } } */
 /* { dg-final { scan-assembler-times "\nfoo\._Mdotprod:\n" 0 } } */
 /* { dg-final { scan-assembler-times "\nfoo\._MsveMsve2:\n" 0 } } */
-/* { dg-final { scan-assembler-times "\nfoo\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\nfoo\.resolver:\n" 0 } } */
 /* { dg-final { scan-assembler-times "\n\tbl\tfoo\n" 1 } } */
-/* { dg-final { scan-assembler-times "\n\t\.type\tfoo, %gnu_indirect_function\n" 1 } } */
-/* { dg-final { scan-assembler-times "\n\t\.set\tfoo,foo\.resolver\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.type\tfoo, %gnu_indirect_function\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\tfoo,foo\.resolver\n" 0 } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/mv-symbols5.c b/gcc/testsuite/gcc.target/aarch64/mv-symbols5.c
index 95fa72583bb..82f7834dfea 100644
--- a/gcc/testsuite/gcc.target/aarch64/mv-symbols5.c
+++ b/gcc/testsuite/gcc.target/aarch64/mv-symbols5.c
@@ -29,7 +29,7 @@  int bar()
 /* { dg-final { scan-assembler-times "\nfoo\.default:\n" 0 } } */
 /* { dg-final { scan-assembler-times "\nfoo\._Mdotprod:\n" 1 } } */
 /* { dg-final { scan-assembler-times "\nfoo\._MsveMsve2:\n" 1 } } */
-/* { dg-final { scan-assembler-times "\nfoo\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\nfoo\.resolver:\n" 0 } } */
 /* { dg-final { scan-assembler-times "\n\tbl\tfoo\n" 1 } } */
-/* { dg-final { scan-assembler-times "\n\t\.type\tfoo, %gnu_indirect_function\n" 1 } } */
-/* { dg-final { scan-assembler-times "\n\t\.set\tfoo,foo\.resolver\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.type\tfoo, %gnu_indirect_function\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\tfoo,foo\.resolver\n" 0 } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/mv-symbols6.c b/gcc/testsuite/gcc.target/aarch64/mv-symbols6.c
index 8080502a4a4..de6ae7e9774 100644
--- a/gcc/testsuite/gcc.target/aarch64/mv-symbols6.c
+++ b/gcc/testsuite/gcc.target/aarch64/mv-symbols6.c
@@ -7,11 +7,15 @@  int foo ()
   return 1;
 }
 
+int bar() {
+  return foo();
+}
 
 /* It is not overly clear what the correct behaviour is in this case.
    This test serves more as a test of consistency for this case rather
    than a test of correctness.  */
 
 /* { dg-final { scan-assembler-times "\nfoo:\n" 1 } } */
+/* { dg-final { scan-assembler-times "bl\tfoo\n" 1 } } */
 /* { dg-final { scan-assembler-times "\nfoo\.default:\n" 0 } } */
 /* { dg-final { scan-assembler-times "\nfoo\.resolver:\n" 0 } } */