diff mbox series

[v2,1/1] C: Support Function multiversionsing in the C front end

Message ID 20241024125448.4426-2-alfie.richards@arm.com
State New
Headers show
Series Support for FMV in C front end. | expand

Commit Message

Alfie Richards Oct. 24, 2024, 12:54 p.m. UTC
This patch adds support for `target_version` function multiversioning to
the C frontend, specifically intended for enabling this for Aarch64
targets.

The functionality and behavior matches the CPP frontend. This is likely
to need to be changed later down the line for Aarch64 targets to
match the updated ACLE details but that is future work.

Note, in particular this adds a call to process_same_body_aliases
to the C frontend. this function is seems to be intended to be used
in the CPP and D frontends. However, not calling this function was
meaning cpp_implicit_aliases_done was false for the C cgraph nodes
and then references to the dispatched symbol were not being resolved
resulting in the nodes getting deleted and a segfault later.

I experimented with this patch on X86 and found that the assembler
produces a duplicate symbol error. I would like input from relevant
teams if there is interest in supporting this for their backends.

gcc/c-family/ChangeLog:

	* c-gimplify.cc (c_gimplify_expr): Process calls to FMV functions.

gcc/c/ChangeLog:

	* c-decl.cc (maybe_mark_function_versioned): New function.
	(diagnose_mismatched_decls): Add logic for target_version functions.
	(duplicate_decls): Add logic to differentiate target_version functions.
	(c_parse_final_cleanups): Add call to process_same_body_aliases.

gcc/ChangeLog:

	* calls.cc (get_c_function_version_dispatcher): New function.
	* calls.h (get_c_function_version_dispatcher): New function.

gcc/testsuite/ChangeLog:

	* g++.target/aarch64/mv-symbols6.C: New test.
	* gcc.target/aarch64/mv-1.c: New test.
	* gcc.target/aarch64/mv-symbols1.c: New test.
	* gcc.target/aarch64/mv-symbols2.c: New test.
	* gcc.target/aarch64/mv-symbols3.c: New test.
	* gcc.target/aarch64/mv-symbols4.c: New test.
	* gcc.target/aarch64/mv-symbols5.c: New test.
	* gcc.target/aarch64/mv-symbols6.c: New test.
	* gcc.target/aarch64/mvc-symbols1.c: New test.
	* gcc.target/aarch64/mvc-symbols2.c: New test.
	* gcc.target/aarch64/mvc-symbols3.c: New test.
	* gcc.target/aarch64/mvc-symbols4.c: New test.

This has been reg tested on Aarch64 and X86, and bootstrapped for
aarch64-none-linux-gnu and x86_64-unknown-linux-gnu.

OK for master?
---
 gcc/c-family/c-gimplify.cc                    | 11 ++++
 gcc/c/c-decl.cc                               | 63 ++++++++++++++++++-
 gcc/calls.cc                                  | 23 +++++++
 gcc/calls.h                                   |  1 +
 .../g++.target/aarch64/mv-symbols6.C          | 16 +++++
 gcc/testsuite/gcc.target/aarch64/mv-1.c       | 39 ++++++++++++
 .../gcc.target/aarch64/mv-symbols1.c          | 37 +++++++++++
 .../gcc.target/aarch64/mv-symbols2.c          | 28 +++++++++
 .../gcc.target/aarch64/mv-symbols3.c          | 26 ++++++++
 .../gcc.target/aarch64/mv-symbols4.c          | 29 +++++++++
 .../gcc.target/aarch64/mv-symbols5.c          | 35 +++++++++++
 .../gcc.target/aarch64/mv-symbols6.c          | 17 +++++
 .../gcc.target/aarch64/mvc-symbols1.c         | 25 ++++++++
 .../gcc.target/aarch64/mvc-symbols2.c         | 16 +++++
 .../gcc.target/aarch64/mvc-symbols3.c         | 19 ++++++
 .../gcc.target/aarch64/mvc-symbols4.c         | 12 ++++
 16 files changed, 396 insertions(+), 1 deletion(-)
 create mode 100644 gcc/testsuite/g++.target/aarch64/mv-symbols6.C
 create mode 100644 gcc/testsuite/gcc.target/aarch64/mv-1.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/mv-symbols1.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/mv-symbols2.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/mv-symbols3.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/mv-symbols4.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/mv-symbols5.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/mv-symbols6.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/mvc-symbols1.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/mvc-symbols2.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/mvc-symbols3.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/mvc-symbols4.c

