[v11,11/13] c: Add target_version attribute support.

Message ID 20250917124456.170437-4-alfie.richards@arm.com
State Committed
Headers
Series c: c++: FMV refactor, C FMV support and ACLE compliance |

Commit Message

Alfie Richards Sept. 17, 2025, 12:44 p.m. UTC
  From: Alfie Richards <alfie.richards@arm.com>

This commit introduces support for the target_version attribute in the c
frontend, following the behavior defined in the Arm C Language Extension.

Key changes include:

- During pushdecl, the compiler now checks whether the current symbol is
  part of a multiversioned set.
  - New versions are added to the function multiversioning (FMV) set, and the
    symbol binding is updated to include the default version (if present).
    This means the binding for a multiversioned symbol will always reference
    the default version (if present), as it defines the scope and signature
    for the entire set.
  - Pre-existing versions are merged with their previous version (or diagnosed).
- Lookup logic is adjusted to prevent resolving non-default versions.
- start_decl and start_function are updated to handle marking and mangling of
  versioned functions.
- c_parse_final_cleanups now includes a call to process_same_body_aliases.
  This has no functional impact other than setting cpp_implicit_aliases_done
  on all nodes, which is necessary for certain shared FMV logic.

gcc/c/ChangeLog:

	* c-decl.cc (maybe_mark_function_versioned): New function.
	(merge_decls): Preserve DECL_FUNCTION_VERSIONED in merging.
	(duplicate_decls): Add check and diagnostic for unmergable version decls.
	(pushdecl): Add FMV target_version logic.
	(lookup_name): Don't resolve non-default versions.
	(start_decl): Mark and mangle versioned functions.
	(start_function): Mark and mangle versioned functions.
	(c_parse_final_cleanups): Add call to process_same_body_aliases.

gcc/testsuite/ChangeLog:

	* gcc.target/aarch64/mv-1.c: New test.
	* gcc.target/aarch64/mv-and-mvc1.c: New test.
	* gcc.target/aarch64/mv-and-mvc2.c: New test.
	* gcc.target/aarch64/mv-and-mvc3.c: New test.
	* gcc.target/aarch64/mv-and-mvc4.c: New test.
	* gcc.target/aarch64/mv-symbols1.c: New test.
	* gcc.target/aarch64/mv-symbols10.c: New test.
	* gcc.target/aarch64/mv-symbols11.c: New test.
	* gcc.target/aarch64/mv-symbols12.c: New test.
	* gcc.target/aarch64/mv-symbols13.c: New test.
	* gcc.target/aarch64/mv-symbols14.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/mv-symbols7.c: New test.
	* gcc.target/aarch64/mv-symbols8.c: New test.
	* gcc.target/aarch64/mv-symbols9.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.
