From: Alfie Richards <alfie.richa...@arm.com>
This patch changes the semantics of target_version and target_clones attributes
to match the behavior described in the Arm C Language extension.
The changes to behavior are:
- The scope and signature of an FMV function set is now that of the default
version.
- The FMV resolver is now created at the locations of the default version
implementation. Previously this was at the first call to an FMV function.
- When a TU has a single annotated function version, it gets mangled.
- This includes a lone annotated default version.
This only affects targets with TARRGET_HAS_FMV_TARGET_ATTRIBUTE set to false.
Currently that is aarch64 and riscv.
This is achieved by:
- Skipping the existing FMV dispatching code at C++ gimplification and instead
making use of the target_clones dispatching code in multiple_targets.cc.
(This fixes PR target/118313 for aarch64 and riscv).
- Splitting target_clones pass in two, an early and late pass, where the early
pass handles cases where multiple declarations are used to define a version,
and the late pass handling target semantics targets, and cases where a FMV
set is defined by a single target_clones decl.
- Changing the logic in add_candidates and resolve_address of overloaded
function to prevent resolution of any version except a default version.
(thus making the default version determine scope and signature of the
versioned function set).
- Adding logic for dispatching a lone annotated default version in
multiple_targets.cc
- As as annotated default version gets mangled an alias is created from the
dispatched symbol to the default version as no ifunc resolution is required
in this case. (ie. an alias from `_Z3foov` to `_Z3foov.default`)
- Adding logic to `symbol_table::remove_unreachable_nodes` and analyze_functions
that a reference to the default function version also implies a possible
reference to the other versions (so they shouldnt be deleted and do need to
be analyzed).
gcc/ChangeLog:
PR target/118313
* cgraph.cc (delete_function_version): Made public static member of
cgraph_node.
* cgraph.h (delete_function_version): Ditto.
* cgraphunit.cc (analyze_functions): Add logic for target version
dependencies.
* ipa.cc (symbol_table::remove_unreachable_nodes): Ditto.
* multiple_target.cc (create_dispatcher_calls): Change to support
target version semantics.
(ipa_target_clone): Change to dispatch all function sets in
target_version semantics, and to have early and late pass.
(expand_target_clones): Add logic for cases of target_clones with no
defaults.
(is_simple_target_clones_case): New function.
(class pass_target_clone): New parameter for early or late pass.
* config/aarch64/aarch64.cc: (aarch64_get_function_versions_dispatcher):
Refactor with the assumption that the DECL node will be default.
* config/riscv/riscv.cc: (riscv_get_function_versions_dispatcher):
Refactor with the assumption that the DECL node will be default.
* passes.def: Split target_clones pass into early and late version.
gcc/cp/ChangeLog:
PR target/118313
* call.cc (add_candidates): Change to not resolve non-default versions
in target_version semantics.
* class.cc (resolve_address_of_overloaded_function): Ditto.
* cp-gimplify.cc (cp_genericize_r): Change logic to not apply for
target_version semantics.
* decl.cc (start_decl): Change to mark and therefore mangle all
target_version decls in target_version semantics.
(start_preparsed_function): Ditto.
* typeck.cc (cp_build_function_call_vec): Add error for calling
unresolvable non-default node in target_version semantics.
gcc/testsuite/ChangeLog:
* g++.target/aarch64/mv-1.C: Change for target_version semantics.
* g++.target/aarch64/mv-symbols2.C: Ditto.
* g++.target/aarch64/mv-symbols3.C: Ditto.
* g++.target/aarch64/mv-symbols4.C: Ditto.
* g++.target/aarch64/mv-symbols5.C: Ditto.
* g++.target/aarch64/mvc-symbols3.C: Ditto.
* g++.target/riscv/mv-symbols2.C: Ditto.
* g++.target/riscv/mv-symbols3.C: Ditto.
* g++.target/riscv/mv-symbols4.C: Ditto.
* g++.target/riscv/mv-symbols5.C: Ditto.
* g++.target/riscv/mvc-symbols3.C: Ditto.
* g++.target/aarch64/mv-symbols10.C: New test.
* g++.target/aarch64/mv-symbols11.C: New test.
* g++.target/aarch64/mv-symbols12.C: New test.
* g++.target/aarch64/mv-symbols13.C: New test.
* g++.target/aarch64/mv-symbols6.C: New test.
* g++.target/aarch64/mv-symbols7.C: New test.
* g++.target/aarch64/mv-symbols8.C: New test.
* g++.target/aarch64/mv-symbols9.C: New test.
---
gcc/cgraph.cc | 4 +-
gcc/cgraph.h | 2 +
gcc/cgraphunit.cc | 9 +
gcc/config/aarch64/aarch64.cc | 43 ++--
gcc/config/riscv/riscv.cc | 43 ++--
gcc/cp/call.cc | 10 +
gcc/cp/class.cc | 13 +-
gcc/cp/cp-gimplify.cc | 11 +-
gcc/cp/decl.cc | 14 ++
gcc/cp/typeck.cc | 10 +
gcc/ipa.cc | 11 +
gcc/multiple_target.cc | 188 +++++++++++++++---
gcc/passes.def | 3 +-
gcc/testsuite/g++.target/aarch64/mv-1.C | 4 +
.../g++.target/aarch64/mv-symbols10.C | 27 +++
.../g++.target/aarch64/mv-symbols11.C | 30 +++
.../g++.target/aarch64/mv-symbols12.C | 28 +++
.../g++.target/aarch64/mv-symbols13.C | 28 +++
.../g++.target/aarch64/mv-symbols2.C | 12 +-
.../g++.target/aarch64/mv-symbols3.C | 6 +-
.../g++.target/aarch64/mv-symbols4.C | 6 +-
.../g++.target/aarch64/mv-symbols5.C | 6 +-
.../g++.target/aarch64/mv-symbols6.C | 21 ++
.../g++.target/aarch64/mv-symbols7.C | 48 +++++
.../g++.target/aarch64/mv-symbols8.C | 46 +++++
.../g++.target/aarch64/mv-symbols9.C | 43 ++++
.../g++.target/aarch64/mvc-symbols3.C | 12 +-
gcc/testsuite/g++.target/riscv/mv-symbols2.C | 12 +-
gcc/testsuite/g++.target/riscv/mv-symbols3.C | 6 +-
gcc/testsuite/g++.target/riscv/mv-symbols4.C | 6 +-
gcc/testsuite/g++.target/riscv/mv-symbols5.C | 6 +-
gcc/testsuite/g++.target/riscv/mvc-symbols3.C | 12 +-
32 files changed, 588 insertions(+), 132 deletions(-)
create mode 100644 gcc/testsuite/g++.target/aarch64/mv-symbols10.C
create mode 100644 gcc/testsuite/g++.target/aarch64/mv-symbols11.C
create mode 100644 gcc/testsuite/g++.target/aarch64/mv-symbols12.C
create mode 100644 gcc/testsuite/g++.target/aarch64/mv-symbols13.C
create mode 100644 gcc/testsuite/g++.target/aarch64/mv-symbols6.C
create mode 100644 gcc/testsuite/g++.target/aarch64/mv-symbols7.C
create mode 100644 gcc/testsuite/g++.target/aarch64/mv-symbols8.C
create mode 100644 gcc/testsuite/g++.target/aarch64/mv-symbols9.C
diff --git a/gcc/cgraph.cc b/gcc/cgraph.cc
index c0be16edfcb..1d86bcec67f 100644
--- a/gcc/cgraph.cc
+++ b/gcc/cgraph.cc
@@ -333,8 +333,8 @@ cgraph_node::insert_new_function_version (void)
}
/* Remove the cgraph_function_version_info node given by DECL_V. */
-static void
-delete_function_version (cgraph_function_version_info *decl_v)
+void
+cgraph_node::delete_function_version (cgraph_function_version_info *decl_v)
{
if (decl_v == NULL)
return;
diff --git a/gcc/cgraph.h b/gcc/cgraph.h
index 21f89112769..a719321a538 100644
--- a/gcc/cgraph.h
+++ b/gcc/cgraph.h
@@ -1352,6 +1352,8 @@ struct GTY((tag ("SYMTAB_FUNCTION"))) cgraph_node :
public symtab_node
DECL is a duplicate declaration. */
static void delete_function_version_by_decl (tree decl);
+ static void delete_function_version (cgraph_function_version_info *);
+
/* Add the function FNDECL to the call graph.
Unlike finalize_function, this function is intended to be used
by middle end and allows insertion of new function at arbitrary point
diff --git a/gcc/cgraphunit.cc b/gcc/cgraphunit.cc
index 9f4af63b7dc..a81f685654f 100644
--- a/gcc/cgraphunit.cc
+++ b/gcc/cgraphunit.cc
@@ -1264,6 +1264,15 @@ analyze_functions (bool first_time)
if (!cnode->analyzed)
cnode->analyze ();
+ /* A reference to a default node in a function set implies a
+ reference to all versions in the set. */
+ cgraph_function_version_info *node_v = cnode->function_version ();
+ if (node_v && is_function_default_version (node->decl))
+ for (cgraph_function_version_info *fvi = node_v->next;
+ fvi;
+ fvi = fvi->next)
+ enqueue_node (fvi->this_node);
+
for (edge = cnode->callees; edge; edge = edge->next_callee)
if (edge->callee->definition
&& (!DECL_EXTERNAL (edge->callee->decl)
diff --git a/gcc/config/aarch64/aarch64.cc b/gcc/config/aarch64/aarch64.cc
index df3f5b2ea18..766da917732 100644
--- a/gcc/config/aarch64/aarch64.cc
+++ b/gcc/config/aarch64/aarch64.cc
@@ -21112,42 +21112,29 @@ aarch64_generate_version_dispatcher_body (void
*node_p)
return resolver_decl;
}
-/* Make a dispatcher declaration for the multi-versioned function DECL.
- Calls to DECL function will be replaced with calls to the dispatcher
- by the front-end. Returns the decl of the dispatcher function. */
+/* Make a dispatcher declaration for the multi-versioned default function DECL.
+ Calls to DECL function will be replaced with calls to the dispatcher by
+ the target_clones pass. Returns the decl of the dispatcher function. */
tree
aarch64_get_function_versions_dispatcher (void *decl)
{
- tree fn = (tree) decl;
- struct cgraph_node *node = NULL;
- struct cgraph_node *default_node = NULL;
- struct cgraph_function_version_info *node_v = NULL;
-
+ tree default_decl = (tree) decl;
tree dispatch_decl = NULL;
- struct cgraph_function_version_info *default_version_info = NULL;
-
- gcc_assert (fn != NULL && DECL_FUNCTION_VERSIONED (fn));
-
- node = cgraph_node::get (fn);
- gcc_assert (node != NULL);
+ gcc_assert (decl != NULL
+ && DECL_FUNCTION_VERSIONED (default_decl)
+ && is_function_default_version (default_decl));
- node_v = node->function_version ();
- gcc_assert (node_v != NULL);
+ struct cgraph_node *default_node = cgraph_node::get (default_decl);
+ gcc_assert (default_node != NULL);
- if (node_v->dispatcher_resolver != NULL)
- return node_v->dispatcher_resolver;
+ struct cgraph_function_version_info *default_node_v
+ = default_node->function_version ();
+ gcc_assert (default_node_v != NULL && !default_node_v->prev);
- /* The default node is always the beginning of the chain. */
- default_version_info = node_v;
- while (default_version_info->prev)
- default_version_info = default_version_info->prev;
- default_node = default_version_info->this_node;
-
- /* If there is no default node, just return NULL. */
- if (!is_function_default_version (default_node->decl))
- return NULL;
+ if (default_node_v->dispatcher_resolver != NULL)
+ return default_node_v->dispatcher_resolver;
if (targetm.has_ifunc_p ())
{
@@ -21157,7 +21144,7 @@ aarch64_get_function_versions_dispatcher (void *decl)
dispatch_decl = make_dispatcher_decl (default_node->decl);
/* Set the dispatcher for all the versions. */
- it_v = default_version_info;
+ it_v = default_node_v;
while (it_v != NULL)
{
it_v->dispatcher_resolver = dispatch_decl;
diff --git a/gcc/config/riscv/riscv.cc b/gcc/config/riscv/riscv.cc
index 185c66c6b2c..e01aa37f947 100644
--- a/gcc/config/riscv/riscv.cc
+++ b/gcc/config/riscv/riscv.cc
@@ -14587,42 +14587,29 @@ riscv_generate_version_dispatcher_body (void *node_p)
return resolver_decl;
}
-/* Make a dispatcher declaration for the multi-versioned function DECL.
- Calls to DECL function will be replaced with calls to the dispatcher
- by the front-end. Returns the decl of the dispatcher function. */
+/* Make a dispatcher declaration for the multi-versioned default function DECL.
+ Calls to DECL function will be replaced with calls to the dispatcher by
+ the target_clones pass. Returns the decl of the dispatcher function. */
tree
riscv_get_function_versions_dispatcher (void *decl)
{
- tree fn = (tree) decl;
- struct cgraph_node *node = NULL;
- struct cgraph_node *default_node = NULL;
- struct cgraph_function_version_info *node_v = NULL;
-
+ tree default_decl = (tree) decl;
tree dispatch_decl = NULL;
- struct cgraph_function_version_info *default_version_info = NULL;
-
- gcc_assert (fn != NULL && DECL_FUNCTION_VERSIONED (fn));
-
- node = cgraph_node::get (fn);
- gcc_assert (node != NULL);
-
- node_v = node->function_version ();
- gcc_assert (node_v != NULL);
+ gcc_assert (decl != NULL
+ && DECL_FUNCTION_VERSIONED (default_decl)
+ && is_function_default_version (default_decl));
- if (node_v->dispatcher_resolver != NULL)
- return node_v->dispatcher_resolver;
+ struct cgraph_node *default_node = cgraph_node::get (default_decl);
+ gcc_assert (default_node != NULL);
- /* The default node is always the beginning of the chain. */
- default_version_info = node_v;
- while (default_version_info->prev)
- default_version_info = default_version_info->prev;
- default_node = default_version_info->this_node;
+ struct cgraph_function_version_info *default_node_v
+ = default_node->function_version ();
+ gcc_assert (default_node_v != NULL && !default_node_v->prev);
- /* If there is no default node, just return NULL. */
- if (!is_function_default_version (default_node->decl))
- return NULL;
+ if (default_node_v->dispatcher_resolver != NULL)
+ return default_node_v->dispatcher_resolver;
if (targetm.has_ifunc_p ())
{
@@ -14632,7 +14619,7 @@ riscv_get_function_versions_dispatcher (void *decl)
dispatch_decl = make_dispatcher_decl (default_node->decl);
/* Set the dispatcher for all the versions. */
- it_v = default_version_info;
+ it_v = default_node_v;
while (it_v != NULL)
{
it_v->dispatcher_resolver = dispatch_decl;
diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index 02cef63a453..4d6f60f9b65 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -6927,6 +6927,16 @@ add_candidates (tree fns, tree first_arg, const vec<tree,
va_gc> *args,
continue;
}
+ /* Do not resolve any non-default function. Only the default version
+ is resolvable (for the target_version attribute semantics.) */
+ if (!TARGET_HAS_FMV_TARGET_ATTRIBUTE
+ && TREE_CODE (fn) == FUNCTION_DECL
+ && !is_function_default_version (fn))
+ {
+ add_ignored_candidate (candidates, fn);
+ continue;
+ }
+
if (TREE_CODE (fn) == TEMPLATE_DECL)
add_template_candidate (candidates,
fn,
diff --git a/gcc/cp/class.cc b/gcc/cp/class.cc
index cf58f652fc1..df8d3c9321e 100644
--- a/gcc/cp/class.cc
+++ b/gcc/cp/class.cc
@@ -8974,6 +8974,13 @@ resolve_address_of_overloaded_function (tree target_type,
if (!constraints_satisfied_p (fn))
continue;
+ /* For target_version semantics, never resolve a non-default
+ version. */
+ if (!TARGET_HAS_FMV_TARGET_ATTRIBUTE
+ && TREE_CODE (fn) == FUNCTION_DECL
+ && !is_function_default_version (fn))
+ continue;
+
if (undeduced_auto_decl (fn))
{
/* Force instantiation to do return type deduction. */
@@ -9199,8 +9206,10 @@ resolve_address_of_overloaded_function (tree target_type,
/* If a pointer to a function that is multi-versioned is requested, the
pointer to the dispatcher function is returned instead. This works
well because indirectly calling the function will dispatch the right
- function version at run-time. */
- if (DECL_FUNCTION_VERSIONED (fn))
+ function version at run-time.
+ This is done at multiple_target.cc for target_version semantics. */
+
+ if (DECL_FUNCTION_VERSIONED (fn) && TARGET_HAS_FMV_TARGET_ATTRIBUTE)
{
fn = get_function_version_dispatcher (fn);
if (fn == NULL)
diff --git a/gcc/cp/cp-gimplify.cc b/gcc/cp/cp-gimplify.cc
index 4ff8f36a9fb..7ff287a2b1c 100644
--- a/gcc/cp/cp-gimplify.cc
+++ b/gcc/cp/cp-gimplify.cc
@@ -2165,13 +2165,16 @@ cp_genericize_r (tree *stmt_p, int *walk_subtrees, void
*data)
returns the function with the highest target priority, that is,
the version that will checked for dispatching first. If this
version is inlinable, a direct call to this version can be made
- otherwise the call should go through the dispatcher. */
+ otherwise the call should go through the dispatcher.
+ This is done at multiple_target.cc for target_version semantics. */
{
tree fn = cp_get_callee_fndecl_nofold (stmt);
- if (fn && DECL_FUNCTION_VERSIONED (fn)
+ if (TARGET_HAS_FMV_TARGET_ATTRIBUTE
+ && fn
+ && DECL_FUNCTION_VERSIONED (fn)
&& (current_function_decl == NULL
- || !targetm.target_option.can_inline_p (current_function_decl,
- fn)))
+ || !targetm.target_option.can_inline_p
+ (current_function_decl, fn)))
if (tree dis = get_function_version_dispatcher (fn))
{
mark_versions_used (dis);
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index d416f6200d6..5dd5d767c19 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -6208,6 +6208,13 @@ start_decl (const cp_declarator *declarator,
was_public = TREE_PUBLIC (decl);
+ /* For target_version semantics, mark any annotated function as versioned
+ so that it gets mangled even when on its own in a TU. */
+ if (!TARGET_HAS_FMV_TARGET_ATTRIBUTE
+ && TREE_CODE (decl) == FUNCTION_DECL
+ && get_target_version (decl).is_valid ())
+ maybe_mark_function_versioned (decl);
+
if ((DECL_EXTERNAL (decl) || TREE_CODE (decl) == FUNCTION_DECL)
&& current_function_decl)
{
@@ -19340,6 +19347,13 @@ start_preparsed_function (tree decl1, tree attrs, int
flags)
if (!DECL_OMP_DECLARE_REDUCTION_P (decl1))
start_lambda_scope (decl1);
+ /* For target_version semantics, mark any annotated function as versioned
+ so that it gets mangled even when on its own in a TU. */
+ if (!TARGET_HAS_FMV_TARGET_ATTRIBUTE
+ && TREE_CODE (decl1) == FUNCTION_DECL
+ && get_target_version (decl1).is_valid ())
+ maybe_mark_function_versioned (decl1);
return true;
}
diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
index 41a3f4cb7cb..0b6b3e70bb1 100644
--- a/gcc/cp/typeck.cc
+++ b/gcc/cp/typeck.cc
@@ -4490,6 +4490,16 @@ cp_build_function_call_vec (tree function, vec<tree,
va_gc> **params,
return error_mark_node;
fndecl = function;
+ /* For target_version semantics, the function set cannot be called
+ if there is no default version in scope. */
+ if (!TARGET_HAS_FMV_TARGET_ATTRIBUTE
+ && !is_function_default_version (fndecl))
+ {
+ if (complain & tf_error)
+ error ("no default version in scope");
+ return error_mark_node;
+ }
+
/* Convert anything with function type to a pointer-to-function. */
if (DECL_MAIN_P (function))
{
diff --git a/gcc/ipa.cc b/gcc/ipa.cc
index 8a7b067a526..dea22ea0b49 100644
--- a/gcc/ipa.cc
+++ b/gcc/ipa.cc
@@ -433,6 +433,17 @@ symbol_table::remove_unreachable_nodes (FILE *file)
e, &first, &reachable);
}
}
+
+ /* A reference to the default node implies use of all the other
+ versions (they get used in the function resolver made later
+ in multiple_target.cc) */
+ cgraph_function_version_info *node_v = cnode->function_version ();
+ if (node_v && is_function_default_version (node->decl))
+ for (cgraph_function_version_info *fvi = node_v->next;
+ fvi;
+ fvi = fvi->next)
+ enqueue_node (fvi->this_node, &first, &reachable);
+
for (e = cnode->callees; e; e = e->next_callee)
{
symtab_node *body = e->callee->function_symbol ();
diff --git a/gcc/multiple_target.cc b/gcc/multiple_target.cc
index dc3b72c8e85..02e868212e4 100644
--- a/gcc/multiple_target.cc
+++ b/gcc/multiple_target.cc
@@ -58,8 +58,15 @@ replace_function_decl (tree *op, int *walk_subtrees, void
*data)
return NULL;
}
-/* If the call in NODE has multiple target attribute with multiple fields,
- replace it with dispatcher call and create dispatcher (once). */
+/* In target FMV attributes, if the call in NODE has multiple target attribute
+ with multiple fields, replace it with calls to the dispatched symbol and
+ create the dispatcher body (once).
+
+ In target_version semantics, if it is a lone annotated default, then
+ the dispatched symbol is changed to be an alias and no resolver is
+ required. Otherwise, redirect all calls and references to the dispatched
+ symbol, but only create the resolver body if the default version is
+ implemented. */
static void
create_dispatcher_calls (struct cgraph_node *node)
@@ -90,13 +97,48 @@ create_dispatcher_calls (struct cgraph_node *node)
cgraph_node *inode = cgraph_node::get (idecl);
gcc_assert (inode);
- tree resolver_decl = targetm.generate_version_dispatcher_body (inode);
-
- /* Update aliases. */
- inode->alias = true;
- inode->alias_target = resolver_decl;
- if (!inode->analyzed)
- inode->resolve_alias (cgraph_node::get (resolver_decl));
+ cgraph_function_version_info *inode_info = inode->function_version ();
+ gcc_assert (inode_info);
+
+ tree resolver_decl = NULL;
+
+ /* For target_version semantics, if there is a lone default declaration
+ it needs to be mangled, with an alias from the dispatched symbol to the
+ default version. */
+ if (!TARGET_HAS_FMV_TARGET_ATTRIBUTE
+ && TREE_STATIC (node->decl)
+ && inode_info->next
+ && !inode_info->next->next)
+ {
+ inode->alias = true;
+ inode->alias_target = inode_info->next->this_node->decl;
+ inode->externally_visible = true;
+ if (!inode->analyzed)
+ inode->resolve_alias
+ (cgraph_node::get (inode_info->next->this_node->decl));
+
+ DECL_ATTRIBUTES (idecl)
+ = make_attribute ("alias",
+ IDENTIFIER_POINTER
+ (DECL_ASSEMBLER_NAME
+ (inode_info->next->this_node->decl)),
+ DECL_ATTRIBUTES (node->decl));
+ TREE_USED (idecl) = true;
+ DECL_EXTERNAL (idecl) = false;
+ TREE_STATIC (idecl) = true;
+ return;
+ }
+ /* In target_version semantics, only create the resolver if the
+ default node is implemented. */
+ else if (TARGET_HAS_FMV_TARGET_ATTRIBUTE || TREE_STATIC (node->decl))
+ {
+ resolver_decl = targetm.generate_version_dispatcher_body (inode);
+ /* Update aliases. */
+ inode->alias = true;
+ inode->alias_target = resolver_decl;
+ if (!inode->analyzed)
+ inode->resolve_alias (cgraph_node::get (resolver_decl));
+ }
auto_vec<cgraph_edge *> edges_to_redirect;
/* We need to capture the references by value rather than just pointers to
them
@@ -299,19 +341,32 @@ expand_target_clones (struct cgraph_node *node, bool
definition)
tree assembler_name = node_v->assembler_name;
/* Change the current node into the default node. */
- gcc_assert (num_defaults == 1);
-
- /* Setting new attribute to initial function. */
- tree attributes = make_attribute (new_attr_name, "default", attrs);
- DECL_ATTRIBUTES (node->decl) = attributes;
- DECL_FUNCTION_VERSIONED (node->decl) = true;
+ if (num_defaults == 1)
+ {
+ /* Setting new attribute to initial function. */
+ tree attributes = make_attribute (new_attr_name, "default", attrs);
+ DECL_ATTRIBUTES (node->decl) = attributes;
+ DECL_FUNCTION_VERSIONED (node->decl) = true;
- node->is_target_clone = true;
- node->local = false;
+ node->is_target_clone = true;
+ node->local = false;
- /* Remangle base node after new target version string set. */
- tree id = targetm.mangle_decl_assembler_name (node->decl, assembler_name);
- symtab->change_decl_assembler_name (node->decl, id);
+ /* Remangle base node after new target version string set. */
+ tree id = targetm.mangle_decl_assembler_name (node->decl,
assembler_name);
+ symtab->change_decl_assembler_name (node->decl, id);
+ }
+ else
+ {
+ /* Target clones without a default are only allowed for target_version
+ semantics where we can have target_clones/target_version mixing. */
+ gcc_assert (!TARGET_HAS_FMV_TARGET_ATTRIBUTE);
+
+ /* If there isn't a default version, can safely remove this version.
+ The node itself gets removed after the other versions are created. */
+ cgraph_function_version_info *temp = node_v;
+ node_v = node_v->next ? node_v->next : node_v->prev;
+ cgraph_node::delete_function_version (temp);
+ }
for (string_slice attr : attr_list)
{
@@ -344,6 +399,10 @@ expand_target_clones (struct cgraph_node *node, bool
definition)
symtab->change_decl_assembler_name (new_node->decl, id);
}
+ /* If there are no default versions in the target_clones, this node is not
+ reused, so can delete this node. */
+ if (num_defaults == 0)
+ node->remove ();
return true;
}
@@ -406,15 +465,87 @@ redirect_to_specific_clone (cgraph_node *node)
}
}
+/* Checks if NODE is in the 'simple' target_clones case, which is where NODE
+ is a declaration annotated with target_clones containing the default, and it
+ is the sole function declaration in the FMV function set. */
+
+static bool
+is_simple_target_clones_case (cgraph_node *node)
+{
+ /* target attribute semantics doesnt support the complex case,
+ so this is always true. */
+ if (TARGET_HAS_FMV_TARGET_ATTRIBUTE)
+ return true;
+
+ int num_defaults = 0;
+ auto versions = get_clone_versions (node->decl, &num_defaults);
+ if (versions.is_empty () || num_defaults != 1)
+ return false;
+
+ cgraph_function_version_info *fv = node->function_version ();
+
+ if (fv && (fv->next || fv->prev))
+ return false;
+
+ return true;
+}
+
static unsigned int
-ipa_target_clone (void)
+ipa_target_clone (bool early)
{
struct cgraph_node *node;
auto_vec<cgraph_node *> to_dispatch;
+ /* Don't need to do anything early for target attribute semantics. */
+ if (early && TARGET_HAS_FMV_TARGET_ATTRIBUTE)
+ return 0;
+
+ /* For target attribute semantics, this pass skips the early phase, and in
+ the later stage is only responsible for expanding and dispatching
+ target_clone declarations, as target annotated functions are dispatched
+ in the front end.
+
+ The expanding and dispatching can be done at the late stage as the
+ target_clone functions aren't allowed to be part of a larger FMV set, so
+ all versions will all have the same body, so early optimisations are safe
+ to treat a call to a target_clones set as a call to one function.
+
+ For target_version semantics, this pass is responsible for expanding
+ target_clones and dispatching all FMV function sets, including ones only
+ made up of target_version declarations.
+
+ Cases where there is more than one declaration must be expanded and
+ dispatched at the early stage, as the declarations may have different
+ bodies, and so the early optimisation passes would not be valid.
+
+ The late stage is only used for the expansion and dispatching of the
simple
+ case where the FMV set is defined by a single target_clone attribute. */
+
FOR_EACH_FUNCTION (node)
- if (expand_target_clones (node, node->definition))
- to_dispatch.safe_push (node);
+ {
+ /* In the early stage, we need to expand any target clone that is not
+ the simple case. Simple cases are dispatched in the later stage. */
+
+ if (early == !is_simple_target_clones_case (node))
+ if (expand_target_clones (node, node->definition)
+ && TARGET_HAS_FMV_TARGET_ATTRIBUTE)
+ /* In non target_version semantics, dispatch all target clones. */
+ to_dispatch.safe_push (node);
+ }
+
+ /* In target_version semantics dispatch all FMV function sets with a default
+ implementation in the early stage.
+ Also dispatch any default versions generated by expanding target_clones
+ in the late stage. */
+
+ if (!TARGET_HAS_FMV_TARGET_ATTRIBUTE)
+ FOR_EACH_FUNCTION (node)
+ if (is_function_default_version (node->decl)
+ && DECL_FUNCTION_VERSIONED (node->decl)
+ /* Don't dispatch target clones, as they haven't been expanded so
+ are simple. */
+ && !lookup_attribute ("target_clones", DECL_ATTRIBUTES (node->decl)))
+ to_dispatch.safe_push (node);
for (unsigned i = 0; i < to_dispatch.length (); i++)
create_dispatcher_calls (to_dispatch[i]);
@@ -445,14 +576,21 @@ class pass_target_clone : public simple_ipa_opt_pass
{
public:
pass_target_clone (gcc::context *ctxt)
- : simple_ipa_opt_pass (pass_data_target_clone, ctxt)
+ : simple_ipa_opt_pass (pass_data_target_clone, ctxt), early_p (false)
{}
+ bool early_p;
+ void set_pass_param (unsigned int n, bool param) final override
+ {
+ gcc_assert (n == 0);
+ early_p = param;
+ }
/* opt_pass methods: */
bool gate (function *) final override;
+ opt_pass * clone () final override { return new pass_target_clone (m_ctxt); }
unsigned int execute (function *) final override
{
- return ipa_target_clone ();
+ return ipa_target_clone (early_p);
}
};
diff --git a/gcc/passes.def b/gcc/passes.def
index 68ce53baa0f..6f8978bb48a 100644
--- a/gcc/passes.def
+++ b/gcc/passes.def
@@ -66,6 +66,7 @@ along with GCC; see the file COPYING3. If not see
NEXT_PASS (pass_nothrow);
NEXT_PASS (pass_rebuild_cgraph_edges);
POP_INSERT_PASSES ()
+ NEXT_PASS (pass_target_clone, true);
NEXT_PASS (pass_local_optimization_passes);
PUSH_INSERT_PASSES_WITHIN (pass_local_optimization_passes)
@@ -141,7 +142,7 @@ along with GCC; see the file COPYING3. If not see
POP_INSERT_PASSES ()
POP_INSERT_PASSES ()
- NEXT_PASS (pass_target_clone);
+ NEXT_PASS (pass_target_clone, false);
NEXT_PASS (pass_ipa_auto_profile);
PUSH_INSERT_PASSES_WITHIN (pass_ipa_auto_profile)
NEXT_PASS (pass_feedback_split_functions);
diff --git a/gcc/testsuite/g++.target/aarch64/mv-1.C
b/gcc/testsuite/g++.target/aarch64/mv-1.C
index b10037f1b9b..93b8a136587 100644
--- a/gcc/testsuite/g++.target/aarch64/mv-1.C
+++ b/gcc/testsuite/g++.target/aarch64/mv-1.C
@@ -37,3 +37,7 @@ int bar()
/* { dg-final { scan-assembler-times "\n_Z3foov\._Mrng:\n" 1 } } */
/* { dg-final { scan-assembler-times "\n_Z3foov\._MrngMflagm:\n" 1 } } */
/* { dg-final { scan-assembler-times "\n_Z3foov\._Mflagm:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tbl\t_Z3foov\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3foov,
%gnu_indirect_function\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3foov,_Z3foov\.resolver\n"
1 } } */
diff --git a/gcc/testsuite/g++.target/aarch64/mv-symbols10.C
b/gcc/testsuite/g++.target/aarch64/mv-symbols10.C
new file mode 100644
index 00000000000..92d4ab617d8
--- /dev/null
+++ b/gcc/testsuite/g++.target/aarch64/mv-symbols10.C
@@ -0,0 +1,27 @@
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+/* { dg-additional-options "-Wno-experimental-fmv-target" } */
+
+__attribute__ ((target_version ("default"))) void
+foo (int a = 3);
+
+__attribute__ ((target_version ("sve"))) void
+foo (int a = 4);
+
+void bar() {
+ foo();
+}
+
+__attribute__ ((target_version ("sve"))) void
+foo2 (int a = 6);
+
+__attribute__ ((target_version ("default"))) void
+foo2 (int a = 5);
+
+void bar2() {
+ foo2();
+}
+
+
+/* { dg-final { scan-assembler-times "\n\tmov\tw\[0-9\]\+, 3\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tmov\tw\[0-9\]\+, 5\n" 1 } } */
diff --git a/gcc/testsuite/g++.target/aarch64/mv-symbols11.C
b/gcc/testsuite/g++.target/aarch64/mv-symbols11.C
new file mode 100644
index 00000000000..dadde22622e
--- /dev/null
+++ b/gcc/testsuite/g++.target/aarch64/mv-symbols11.C
@@ -0,0 +1,30 @@
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+/* { dg-additional-options "-Wno-experimental-fmv-target" } */
+
+__attribute__ ((target_version ("default"))) int
+foo () { return 1; }
+
+__attribute__ ((target_version ("dotprod"))) int
+foo () { return 3; }
+
+int (*test)();
+
+void bar ()
+{
+ test = foo;
+}
+
+__attribute__ ((target_version ("default"))) int
+foo2 ();
+
+__attribute__ ((target_version ("dotprod"))) int
+foo2 ();
+
+void bar2 ()
+{
+ test = foo2;
+}
+
+/* { dg-final { scan-assembler-times "\n\tadrp\tx\[0-9\]+, _Z3foov\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx\[0-9\]+, _Z4foo2v\n" 1 } } */
diff --git a/gcc/testsuite/g++.target/aarch64/mv-symbols12.C
b/gcc/testsuite/g++.target/aarch64/mv-symbols12.C
new file mode 100644
index 00000000000..d78ee4b91c5
--- /dev/null
+++ b/gcc/testsuite/g++.target/aarch64/mv-symbols12.C
@@ -0,0 +1,28 @@
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+/* { dg-additional-options "-Wno-experimental-fmv-target" } */
+
+int foo () {
+ return 1;
+}
+
+void
+bar ()
+{
+ foo ();
+}
+
+__attribute__ ((target_version ("dotprod"))) int
+foo ();
+
+/* { dg-final { scan-assembler-times "\n_Z3foov\.default:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\._Mdotprod:\n" 0 } } */
+
+/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx\[0-9\]+,
_Z3foov\._Mdotprod\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx\[0-9\]+, _Z3foov\.default\n"
1 } } */
+
+/* { dg-final { scan-assembler-times "\n\tbl\t_Z3foov\n" 1 } } */
+
+/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3foov,
%gnu_indirect_function\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3foov,_Z3foov\.resolver\n"
1 } } */
diff --git a/gcc/testsuite/g++.target/aarch64/mv-symbols13.C
b/gcc/testsuite/g++.target/aarch64/mv-symbols13.C
new file mode 100644
index 00000000000..997b9bad6d6
--- /dev/null
+++ b/gcc/testsuite/g++.target/aarch64/mv-symbols13.C
@@ -0,0 +1,28 @@
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+/* { dg-additional-options "-Wno-experimental-fmv-target" } */
+
+int foo () {
+ return 1;
+}
+
+void bar ()
+{
+ int (*test)() = foo;
+
+ test();
+}
+
+__attribute__ ((target_version ("dotprod"))) int foo ();
+
+/* { dg-final { scan-assembler-times "\n_Z3foov\.default:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\._Mdotprod:\n" 0 } } */
+
+/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx\[0-9\]+,
_Z3foov\._Mdotprod\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx\[0-9\]+, _Z3foov\.default\n"
1 } } */
+
+/* { dg-final { scan-assembler-times "\n\tadrp\tx\[0-9\]+, _Z3foov\n" 1 } } */
+
+/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3foov,
%gnu_indirect_function\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3foov,_Z3foov\.resolver\n"
1 } } */
diff --git a/gcc/testsuite/g++.target/aarch64/mv-symbols2.C
b/gcc/testsuite/g++.target/aarch64/mv-symbols2.C
index 6da88ddfb48..55f2d48f5e4 100644
--- a/gcc/testsuite/g++.target/aarch64/mv-symbols2.C
+++ b/gcc/testsuite/g++.target/aarch64/mv-symbols2.C
@@ -41,13 +41,13 @@ int foo (int)
/* { dg-final { scan-assembler-times "\n_Z3foov\.default:\n" 1 } } */
/* { dg-final { scan-assembler-times "\n_Z3foov\._Mdotprod:\n" 1 } } */
/* { dg-final { scan-assembler-times "\n_Z3foov\._MsveMsve2:\n" 1 } } */
-/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 0 } } */
-/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3foov,
%gnu_indirect_function\n" 0 } } */
-/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3foov,_Z3foov\.resolver\n"
0 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3foov,
%gnu_indirect_function\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3foov,_Z3foov\.resolver\n"
1 } } */
/* { dg-final { scan-assembler-times "\n_Z3fooi\.default:\n" 1 } } */
/* { dg-final { scan-assembler-times "\n_Z3fooi\._Mdotprod:\n" 1 } } */
/* { dg-final { scan-assembler-times "\n_Z3fooi\._MsveMsve2:\n" 1 } } */
-/* { dg-final { scan-assembler-times "\n_Z3fooi\.resolver:\n" 0 } } */
-/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3fooi,
%gnu_indirect_function\n" 0 } } */
-/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3fooi,_Z3fooi\.resolver\n"
0 } } */
+/* { dg-final { scan-assembler-times "\n_Z3fooi\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3fooi,
%gnu_indirect_function\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3fooi,_Z3fooi\.resolver\n"
1 } } */
diff --git a/gcc/testsuite/g++.target/aarch64/mv-symbols3.C
b/gcc/testsuite/g++.target/aarch64/mv-symbols3.C
index 5dd7b49be2a..6ba02a2aae9 100644
--- a/gcc/testsuite/g++.target/aarch64/mv-symbols3.C
+++ b/gcc/testsuite/g++.target/aarch64/mv-symbols3.C
@@ -29,10 +29,10 @@ int bar()
/* { dg-final { scan-assembler-times "\n_Z3foov\.default:\n" 0 } } */
/* { dg-final { scan-assembler-times "\n_Z3foov\._Mdotprod:\n" 0 } } */
/* { dg-final { scan-assembler-times "\n_Z3foov\._MsveMsve2:\n" 0 } } */
-/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 0 } } */
/* { dg-final { scan-assembler-times "\n\tbl\t_Z3foov\n" 1 } } */
-/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3foov,
%gnu_indirect_function\n" 1 } } */
-/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3foov,_Z3foov\.resolver\n"
1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3foov,
%gnu_indirect_function\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3foov,_Z3foov\.resolver\n"
0 } } */
/* { dg-final { scan-assembler-times "\n_Z3fooi\.default:\n" 0 } } */
/* { dg-final { scan-assembler-times "\n_Z3fooi\._Mdotprod:\n" 0 } } */
diff --git a/gcc/testsuite/g++.target/aarch64/mv-symbols4.C
b/gcc/testsuite/g++.target/aarch64/mv-symbols4.C
index 4b25d17cc15..cc013c47848 100644
--- a/gcc/testsuite/g++.target/aarch64/mv-symbols4.C
+++ b/gcc/testsuite/g++.target/aarch64/mv-symbols4.C
@@ -44,6 +44,6 @@ int bar()
/* { dg-final { scan-assembler-times "\n_Z3fooi\.default:\n" 1 } } */
/* { dg-final { scan-assembler-times "\n_Z3fooi\._Mdotprod:\n" 0 } } */
/* { dg-final { scan-assembler-times "\n_Z3fooi\._MsveMsve2:\n" 0 } } */
-/* { dg-final { scan-assembler-times "\n_Z3fooi\.resolver:\n" 0 } } */
-/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3fooi,
%gnu_indirect_function\n" 0 } } */
-/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3fooi,_Z3fooi\.resolver\n"
0 } } */
+/* { dg-final { scan-assembler-times "\n_Z3fooi\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3fooi,
%gnu_indirect_function\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3fooi,_Z3fooi\.resolver\n"
1 } } */
diff --git a/gcc/testsuite/g++.target/aarch64/mv-symbols5.C
b/gcc/testsuite/g++.target/aarch64/mv-symbols5.C
index fac00b20313..1396ca379e4 100644
--- a/gcc/testsuite/g++.target/aarch64/mv-symbols5.C
+++ b/gcc/testsuite/g++.target/aarch64/mv-symbols5.C
@@ -44,10 +44,10 @@ int bar()
/* { dg-final { scan-assembler-times "\n_Z3foov\.default:\n" 0 } } */
/* { dg-final { scan-assembler-times "\n_Z3foov\._Mdotprod:\n" 1 } } */
/* { dg-final { scan-assembler-times "\n_Z3foov\._MsveMsve2:\n" 1 } } */
-/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 0 } } */
/* { dg-final { scan-assembler-times "\n\tbl\t_Z3foov\n" 1 } } */
-/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3foov,
%gnu_indirect_function\n" 1 } } */
-/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3foov,_Z3foov\.resolver\n"
1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3foov,
%gnu_indirect_function\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3foov,_Z3foov\.resolver\n"
0 } } */
/* { dg-final { scan-assembler-times "\n_Z3fooi\.default:\n" 0 } } */
/* { dg-final { scan-assembler-times "\n_Z3fooi\._Mdotprod:\n" 1 } } */
diff --git a/gcc/testsuite/g++.target/aarch64/mv-symbols6.C
b/gcc/testsuite/g++.target/aarch64/mv-symbols6.C
new file mode 100644
index 00000000000..2b67bcb47a4
--- /dev/null
+++ b/gcc/testsuite/g++.target/aarch64/mv-symbols6.C
@@ -0,0 +1,21 @@
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+/* { dg-additional-options "-Wno-experimental-fmv-target" } */
+
+__attribute__ ((target_version ("default"))) int
+foo ()
+{
+ return 1;
+}
+
+int bar()
+{
+ return foo();
+}
+
+/* { dg-final { scan-assembler-times "\n_Z3foov:\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\.default:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 0 } } */
+/* { dg-final { scan-assembler-times "bl\t_Z3foov.default\n" 1 } } */
+/* { dg-final { scan-assembler-times ".global\t_Z3foov\n" 1 } } */
+/* { dg-final { scan-assembler-times ".set\t_Z3foov,_Z3foov.default\n" 1 } } */
diff --git a/gcc/testsuite/g++.target/aarch64/mv-symbols7.C
b/gcc/testsuite/g++.target/aarch64/mv-symbols7.C
new file mode 100644
index 00000000000..3998adb54a7
--- /dev/null
+++ b/gcc/testsuite/g++.target/aarch64/mv-symbols7.C
@@ -0,0 +1,48 @@
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+/* { dg-additional-options "-Wno-experimental-fmv-target" } */
+
+__attribute__ ((target_version ("dotprod"))) int
+foo ();
+
+__attribute__ ((target_version ("sve+sve2"))) int
+foo ();
+
+__attribute__ ((target_version ("default"))) int
+foo ();
+
+__attribute__ ((target_version ("sve+sve2"))) int
+foo ()
+{
+ return 5;
+}
+__attribute__ ((target_version ("dotprod"))) int
+foo ()
+{
+ return 3;
+}
+__attribute__ ((target_version ("default"))) int
+foo ()
+{
+ return 1;
+}
+
+int
+bar ()
+{
+ return foo ();
+}
+
+/* { dg-final { scan-assembler-times "\n_Z3foov\.default:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\._Mdotprod:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\._MsveMsve2:\n" 1 } } */
+
+/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx\[0-9\]+,
_Z3foov\._MsveMsve2\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx\[0-9\]+,
_Z3foov\._Mdotprod\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx\[0-9\]+, _Z3foov\.default\n"
1 } } */
+
+/* { dg-final { scan-assembler-times "\n\tbl\t_Z3foov\n" 1 } } */
+
+/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3foov,
%gnu_indirect_function\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3foov,_Z3foov\.resolver\n"
1 } } */
diff --git a/gcc/testsuite/g++.target/aarch64/mv-symbols8.C
b/gcc/testsuite/g++.target/aarch64/mv-symbols8.C
new file mode 100644
index 00000000000..5983bbd6925
--- /dev/null
+++ b/gcc/testsuite/g++.target/aarch64/mv-symbols8.C
@@ -0,0 +1,46 @@
+/* { dg-do compile } */
+/* { dg-require-ifunc "" } */
+/* { dg-options "-O0" } */
+/* { dg-additional-options "-Wno-experimental-fmv-target" } */
+
+__attribute__ ((target_version ("dotprod"))) int
+foo ();
+__attribute__ ((target_version ("sve+sve2"))) int
+foo ();
+
+int
+foo ()
+{
+ return 1;
+}
+
+__attribute__ ((target_version ("dotprod"))) int
+foo ()
+{
+ return 3;
+}
+__attribute__ ((target_version ("sve+sve2"))) int
+foo ()
+{
+ return 5;
+}
+
+int
+bar ()
+{
+ return foo ();
+}
+
+/* { dg-final { scan-assembler-times "\n_Z3foov\.default:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\._Mdotprod:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\._MsveMsve2:\n" 1 } } */
+
+/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx\[0-9\]+,
_Z3foov\._MsveMsve2\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx\[0-9\]+,
_Z3foov\._Mdotprod\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx\[0-9\]+, _Z3foov\.default\n"
1 } } */
+
+/* { dg-final { scan-assembler-times "\n\tbl\t_Z3foov\n" 1 } } */
+
+/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3foov,
%gnu_indirect_function\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3foov,_Z3foov\.resolver\n"
1 } } */
diff --git a/gcc/testsuite/g++.target/aarch64/mv-symbols9.C
b/gcc/testsuite/g++.target/aarch64/mv-symbols9.C
new file mode 100644
index 00000000000..bfad9bb5850
--- /dev/null
+++ b/gcc/testsuite/g++.target/aarch64/mv-symbols9.C
@@ -0,0 +1,43 @@
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+/* { dg-additional-options "-Wno-experimental-fmv-target" } */
+
+int
+foo ();
+
+int
+foo ()
+{
+ return 1;
+}
+
+__attribute__ ((target_version ("dotprod"))) int
+foo ()
+{
+ return 3;
+}
+__attribute__ ((target_version ("sve+sve2"))) int
+foo ()
+{
+ return 5;
+}
+
+int
+bar ()
+{
+ return foo ();
+}
+
+/* { dg-final { scan-assembler-times "\n_Z3foov\.default:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\._Mdotprod:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\._MsveMsve2:\n" 1 } } */
+
+/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx\[0-9\]+,
_Z3foov\._MsveMsve2\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx\[0-9\]+,
_Z3foov\._Mdotprod\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx\[0-9\]+, _Z3foov\.default\n"
1 } } */
+
+/* { dg-final { scan-assembler-times "\n\tbl\t_Z3foov\n" 1 } } */
+
+/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3foov,
%gnu_indirect_function\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3foov,_Z3foov\.resolver\n"
1 } } */
diff --git a/gcc/testsuite/g++.target/aarch64/mvc-symbols3.C
b/gcc/testsuite/g++.target/aarch64/mvc-symbols3.C
index 350a5586643..2a315d2db5c 100644
--- a/gcc/testsuite/g++.target/aarch64/mvc-symbols3.C
+++ b/gcc/testsuite/g++.target/aarch64/mvc-symbols3.C
@@ -22,15 +22,15 @@ int bar(int x)
/* { dg-final { scan-assembler-times "\n_Z3foov\.default:\n" 0 } } */
/* { dg-final { scan-assembler-times "\n_Z3foov\._Mdotprod:\n" 0 } } */
/* { dg-final { scan-assembler-times "\n_Z3foov\._MsveMsve2:\n" 0 } } */
-/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 0 } } */
/* { dg-final { scan-assembler-times "\n\tbl\t_Z3foov\n" 1 } } */
-/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3foov,
%gnu_indirect_function\n" 1 } } */
-/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3foov,_Z3foov\.resolver\n"
1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3foov,
%gnu_indirect_function\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3foov,_Z3foov\.resolver\n"
0 } } */
/* { dg-final { scan-assembler-times "\n_Z3fooi\.default:\n" 0 } } */
/* { dg-final { scan-assembler-times "\n_Z3fooi\._Mdotprod:\n" 0 } } */
/* { dg-final { scan-assembler-times "\n_Z3fooi\._MsveMsve2:\n" 0 } } */
-/* { dg-final { scan-assembler-times "\n_Z3fooi\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3fooi\.resolver:\n" 0 } } */
/* { dg-final { scan-assembler-times "\n\tbl\t_Z3fooi\n" 1 } } */
-/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3fooi,
%gnu_indirect_function\n" 1 } } */
-/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3fooi,_Z3fooi\.resolver\n"
1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3fooi,
%gnu_indirect_function\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3fooi,_Z3fooi\.resolver\n"
0 } } */
diff --git a/gcc/testsuite/g++.target/riscv/mv-symbols2.C
b/gcc/testsuite/g++.target/riscv/mv-symbols2.C
index 43fa1502b7d..8a5c5a0bc9f 100644
--- a/gcc/testsuite/g++.target/riscv/mv-symbols2.C
+++ b/gcc/testsuite/g++.target/riscv/mv-symbols2.C
@@ -47,15 +47,15 @@ int foo (int)
/* { dg-final { scan-assembler-times "\n_Z3foov\.default:\n" 1 } } */
/* { dg-final { scan-assembler-times "\n_Z3foov\.arch__v:\n" 1 } } */
/* { dg-final { scan-assembler-times "\n_Z3foov\.arch__zba__zbb:\n" 1 } } */
-/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 1 } } */
/* { dg-final { scan-assembler-times "\n\tcall\t_Z3foov\n" 0 } } */
-/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3foov,
@gnu_indirect_function\n" 0 } } */
-/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3foov,_Z3foov\.resolver\n"
0 } } */
+/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3foov,
@gnu_indirect_function\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3foov,_Z3foov\.resolver\n"
1 } } */
/* { dg-final { scan-assembler-times "\n_Z3fooi\.default:\n" 1 } } */
/* { dg-final { scan-assembler-times "\n_Z3fooi\.arch__v:\n" 1 } } */
/* { dg-final { scan-assembler-times "\n_Z3fooi\.arch__zba__zbb:\n" 1 } } */
-/* { dg-final { scan-assembler-times "\n_Z3fooi\.resolver:\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n_Z3fooi\.resolver:\n" 1 } } */
/* { dg-final { scan-assembler-times "\n\tcall\t_Z3fooi\n" 0 } } */
-/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3fooi,
@gnu_indirect_function\n" 0 } } */
-/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3fooi,_Z3fooi\.resolver\n"
0 } } */
+/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3fooi,
@gnu_indirect_function\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3fooi,_Z3fooi\.resolver\n"
1 } } */
diff --git a/gcc/testsuite/g++.target/riscv/mv-symbols3.C
b/gcc/testsuite/g++.target/riscv/mv-symbols3.C
index 6596a277f6a..2a7b2bc8e42 100644
--- a/gcc/testsuite/g++.target/riscv/mv-symbols3.C
+++ b/gcc/testsuite/g++.target/riscv/mv-symbols3.C
@@ -36,10 +36,10 @@ int bar()
/* { dg-final { scan-assembler-times "\n_Z3foov\.default:\n" 0 } } */
/* { dg-final { scan-assembler-times "\n_Z3foov\.arch__v:\n" 0 } } */
/* { dg-final { scan-assembler-times "\n_Z3foov\.arch__zba__zbb:\n" 0 } } */
-/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 0 } } */
/* { dg-final { scan-assembler-times "\n\tcall\t_Z3foov(?:@plt)?\n" 1 } } */
-/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3foov,
@gnu_indirect_function\n" 1 } } */
-/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3foov,_Z3foov\.resolver\n"
1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3foov,
@gnu_indirect_function\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3foov,_Z3foov\.resolver\n"
0 } } */
/* { dg-final { scan-assembler-times "\n_Z3fooi\.default:\n" 0 } } */
/* { dg-final { scan-assembler-times "\n_Z3fooi\.arch__v:\n" 0 } } */
diff --git a/gcc/testsuite/g++.target/riscv/mv-symbols4.C
b/gcc/testsuite/g++.target/riscv/mv-symbols4.C
index 83d51e33706..7665e775239 100644
--- a/gcc/testsuite/g++.target/riscv/mv-symbols4.C
+++ b/gcc/testsuite/g++.target/riscv/mv-symbols4.C
@@ -50,7 +50,7 @@ int bar()
/* { dg-final { scan-assembler-times "\n_Z3fooi\.default:\n" 1 } } */
/* { dg-final { scan-assembler-times "\n_Z3fooi\.arch__v:\n" 0 } } */
/* { dg-final { scan-assembler-times "\n_Z3fooi\.arch__zba__zbb:\n" 0 } } */
-/* { dg-final { scan-assembler-times "\n_Z3fooi\.resolver:\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n_Z3fooi\.resolver:\n" 1 } } */
/* { dg-final { scan-assembler-times "\n\tcall\t_Z3fooi(?:@plt)?\n" 0 } } */
-/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3fooi,
@gnu_indirect_function\n" 0 } } */
-/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3fooi,_Z3fooi\.resolver\n"
0 } } */
+/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3fooi,
@gnu_indirect_function\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3fooi,_Z3fooi\.resolver\n"
1 } } */
diff --git a/gcc/testsuite/g++.target/riscv/mv-symbols5.C
b/gcc/testsuite/g++.target/riscv/mv-symbols5.C
index c92421ab0fb..5ca31851618 100644
--- a/gcc/testsuite/g++.target/riscv/mv-symbols5.C
+++ b/gcc/testsuite/g++.target/riscv/mv-symbols5.C
@@ -48,10 +48,10 @@ int bar()
/* { dg-final { scan-assembler-times "\n_Z3foov\.default:\n" 0 } } */
/* { dg-final { scan-assembler-times "\n_Z3foov\.arch__v:\n" 1 } } */
/* { dg-final { scan-assembler-times "\n_Z3foov\.arch__zba__zbb:\n" 1 } } */
-/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 0 } } */
/* { dg-final { scan-assembler-times "\n\tcall\t_Z3foov(?:@plt)?\n" 1 } } */
-/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3foov,
@gnu_indirect_function\n" 1 } } */
-/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3foov,_Z3foov\.resolver\n"
1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3foov,
@gnu_indirect_function\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3foov,_Z3foov\.resolver\n"
0 } } */
/* { dg-final { scan-assembler-times "\n_Z3fooi\.default:\n" 0 } } */
/* { dg-final { scan-assembler-times "\n_Z3fooi\.arch__v:\n" 1 } } */
diff --git a/gcc/testsuite/g++.target/riscv/mvc-symbols3.C
b/gcc/testsuite/g++.target/riscv/mvc-symbols3.C
index 78f027b0457..e3987eaa75a 100644
--- a/gcc/testsuite/g++.target/riscv/mvc-symbols3.C
+++ b/gcc/testsuite/g++.target/riscv/mvc-symbols3.C
@@ -28,15 +28,15 @@ int bar(int x)
/* { dg-final { scan-assembler-times "\n_Z3foov\.default:\n" 0 } } */
/* { dg-final { scan-assembler-times "\n_Z3foov\.arch__v:\n" 0 } } */
/* { dg-final { scan-assembler-times "\n_Z3foov\.arch__zba__zbb:\n" 0 } } */
-/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 0 } } */
/* { dg-final { scan-assembler-times "\n\tcall\t_Z3foov(?:@plt)?\n" 1 } } */
-/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3foov,
@gnu_indirect_function\n" 1 } } */
-/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3foov,_Z3foov\.resolver\n"
1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3foov,
@gnu_indirect_function\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3foov,_Z3foov\.resolver\n"
0 } } */
/* { dg-final { scan-assembler-times "\n_Z3fooi\.default:\n" 0 } } */
/* { dg-final { scan-assembler-times "\n_Z3fooi\.arch__v:\n" 0 } } */
/* { dg-final { scan-assembler-times "\n_Z3fooi\.arch__zba__zbb:\n" 0 } } */
-/* { dg-final { scan-assembler-times "\n_Z3fooi\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3fooi\.resolver:\n" 0 } } */
/* { dg-final { scan-assembler-times "\n\tcall\t_Z3fooi(?:@plt)?\n" 1 } } */
-/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3fooi,
@gnu_indirect_function\n" 1 } } */
-/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3fooi,_Z3fooi\.resolver\n"
1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.type\t_Z3fooi,
@gnu_indirect_function\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\t_Z3fooi,_Z3fooi\.resolver\n"
0 } } */