Comments

Alfie Richards Oct. 31, 2024, 9:35 a.m. UTC | #1
Hi,

Ping for this patch

I'd really like to get this in for GCC 15 to align FMV with LLVM and avoid 
any divergence of use in code bases as much as possible as we push this more 
to users.

Thanks,
Alfie Richards

> On 24 Oct 2024, at 13:54, alfie.richards@arm.com wrote:
> 
> 
> This patch adds support for `target_version` function multiversioning to
> the C frontend, specifically intended for enabling this for Aarch64
> targets.
> 
> The functionality and behavior matches the CPP frontend. This is likely
> to need to be changed later down the line for Aarch64 targets to
> match the updated ACLE details but that is future work.
> 
> Note, in particular this adds a call to process_same_body_aliases
> to the C frontend. this function is seems to be intended to be used
> in the CPP and D frontends. However, not calling this function was
> meaning cpp_implicit_aliases_done was false for the C cgraph nodes
> and then references to the dispatched symbol were not being resolved
> resulting in the nodes getting deleted and a segfault later.
> 
> I experimented with this patch on X86 and found that the assembler
> produces a duplicate symbol error. I would like input from relevant
> teams if there is interest in supporting this for their backends.
> 
> gcc/c-family/ChangeLog:
> 
> 	* c-gimplify.cc (c_gimplify_expr): Process calls to FMV functions.
> 
> gcc/c/ChangeLog:
> 
> 	* c-decl.cc (maybe_mark_function_versioned): New function.
> 	(diagnose_mismatched_decls): Add logic for target_version functions.
> 	(duplicate_decls): Add logic to differentiate target_version functions.
> 	(c_parse_final_cleanups): Add call to process_same_body_aliases.
> 
> gcc/ChangeLog:
> 
> 	* calls.cc (get_c_function_version_dispatcher): New function.
> 	* calls.h (get_c_function_version_dispatcher): New function.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.target/aarch64/mv-symbols6.C: New test.
> 	* gcc.target/aarch64/mv-1.c: New test.
> 	* gcc.target/aarch64/mv-symbols1.c: New test.
> 	* gcc.target/aarch64/mv-symbols2.c: New test.
> 	* gcc.target/aarch64/mv-symbols3.c: New test.
> 	* gcc.target/aarch64/mv-symbols4.c: New test.
> 	* gcc.target/aarch64/mv-symbols5.c: New test.
> 	* gcc.target/aarch64/mv-symbols6.c: New test.
> 	* gcc.target/aarch64/mvc-symbols1.c: New test.
> 	* gcc.target/aarch64/mvc-symbols2.c: New test.
> 	* gcc.target/aarch64/mvc-symbols3.c: New test.
> 	* gcc.target/aarch64/mvc-symbols4.c: New test.
> 
> This has been reg tested on Aarch64 and X86, and bootstrapped for
> aarch64-none-linux-gnu and x86_64-unknown-linux-gnu.
> 
> OK for master?
> ---
> gcc/c-family/c-gimplify.cc                    | 11 ++++
> gcc/c/c-decl.cc                               | 63 ++++++++++++++++++-
> gcc/calls.cc                                  | 23 +++++++
> gcc/calls.h                                   |  1 +
> .../g++.target/aarch64/mv-symbols6.C          | 16 +++++
> gcc/testsuite/gcc.target/aarch64/mv-1.c       | 39 ++++++++++++
> .../gcc.target/aarch64/mv-symbols1.c          | 37 +++++++++++
> .../gcc.target/aarch64/mv-symbols2.c          | 28 +++++++++
> .../gcc.target/aarch64/mv-symbols3.c          | 26 ++++++++
> .../gcc.target/aarch64/mv-symbols4.c          | 29 +++++++++
> .../gcc.target/aarch64/mv-symbols5.c          | 35 +++++++++++
> .../gcc.target/aarch64/mv-symbols6.c          | 17 +++++
> .../gcc.target/aarch64/mvc-symbols1.c         | 25 ++++++++
> .../gcc.target/aarch64/mvc-symbols2.c         | 16 +++++
> .../gcc.target/aarch64/mvc-symbols3.c         | 19 ++++++
> .../gcc.target/aarch64/mvc-symbols4.c         | 12 ++++
> 16 files changed, 396 insertions(+), 1 deletion(-)
> create mode 100644 gcc/testsuite/g++.target/aarch64/mv-symbols6.C
> create mode 100644 gcc/testsuite/gcc.target/aarch64/mv-1.c
> create mode 100644 gcc/testsuite/gcc.target/aarch64/mv-symbols1.c
> create mode 100644 gcc/testsuite/gcc.target/aarch64/mv-symbols2.c
> create mode 100644 gcc/testsuite/gcc.target/aarch64/mv-symbols3.c
> create mode 100644 gcc/testsuite/gcc.target/aarch64/mv-symbols4.c
> create mode 100644 gcc/testsuite/gcc.target/aarch64/mv-symbols5.c
> create mode 100644 gcc/testsuite/gcc.target/aarch64/mv-symbols6.c
> create mode 100644 gcc/testsuite/gcc.target/aarch64/mvc-symbols1.c
> create mode 100644 gcc/testsuite/gcc.target/aarch64/mvc-symbols2.c
> create mode 100644 gcc/testsuite/gcc.target/aarch64/mvc-symbols3.c
> create mode 100644 gcc/testsuite/gcc.target/aarch64/mvc-symbols4.c
> 
> <v2-0001-C-Support-Function-multiversionsing-in-the-C-fron.patch>
Joseph Myers Oct. 31, 2024, 6:36 p.m. UTC | #2
On Thu, 24 Oct 2024, alfie.richards@arm.com wrote:

