From: Alfie Richards <[email protected]>
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
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 } } */
--
2.34.1