---
 gcc/c/c-decl.cc                               | 112 ++++++++++++++++++
 gcc/testsuite/gcc.target/aarch64/mv-1.c       |  43 +++++++
 .../gcc.target/aarch64/mv-and-mvc1.c          |  37 ++++++
 .../gcc.target/aarch64/mv-and-mvc2.c          |  28 +++++
 .../gcc.target/aarch64/mv-and-mvc3.c          |  40 +++++++
 .../gcc.target/aarch64/mv-and-mvc4.c          |  37 ++++++
 .../gcc.target/aarch64/mv-symbols1.c          |  38 ++++++
 .../gcc.target/aarch64/mv-symbols10.c         |  42 +++++++
 .../gcc.target/aarch64/mv-symbols11.c         |  16 +++
 .../gcc.target/aarch64/mv-symbols12.c         |  27 +++++
 .../gcc.target/aarch64/mv-symbols13.c         |  28 +++++
 .../gcc.target/aarch64/mv-symbols14.c         |  34 ++++++
 .../gcc.target/aarch64/mv-symbols2.c          |  28 +++++
 .../gcc.target/aarch64/mv-symbols3.c          |  27 +++++
 .../gcc.target/aarch64/mv-symbols4.c          |  31 +++++
 .../gcc.target/aarch64/mv-symbols5.c          |  36 ++++++
 .../gcc.target/aarch64/mv-symbols6.c          |  20 ++++
 .../gcc.target/aarch64/mv-symbols7.c          |  47 ++++++++
 .../gcc.target/aarch64/mv-symbols8.c          |  47 ++++++++
 .../gcc.target/aarch64/mv-symbols9.c          |  44 +++++++
 .../gcc.target/aarch64/mvc-symbols1.c         |  25 ++++
 .../gcc.target/aarch64/mvc-symbols2.c         |  15 +++
 .../gcc.target/aarch64/mvc-symbols3.c         |  19 +++
 .../gcc.target/aarch64/mvc-symbols4.c         |  12 ++
 24 files changed, 833 insertions(+)
 create mode 100644 gcc/testsuite/gcc.target/aarch64/mv-1.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/mv-and-mvc1.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/mv-and-mvc2.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/mv-and-mvc3.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/mv-and-mvc4.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/mv-symbols1.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/mv-symbols10.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/mv-symbols11.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/mv-symbols12.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/mv-symbols13.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/mv-symbols14.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/mv-symbols7.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/mv-symbols8.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/mv-symbols9.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

Joseph Myers Sept. 26, 2025, 8:26 p.m. UTC | #1
Since this feature involves function name mangling, I'd like to understand 
how it interacts with other features affecting assembler names for 
functions, and make sure there are associated testcases for those 
interactions.  Specifically:

* How does this interact with asm redirection for functions, if that's 
used on any target_version or target_clones declaration or definition?

* How does this interact with the use of the simd attribute to map calls 
made to a scalar function to vector variants of that function, if that 
attribute (or similar OMP pragma) is used on a function also using one of 
the new attributes?
  
Alfie Richards Sept. 27, 2025, 9:44 a.m. UTC | #2
On 26/09/2025 21:26, Joseph Myers wrote:
> Since this feature involves function name mangling, I'd like to understand
> how it interacts with other features affecting assembler names for
> functions, and make sure there are associated testcases for those
> interactions.  Specifically:
> 
> * How does this interact with asm redirection for functions, if that's
> used on any target_version or target_clones declaration or definition?
> 
> * How does this interact with the use of the simd attribute to map calls
> made to a scalar function to vector variants of that function, if that
> attribute (or similar OMP pragma) is used on a function also using one of
> the new attributes?
> 
Hi Joseph,

Thank you for the review.

It was previously decided to disallow asm with target_clones and 
target_version. I will resubmit with that disallowed with an error and 
some tests.

I'll check if we want to specify that explicitly in the ACLE.

For OMP pragmas/SIMD attributes. I imagine we will want to disallow this 
similarly. I would be open to adding later but I think it would need 
further analysis thought as the behaviour seems non-obvious. Do you have 
a strong thought on this?

Thanks,
Alfie
  
Alfie Richards Sept. 28, 2025, 10:49 a.m. UTC | #3
On 27/09/2025 10:44, Alfie Richards wrote:
> On 26/09/2025 21:26, Joseph Myers wrote:
>> Since this feature involves function name mangling, I'd like to 
>> understand
>> how it interacts with other features affecting assembler names for
>> functions, and make sure there are associated testcases for those
>> interactions.  Specifically:
>>
>> * How does this interact with asm redirection for functions, if that's
>> used on any target_version or target_clones declaration or definition?
>>
>> * How does this interact with the use of the simd attribute to map calls
>> made to a scalar function to vector variants of that function, if that
>> attribute (or similar OMP pragma) is used on a function also using one of
>> the new attributes?
>>
> Hi Joseph,
> 
> Thank you for the review.
> 
> It was previously decided to disallow asm with target_clones and 
> target_version. I will resubmit with that disallowed with an error and 
> some tests.