> -      if (DECL_INITIAL (newdecl))
> +      if ((DECL_INITIAL (newdecl) || DECL_EXTERNAL (newdecl))
> +	  && (DECL_INITIAL (olddecl) || DECL_EXTERNAL (olddecl))
> +	  && targetm.target_option.function_versions (newdecl, olddecl))
> +	{
> +	  /* Check if we are dealing with Function Multi Versioning using
> +	   the 'target_version' attribute.  */
> +	  tree olddecl_args = TYPE_ARG_TYPES (TREE_TYPE (olddecl));
> +	  tree newdecl_args = TYPE_ARG_TYPES (TREE_TYPE (newdecl));
> +	  /* Function Multi Versioned functions need to have the same
> +	     prototype and the target must support these too, otherwise
> +	     error.
> +	     TODO: should we be checking any other properties?  */
> +	  if (!comptypes (TREE_TYPE (olddecl), TREE_TYPE (newdecl))
> +	      || !comptypes (olddecl_args, newdecl_args))

olddecl_args and newdecl_args are lists, not types; you can't call 
comptypes on them.  comptypes_internal eventually calls 
function_types_compatible_p to check compatibility of function types; you 
should probably use comptypes on the types as a whole rather than 
reimplementing the various checks it does.  Note that you need to consider 
if e.g. you want to allow one function being prototyped and the other 
unprototyped (but compatible) - whether you want something stricter than 
compatibility (e.g. the "same type" notion).

Whatever the conclusion, please make sure there is thorough testsuite 
coverage for the various cases of types that might be the same, or 
compatible but not the same (one prototyped and one unprototyped; both 
prototyped but one having more information than the other, e.g. parameter 
pointing to an array where the array size is specified for only one), as 
well as cases that are not compatible.  I don't see any dg-error tests in 
this patch, so testsuite coverage needs to be expanded.

> @@ -3191,6 +3240,14 @@ duplicate_decls (tree newdecl, tree olddecl)
>        return false;
>      }
>  
> +  /* If both new and old are Function Multi Versioned functions then they are
> +     not duplicates.  */
> +  if (TREE_CODE (newdecl) == FUNCTION_DECL
> +      && TREE_CODE (olddecl) == FUNCTION_DECL
> +      && DECL_FUNCTION_VERSIONED (newdecl)
> +      && DECL_FUNCTION_VERSIONED (olddecl))
> +    return false;

