The 08/22/2025 12:22, Jason Merrill wrote: > On 8/19/25 4:48 AM, Alfie Richards wrote: > > 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 5415af2a73b..441a5cf9b42 100644 > > --- a/gcc/config/aarch64/aarch64.cc > > +++ b/gcc/config/aarch64/aarch64.cc > > @@ -21052,42 +21052,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 ()) > > { > > @@ -21097,7 +21084,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 419eec7ed16..3c953804804 100644 > > --- a/gcc/config/riscv/riscv.cc > > +++ b/gcc/config/riscv/riscv.cc > > @@ -14573,42 +14573,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 ()) > > { > > @@ -14618,7 +14605,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 63cad2aca4c..a6a3fd0d6bb 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 14acb9c23c0..5e9ad1724d5 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; > > Instead of these I wonder about changing update_binding to drop non-default > versions from the name binding? But the C++ changes are OK as is if you'd > rather not explore that direction. > > Jason
Hi Jason, I'd be happy to explore that directions, however I'm not sure what that would look like? We need to be able to detect symbol conflicts still in order to build the FMV set. In the below case, for instance, we need to be able to find the SVE decl at the point of parsing the default decl, so we would still need to attach something (either the sve decl or the generated dispatched decl) to the binding? ```c++ [[gnu::target_version("sve")]] int foo(); int foo(); ``` However, the FMV funciton set itself isn't resolvable until the default version is declared. So the below example needs to be invalid. ```c++ [[gnu::target_version("sve")]] int foo(); int bar () {return foo();} int foo(); ``` So I think we would still need some check to make FMV bindihngs without a default non-resolvable? Do you have a that makes this possible? Thanks, Alfie -- Alfie Richards