Hi Joseph and Marek (or anyone else knowledgeable in the C front-end),

I'm attempting to implement these diagnostics at the moment but I am 
struggling to find an implementation.

The issue is for situations like:

```c
int fn(void) asm ("name");

int fn [[gnu::target_version("sve")]] (void) {
   ...
}
```

At the time of maybe_apply_renaming_pragma we have not processed 
target_version so cannot emit an error.

At time of processing the target_version attribute, I cannot find a way 
to detect the previous decl was renamed to diagnose.

I can imagine some intrusive work around's, such as adding a flag to the 
decl to mark is as renamed. Do you have a preferred solution?

Thanks,
Alfie

> 
> I'll check if we want to specify that explicitly in the ACLE.
> 
> For OMP pragmas/SIMD attributes. I imagine we will want to disallow this 
> similarly. I would be open to adding later but I think it would need 
> further analysis thought as the behaviour seems non-obvious. Do you have 
> a strong thought on this?
> 
> Thanks,
> Alfie
  
Joseph Myers Sept. 29, 2025, 8:08 p.m. UTC | #4
On Sun, 28 Sep 2025, Alfie Richards wrote:

> Hi Joseph and Marek (or anyone else knowledgeable in the C front-end),
> 
> I'm attempting to implement these diagnostics at the moment but I am
> struggling to find an implementation.
> 
> The issue is for situations like:
> 
> ```c
> int fn(void) asm ("name");
> 
> int fn [[gnu::target_version("sve")]] (void) {
>   ...
> }
> ```
> 
> At the time of maybe_apply_renaming_pragma we have not processed
> target_version so cannot emit an error.
> 
> At time of processing the target_version attribute, I cannot find a way to
> detect the previous decl was renamed to diagnose.

Checking DECL_ASSEMBLER_NAME_SET_P on the previous decl should work.  Or 
if you need to distinguish being set by the user from being set internally 
by the compiler, also check IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME 
(decl))[0] != '*' (I think).
  

Patch

diff --git a/gcc/c/c-decl.cc b/gcc/c/c-decl.cc
index b122e82bfc4..2d6fd9be2cd 100644
--- a/gcc/c/c-decl.cc
+++ b/gcc/c/c-decl.cc
@@ -2088,6 +2088,29 @@  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))
+    {
+      /* We need to insert function version now to make sure the correct
+	 pre-mangled assembler name is recorded.  */
+      cgraph_node *node = cgraph_node::get_create (decl);
+
+      if (!node->function_version ())
+	node->insert_new_function_version ();
+
+      DECL_FUNCTION_VERSIONED (decl) = 1;
+
+      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
@@ -2507,6 +2530,10 @@  diagnose_mismatched_decls (tree newdecl, tree olddecl,
 			"but not here");
 	    }
 	}