Is that really the case?  I'd expect duplicates to be possible if there 
are multiple declarations of the same version of the function.  So there 
should be tests in the testsuite both for valid cases of declaring the 
same version multiple times, in the same or different scopes, and invalid 
cases that define the same version multiple times.
Alfie Richards Nov. 4, 2024, 9:23 a.m. UTC | #3
Hi Joseph,

Thank you for the review and nice to meet you.

@@ -3191,6 +3240,14 @@ duplicate_decls (tree newdecl, tree olddecl)
      return false;
    }

+  /* If both new and old are Function Multi Versioned functions then they are
+     not duplicates.  */
+  if (TREE_CODE (newdecl) == FUNCTION_DECL
+      && TREE_CODE (olddecl) == FUNCTION_DECL
+      && DECL_FUNCTION_VERSIONED (newdecl)
+      && DECL_FUNCTION_VERSIONED (olddecl))
+    return false;

Is that really the case?  I'd expect duplicates to be possible if there
are multiple declarations of the same version of the function.  So there
should be tests in the testsuite both for valid cases of declaring the
same version multiple times, in the same or different scopes, and invalid
cases that define the same version multiple times.

Ahh this is a problem. I hadn't considered the merging of declarations
and it leads to duplicate entries in the resolver. Thank you for pointing
this out.

Im working to find a way to fix this and merge declarations of functions
with the same target. It seems like the C frontend is written assuming one
declaration per name, which is normally a good assumption I suppose, but
this invalidates.

Im currently planning to do this at the `pushdecl` level, by iterating through
the `shadows` link in c bindings, and then only doing `duplicate_decls` on
functions with matching targets.

I will get back to you soon,

Alfie Richards
alfie.richards@arm.com
diff mbox series

Patch

diff --git a/gcc/c-family/c-gimplify.cc b/gcc/c-family/c-gimplify.cc
index 09ea1b79159..d288303d118 100644
--- a/gcc/c-family/c-gimplify.cc
+++ b/gcc/c-family/c-gimplify.cc
@@ -43,6 +43,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "context.h"
 #include "tree-pass.h"
 #include "internal-fn.h"
+#include "calls.h"
 
 /*  The gimplification pass converts the language-dependent trees
     (ld-trees) emitted by the parser into language-independent trees
@@ -904,6 +905,16 @@  c_gimplify_expr (tree *expr_p, gimple_seq *pre_p ATTRIBUTE_UNUSED,
     case CALL_EXPR:
       {
 	tree fndecl = get_callee_fndecl (*expr_p);
+
+	/* Change any calls to a multiversioned function to instead
+	 * be a call to the dispatched symbol.  */
+	if (fndecl && DECL_FUNCTION_VERSIONED (fndecl))
+	  if (tree dis = get_c_function_version_dispatcher (fndecl))
+	    {
+	      dis = build_fold_addr_expr_loc (EXPR_LOCATION (dis), dis);
+	      CALL_EXPR_FN (*expr_p) = dis;
+	    }
+
 	if (fndecl
 	    && fndecl_built_in_p (fndecl, BUILT_IN_CLZG, BUILT_IN_CTZG)
 	    && call_expr_nargs (*expr_p) == 2
diff --git a/gcc/c/c-decl.cc b/gcc/c/c-decl.cc
index 1827bbf0646..686283cced6 100644
--- a/gcc/c/c-decl.cc
+++ b/gcc/c/c-decl.cc
@@ -62,6 +62,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "omp-general.h"
 #include "omp-offload.h"  /* For offload_vars.  */
 #include "c-parser.h"
+#include "tree.h"
 
 #include "tree-pretty-print.h"
 
@@ -2098,6 +2099,22 @@  previous_tag (tree type)
   return NULL_TREE;
 }
 
+/* Subroutine to mark functions as versioned when using the attribute
+   'target_version'.  */
+
+static void
+maybe_mark_function_versioned (tree decl)
+{
+  if (!DECL_FUNCTION_VERSIONED (decl))
+    {
+      DECL_FUNCTION_VERSIONED (decl) = true;
+
+      tree mangled_name
+	= targetm.mangle_decl_assembler_name (decl, DECL_NAME (decl));
+      SET_DECL_ASSEMBLER_NAME (decl, mangled_name);
+    }
+}
+
 /* Subroutine of duplicate_decls.  Compare NEWDECL to OLDDECL.
    Returns true if the caller should proceed to merge the two, false
    if OLDDECL should simply be discarded.  As a side effect, issues
@@ -2418,7 +2435,39 @@  diagnose_mismatched_decls (tree newdecl, tree olddecl,
 	    }
 	}
 
-      if (DECL_INITIAL (newdecl))
+      if ((DECL_INITIAL (newdecl) || DECL_EXTERNAL (newdecl))
+	  && (DECL_INITIAL (olddecl) || DECL_EXTERNAL (olddecl))
+	  && targetm.target_option.function_versions (newdecl, olddecl))
+	{
+	  /* Check if we are dealing with Function Multi Versioning using
+	   the 'target_version' attribute.  */
+	  tree olddecl_args = TYPE_ARG_TYPES (TREE_TYPE (olddecl));
+	  tree newdecl_args = TYPE_ARG_TYPES (TREE_TYPE (newdecl));
+	  /* Function Multi Versioned functions need to have the same
+	     prototype and the target must support these too, otherwise
+	     error.
+	     TODO: should we be checking any other properties?  */
+	  if (!comptypes (TREE_TYPE (olddecl), TREE_TYPE (newdecl))
+	      || !comptypes (olddecl_args, newdecl_args))
+	    {
+	      auto_diagnostic_group d;
+	      error ("redefinition of %q+D", newdecl);
+	      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);
+	}
+
+      else if (DECL_INITIAL (newdecl))
 	{
 	  if (DECL_INITIAL (olddecl))
 	    {
@@ -3191,6 +3240,14 @@  duplicate_decls (tree newdecl, tree olddecl)
       return false;
     }
 
+  /* If both new and old are Function Multi Versioned functions then they are
+     not duplicates.  */
+  if (TREE_CODE (newdecl) == FUNCTION_DECL
+      && TREE_CODE (olddecl) == FUNCTION_DECL
+      && DECL_FUNCTION_VERSIONED (newdecl)
+      && DECL_FUNCTION_VERSIONED (olddecl))
+    return false;
+
   merge_decls (newdecl, olddecl, newtype, oldtype);
 
   /* The NEWDECL will no longer be needed.
@@ -13686,6 +13743,10 @@  c_parse_final_cleanups (void)
     c_write_global_declarations_1 (BLOCK_VARS (DECL_INITIAL (t)));
   c_write_global_declarations_1 (BLOCK_VARS (ext_block));
 
+  /* Call this to set cpp_implicit_aliases_done on all nodes.  This is
+     important for function multiversioning aliases to get resolved.  */
+  symtab->process_same_body_aliases ();
+
   if (!in_lto_p)
     free_attr_access_data ();
 
diff --git a/gcc/calls.cc b/gcc/calls.cc
index c5c26f65280..09676a40fc8 100644
--- a/gcc/calls.cc
+++ b/gcc/calls.cc
@@ -5386,3 +5386,26 @@  cxx17_empty_base_field_p (const_tree field)
 	  && RECORD_OR_UNION_TYPE_P (TREE_TYPE (field))
 	  && !lookup_attribute ("no_unique_address", DECL_ATTRIBUTES (field)));
 }