+      /* Check if these are unmergable overlapping FMV declarations.  */
+      if (!TARGET_HAS_FMV_TARGET_ATTRIBUTE
+	  && diagnose_versioned_decls (olddecl, newdecl))
+	return false;
     }
   else if (VAR_P (newdecl))
     {
@@ -2973,6 +3000,12 @@  merge_decls (tree newdecl, tree olddecl, tree newtype, tree oldtype)
 
   if (TREE_CODE (newdecl) == FUNCTION_DECL)
     {
+      if (DECL_FUNCTION_VERSIONED (olddecl)
+	  || DECL_FUNCTION_VERSIONED (newdecl))
+	{
+	  maybe_mark_function_versioned (olddecl);
+	  maybe_mark_function_versioned (newdecl);
+	}
       /* If we're redefining a function previously defined as extern
 	 inline, make sure we emit debug info for the inline before we
 	 throw it away, in case it was inlined into a function that
@@ -3372,6 +3405,53 @@  pushdecl (tree x)
 		TREE_TYPE (b_use->decl) = b_use->u.type;
 	    }
 	}
+
+      /* Check if x is part of a FMV set with b_use.  */
+      if (b_use && TREE_CODE (b_use->decl) == FUNCTION_DECL
+	  && TREE_CODE (x) == FUNCTION_DECL && DECL_FILE_SCOPE_P (b_use->decl)
+	  && DECL_FILE_SCOPE_P (x)
+	  && disjoint_version_decls (x, b_use->decl)
+	  && comptypes (vistype, type) != 0)
+	{
+	  maybe_mark_function_versioned (b_use->decl);
+	  maybe_mark_function_versioned (b->decl);
+	  maybe_mark_function_versioned (x);
+
+	  cgraph_node *b_node = cgraph_node::get_create (b_use->decl);
+	  cgraph_function_version_info *b_v = b_node->function_version ();
+	  if (!b_v)
+	    b_v = b_node->insert_new_function_version ();
+
+	  /* Check if this new node conflicts with any previous functions
+	     in the set.  */
+	  cgraph_function_version_info *version = b_v;
+	  for (; version; version = version->next)
+	    if (!disjoint_version_decls (version->this_node->decl, x))
+	      {
+		/* The decls define overlapping version, so attempt to merge
+		   or diagnose the conflict.  */
+		if (duplicate_decls (x, version->this_node->decl))
+		  return version->this_node->decl;
+		else
+		  return error_mark_node;
+	      }
+
+	  /* This is a new version to be added to FMV structure.  */
+	  cgraph_node::add_function_version (b_v, x);
+
+	  /* Get the first node from the structure.  */
+	  cgraph_function_version_info *default_v = b_v;
+	  while (default_v->prev)
+	    default_v = default_v->prev;
+	  /* Always use the default node for the bindings.  */
+	  b_use->decl = default_v->this_node->decl;
+	  b->decl = default_v->this_node->decl;
+
+	  /* Node is not a duplicate, so no need to do the rest of the
+	     checks.  */
+	  return x;
+	}
+
       if (duplicate_decls (x, b_use->decl))
 	{
 	  if (b_use != b)
@@ -4496,6 +4576,12 @@  tree
 lookup_name (tree name)
 {
   struct c_binding *b = I_SYMBOL_BINDING (name);
+  /* Do not resolve non-default function versions.  */
+  if (b
+      && TREE_CODE (b->decl) == FUNCTION_DECL
+      && DECL_FUNCTION_VERSIONED (b->decl)
+      && !is_function_default_version (b->decl))
+    return NULL_TREE;
   if (b && !b->invisible)
     {
       maybe_record_typedef_use (b->decl);
@@ -5778,6 +5864,17 @@  start_decl (struct c_declarator *declarator, struct c_declspecs *declspecs,
       && VAR_OR_FUNCTION_DECL_P (decl))
       objc_check_global_decl (decl);
 
+  /* To enable versions to be created across TU's we mark and mangle all
+     non-default versioned functions.  */
+  if (TREE_CODE (decl) == FUNCTION_DECL
+      && !TARGET_HAS_FMV_TARGET_ATTRIBUTE
+      && get_target_version (decl).is_valid ())
+    {
+      maybe_mark_function_versioned (decl);
+      if (current_scope != file_scope)
+	error ("versioned declarations are only allowed at file scope");
+    }
+
   /* Add this decl to the current scope.
      TEM may equal DECL or it may be a previous decl of the same name.  */
   if (do_push)
@@ -10754,6 +10851,17 @@  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 (TREE_CODE (decl1) == FUNCTION_DECL
+      && !TARGET_HAS_FMV_TARGET_ATTRIBUTE
+      && get_target_version (decl1).is_valid ())
+    {
+      maybe_mark_function_versioned (decl1);
+      if (current_scope != file_scope)
+	error ("versioned definitions are only allowed at file scope");
+    }
+
   /* 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.  */
@@ -13585,6 +13693,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/testsuite/gcc.target/aarch64/mv-1.c b/gcc/testsuite/gcc.target/aarch64/mv-1.c
new file mode 100644
index 00000000000..6f095ecd7a7
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/mv-1.c
@@ -0,0 +1,43 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+__attribute__ ((target_version ("default"))) int
+foo ()
+{
+  return 1;
+}
+
+__attribute__ ((target_version ("rng"))) int
+foo ()
+{
+  return 2;
+}
+
+__attribute__ ((target_version ("flagm"))) int
+foo ()
+{
+  return 3;
+}
+
+__attribute__ ((target_version ("rng+flagm"))) int
+foo ()
+{
+  return 4;
+}
+
+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 } } */
+/* { 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-and-mvc1.c b/gcc/testsuite/gcc.target/aarch64/mv-and-mvc1.c
new file mode 100644
index 00000000000..39ed306eec2
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/mv-and-mvc1.c
@@ -0,0 +1,37 @@ 
+/* { dg-do compile } */
+/* { dg-require-ifunc "" } */
+/* { dg-options "-O0" } */
+
+__attribute__((target_version("default")))
+int foo ()
+{
+  return 0;
+}
+
+__attribute__((target_clones("dotprod", "sve+sve2")))
+int foo ()
+{
+  return 1;
+}
+
+__attribute__((target_clones("sve", "sve2")))
+int foo ()
+{
+  return 2;
+}
+
+int bar()
+{
+  return 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\._Msve:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\nfoo\._Msve2:\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-and-mvc2.c b/gcc/testsuite/gcc.target/aarch64/mv-and-mvc2.c
new file mode 100644
index 00000000000..17c7cbdd02d
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/mv-and-mvc2.c
@@ -0,0 +1,28 @@ 
+/* { dg-do compile } */
+/* { dg-require-ifunc "" } */
+/* { dg-options "-O0" } */
+
+__attribute__((target_version("default")))
+int foo ();
+
+__attribute__((target_clones("dotprod", "sve+sve2")))
+int foo ()
+{
+  return 1;
+}
+
+__attribute__((target_clones("sve", "sve2")))
+int foo ()
+{
+  return 2;
+}
+
+/* { dg-final { scan-assembler-times "\nfoo\.default:\n" 0 } } */
+/* { dg-final { scan-assembler-times "\nfoo:\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\._Msve:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\nfoo\._Msve2:\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-and-mvc3.c b/gcc/testsuite/gcc.target/aarch64/mv-and-mvc3.c
new file mode 100644
index 00000000000..8325c8e0699
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/mv-and-mvc3.c
@@ -0,0 +1,40 @@ 
+/* { dg-do compile } */
+/* { dg-require-ifunc "" } */
+/* { dg-options "-O0" } */
+
+__attribute__((target_clones("dotprod", "sve+sve2")))
+int foo ();
+
+__attribute__((target_version("default")))
+int foo ()
+{
+  return 0;
+}
+
+__attribute__((target_clones("sve", "sve2")))
+int foo ()
+{
+  return 2;
+}
+
+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\._Msve:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\nfoo\._Msve2:\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 } } */
+// { dg-final { scan-assembler-times "\n\tadrp\tx\[0-9\]+, foo\.default\n" 1 } }
+/* { dg-final { scan-assembler-times "\n\tadrp\tx\[0-9\]+, foo\._Mdotprod\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx\[0-9\]+, foo\._MsveMsve2\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx\[0-9\]+, foo\._Msve\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx\[0-9\]+, foo\._Msve2\n" 1 } } */
+
diff --git a/gcc/testsuite/gcc.target/aarch64/mv-and-mvc4.c b/gcc/testsuite/gcc.target/aarch64/mv-and-mvc4.c
new file mode 100644
index 00000000000..951c9500d74
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/mv-and-mvc4.c
@@ -0,0 +1,37 @@ 
+/* { dg-do compile } */
+/* { dg-require-ifunc "" } */
+/* { dg-options "-O0" } */
+
+__attribute__((target_version("dotprod")))
+int foo ()
+{
+  return 0;
+}
+
+__attribute__((target_clones("default", "sve+sve2")))
+int foo ()
+{
+  return 1;
+}
+
+__attribute__((target_clones("sve", "sve2")))
+int foo ()
+{
+  return 2;
+}
+
+int bar()
+{
+  return 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\._Msve:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\nfoo\._Msve2:\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-symbols1.c b/gcc/testsuite/gcc.target/aarch64/mv-symbols1.c
new file mode 100644
index 00000000000..798227826e5
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/mv-symbols1.c
@@ -0,0 +1,38 @@ 
+/* { 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-symbols10.c b/gcc/testsuite/gcc.target/aarch64/mv-symbols10.c
new file mode 100644
index 00000000000..d5256389d7b
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/mv-symbols10.c
@@ -0,0 +1,42 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+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 "\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\tadrp\tx., foo\._MsveMsve2\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx., foo\._Mdotprod\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx., foo\.default\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-symbols11.c b/gcc/testsuite/gcc.target/aarch64/mv-symbols11.c
new file mode 100644
index 00000000000..fd3dc345a59
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/mv-symbols11.c
@@ -0,0 +1,16 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+// Check that types can be combined
+
+__attribute__ ((target_version ("default"))) int
+foo (int a, int (*b)[4]) { return 1; }
+
+__attribute__ ((target_version ("dotprod"))) int
+foo (int a, int (*b)[]) { return 3; }
+
+/* { dg-final { scan-assembler-times "\nfoo\.default:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\nfoo\._Mdotprod:\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/mv-symbols12.c b/gcc/testsuite/gcc.target/aarch64/mv-symbols12.c
new file mode 100644
index 00000000000..1a0b667eb5f
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/mv-symbols12.c
@@ -0,0 +1,27 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+__attribute__ ((target_version ("default"))) int
+foo () { return 1; }
+
+__attribute__ ((target_version ("dotprod"))) int
+foo () { return 3; }
+
+int bar ()
+{
+  int (*test)() = foo;
+
+  test();
+}
+
+/* { dg-final { scan-assembler-times "\nfoo\.default:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\nfoo\._Mdotprod:\n" 1 } } */
+
+/* { dg-final { scan-assembler-times "\nfoo\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx\[0-9\], foo\._Mdotprod\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx\[0-9\], foo\.default\n" 1 } } */
+
+/* { dg-final { scan-assembler-times "\n\tadrp\tx\[0-9\]+, foo\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-symbols13.c b/gcc/testsuite/gcc.target/aarch64/mv-symbols13.c
new file mode 100644
index 00000000000..308dace64a7
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/mv-symbols13.c
@@ -0,0 +1,28 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+__attribute__ ((target_version ("default"))) int
+foo ();
+
+int bar ()
+{
+  int (*test)() = foo;
+
+  test();
+}
+
+__attribute__ ((target_version ("dotprod"))) int
+foo () { return 3; }
+
+/* { dg-final { scan-assembler-times "\nfoo\.default:\n" 0 } } */
+/* { dg-final { scan-assembler-times "\nfoo\._Mdotprod:\n" 1 } } */
+
+/* { dg-final { scan-assembler-times "\nfoo\.resolver:\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx\[0-9\], foo\._Mdotprod\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx\[0-9\], foo\.default\n" 0 } } */
+
+/* { dg-final { scan-assembler-times "\n\tadrp\tx\[0-9\]+, foo\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-symbols14.c b/gcc/testsuite/gcc.target/aarch64/mv-symbols14.c
new file mode 100644
index 00000000000..d1af69fe31d
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/mv-symbols14.c
@@ -0,0 +1,34 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+int foo ();
+
+__attribute__ ((target_version ("default")))  int
+foo ()
+{
+  return 1;
+}
+
+__attribute__ ((target_version ("dotprod"))) int
+foo ()
+{
+  return 3;
+}
+
+int
+bar ()
+{
+  return 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\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx., foo\._Mdotprod\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx., foo\.default\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..a8732caf214
--- /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" 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
new file mode 100644
index 00000000000..962bae93577
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/mv-symbols3.c
@@ -0,0 +1,27 @@ 
+/* { 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" 0 } } */
+/* { dg-final { scan-assembler-times "\n\tbl\tfoo\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-symbols4.c b/gcc/testsuite/gcc.target/aarch64/mv-symbols4.c
new file mode 100644
index 00000000000..a476800b2c5
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/mv-symbols4.c
@@ -0,0 +1,31 @@ 
+/* { 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..4df20009f79
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/mv-symbols5.c
@@ -0,0 +1,36 @@ 
+/* { 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" 0 } } */
+/* { dg-final { scan-assembler-times "\n\tbl\tfoo\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
new file mode 100644
index 00000000000..cbf8bcaf8e1
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/mv-symbols6.c
@@ -0,0 +1,20 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+__attribute__ ((target_version ("default"))) int
+foo ()
+{
+  return 1;
+}
+
+int bar()
+{
+  return foo();
+}
+
+/* { dg-final { scan-assembler-times "\nfoo:\n" 0 } } */
+/* { dg-final { scan-assembler-times "\nfoo\.default:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\nfoo\.resolver:\n" 0 } } */
+/* { dg-final { scan-assembler-times "bl\tfoo.default\n" 1 } } */
+/* { dg-final { scan-assembler-times ".global\tfoo\n" 1 } } */
+/* { dg-final { scan-assembler-times ".set\tfoo,foo.default\n" 1 } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/mv-symbols7.c b/gcc/testsuite/gcc.target/aarch64/mv-symbols7.c
new file mode 100644
index 00000000000..2ea4d2ebf0f
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/mv-symbols7.c
@@ -0,0 +1,47 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+__attribute__ ((target_version ("dotprod"))) int
+foo ();
+
+__attribute__ ((target_version ("sve+sve2"))) int
+foo ();
+
+__attribute__ ((target_version ("default"))) int
+foo ();
+
+int
+bar ()
+{
+  return 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;
+}
+
+/* { 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\tadrp\tx., foo\._MsveMsve2\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx., foo\._Mdotprod\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx., foo\.default\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-symbols8.c b/gcc/testsuite/gcc.target/aarch64/mv-symbols8.c
new file mode 100644
index 00000000000..3e3eaf21aa9
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/mv-symbols8.c
@@ -0,0 +1,47 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+__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 "\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\tadrp\tx., foo\._MsveMsve2\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx., foo\._Mdotprod\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx., foo\.default\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-symbols9.c b/gcc/testsuite/gcc.target/aarch64/mv-symbols9.c
new file mode 100644
index 00000000000..8e0864f1663
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/mv-symbols9.c
@@ -0,0 +1,44 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+__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 "\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\tadrp\tx., foo\._MsveMsve2\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx., foo\._Mdotprod\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx., foo\.default\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-symbols1.c b/gcc/testsuite/gcc.target/aarch64/mvc-symbols1.c
new file mode 100644
index 00000000000..3ad15e5bb73
--- /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..78385ed904b
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/mvc-symbols2.c
@@ -0,0 +1,15 @@ 
+/* { 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..1cbe3fd0850
--- /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" 0 } } */
+/* { dg-final { scan-assembler-times "\n\tbl\tfoo\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/mvc-symbols4.c b/gcc/testsuite/gcc.target/aarch64/mvc-symbols4.c
new file mode 100644
index 00000000000..abaf60f91c3
--- /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 } } */