+
+/* Returns the decl of the dispatcher function if FN is a function version.  */
+
+tree
+get_c_function_version_dispatcher (tree fn)
+{
+  tree dispatcher_decl = NULL;
+
+  gcc_assert (TREE_CODE (fn) == FUNCTION_DECL
+	      && DECL_FUNCTION_VERSIONED (fn));
+
+  gcc_assert (targetm.get_function_versions_dispatcher);
+  dispatcher_decl = targetm.get_function_versions_dispatcher (fn);
+
+  if (dispatcher_decl == NULL)
+    {
+      error_at (input_location, "use of multiversioned function "
+				"without a default");
+      return NULL;
+    }
+
+  return dispatcher_decl;
+}
diff --git a/gcc/calls.h b/gcc/calls.h
index 464a4e34e33..aa46853ba63 100644
--- a/gcc/calls.h
+++ b/gcc/calls.h
@@ -134,5 +134,6 @@  extern void maybe_complain_about_tail_call (tree, const char *);
 
 extern rtx rtx_for_static_chain (const_tree, bool);
 extern bool cxx17_empty_base_field_p (const_tree);
+extern tree get_c_function_version_dispatcher (tree fn);
 
 #endif // GCC_CALLS_H
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 00000000000..52105449e4a
--- /dev/null
+++ b/gcc/testsuite/g++.target/aarch64/mv-symbols6.C
@@ -0,0 +1,16 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+__attribute__((target_version("default")))
+int foo ()
+{
+  return 1;
+}
+
+/* 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 "\n_Z3foov:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\.default:\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 0 } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/mv-1.c b/gcc/testsuite/gcc.target/aarch64/mv-1.c
new file mode 100644
index 00000000000..60d4985fec3
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/mv-1.c
@@ -0,0 +1,39 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+__attribute__((target_version("default")))
+int foo ()
+{
+  return 1;
+}
+
+__attribute__((target_version("rng")))
+int foo ()
+{
+  return 1;
+}
+
+__attribute__((target_version("flagm")))
+int foo ()
+{
+  return 1;
+}
+
+__attribute__((target_version("rng+flagm")))
+int foo ()
+{
+  return 1;
+}
+
+int bar()
+{
+  return foo ();
+}
+
+/* Check usage of the first two FMV features, in case of off-by-one errors.  */
+/* { dg-final { scan-assembler-times "\nfoo\.default:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\nfoo\._Mrng:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\nfoo\._MrngMflagm:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\nfoo\._Mflagm:\n" 1 } } */
+
+/* { dg-final { scan-assembler-times "\nfoo\.resolver:\n" 1 } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/mv-symbols1.c b/gcc/testsuite/gcc.target/aarch64/mv-symbols1.c
new file mode 100644
index 00000000000..b7a7bc2147a
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/mv-symbols1.c
@@ -0,0 +1,37 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+// Basic case of fmv correctness with all functions and use in one TU. 
+
+__attribute__((target_version("default")))
+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 ();
+}
+
+/* When updating any of the symbol names in these tests, make sure to also
+   update any tests for their absence in mv-symbolsN.C */
+
+/* { 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" 1 } } */
+/* { 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 } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/mv-symbols2.c b/gcc/testsuite/gcc.target/aarch64/mv-symbols2.c
new file mode 100644
index 00000000000..dfa9570da65
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/mv-symbols2.c
@@ -0,0 +1,28 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+// FMV correctness with definitions but no call
+
+__attribute__((target_version("default")))
+int foo ()
+{
+  return 1;
+}
+
+__attribute__((target_version("dotprod")))
+int foo ()
+{
+  return 3;
+}
+__attribute__((target_version("sve+sve2")))
+int foo ()
+{
+  return 5;
+}
+
+/* { 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 } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/mv-symbols3.c b/gcc/testsuite/gcc.target/aarch64/mv-symbols3.c
new file mode 100644
index 00000000000..c94610ab2da
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/mv-symbols3.c
@@ -0,0 +1,26 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+// FMV correctness with declarations but no implementation
+
+__attribute__((target_version("default")))
+int foo ();
+
+__attribute__((target_version("dotprod")))
+int foo ();
+
+__attribute__((target_version("sve+sve2")))
+int foo ();
+
+int bar()
+{
+  return foo ();
+}
+
+/* { 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 "\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 } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/mv-symbols4.c b/gcc/testsuite/gcc.target/aarch64/mv-symbols4.c
new file mode 100644
index 00000000000..25469d20c18
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/mv-symbols4.c
@@ -0,0 +1,29 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+// FMV correctness with a default implementation and declarations of other versions
+
+__attribute__((target_version("default")))
+int foo ()
+{
+  return 1;
+}
+
+__attribute__((target_version("dotprod")))
+int foo ();
+
+__attribute__((target_version("sve+sve2")))
+int foo ();
+
+int bar()
+{
+  return foo ();
+}
+
+/* { dg-final { scan-assembler-times "\nfoo\.default:\n" 1 } } */
+/* { 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 "\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 } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/mv-symbols5.c b/gcc/testsuite/gcc.target/aarch64/mv-symbols5.c
new file mode 100644
index 00000000000..95fa72583bb
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/mv-symbols5.c
@@ -0,0 +1,35 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+// FMV correctness with default declaration, and implementations of other 
+// versions.
+
+__attribute__((target_version("default")))
+int foo ();
+
+__attribute__((target_version("dotprod")))
+int foo ()
+{
+  return 3;
+}
+__attribute__((target_version("sve+sve2")))
+int foo ()
+{
+  return 5;
+}
+
+int bar()
+{
+  return foo ();
+}
+
+/* When updating any of the symbol names in these tests, make sure to also
+   update any tests for their absence in mvc-symbolsN.C */
+
+/* { 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 "\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 } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/mv-symbols6.c b/gcc/testsuite/gcc.target/aarch64/mv-symbols6.c
new file mode 100644
index 00000000000..8080502a4a4
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/mv-symbols6.c
@@ -0,0 +1,17 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+__attribute__((target_version("default")))
+int foo ()
+{
+  return 1;
+}
+
+
+/* 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 "\nfoo\.default:\n" 0 } } */
+/* { dg-final { scan-assembler-times "\nfoo\.resolver:\n" 0 } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/mvc-symbols1.c b/gcc/testsuite/gcc.target/aarch64/mvc-symbols1.c
new file mode 100644
index 00000000000..4662fbbef57
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/mvc-symbols1.c
@@ -0,0 +1,25 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+__attribute__((target_clones("default", "dotprod", "sve+sve2")))
+int foo ()
+{
+  return 1;
+}
+
+int bar()
+{
+  return foo ();
+}
+
+
+/* When updating any of the symbol names in these tests, make sure to also
+   update any tests for their absence in mvc-symbolsN.C */
+
+/* { 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" 1 } } */
+/* { 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 } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/mvc-symbols2.c b/gcc/testsuite/gcc.target/aarch64/mvc-symbols2.c
new file mode 100644
index 00000000000..112a350e2f7
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/mvc-symbols2.c
@@ -0,0 +1,16 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+__attribute__((target_clones("default", "dotprod", "sve+sve2")))
+int foo ()
+{
+  return 1;
+}
+
+
+/* { 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" 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/mvc-symbols3.c b/gcc/testsuite/gcc.target/aarch64/mvc-symbols3.c
new file mode 100644
index 00000000000..c681fb5b0a6
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/mvc-symbols3.c
@@ -0,0 +1,19 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+__attribute__((target_clones("default", "dotprod", "sve+sve2")))
+int foo ();
+
+int bar()
+{
+  return foo ();
+}
+
+/* { 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 "\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 } } */
+
diff --git a/gcc/testsuite/gcc.target/aarch64/mvc-symbols4.c b/gcc/testsuite/gcc.target/aarch64/mvc-symbols4.c
new file mode 100644
index 00000000000..81707552ee3
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/mvc-symbols4.c
@@ -0,0 +1,12 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+__attribute__((target_clones("default", "dotprod", "sve+sve2")))
+int foo ();
+
+/* { 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" 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 } } */