This commit introduces a new target hook, TARGET_VERSION_COMPATIBLE, enabling the redirection of specific target clones based on version compatibility. Additionally, it modifies the existing redirection mechanism to support this new hook, which identifies the most prioritized MV clone compatible with the current caller version and redirects to it.
This mechanism has been discussed in a previous patch [1]. [1] https://patchwork.sourceware.org/comment/197172/ Signed-off-by: Yangyu Chen <c...@cyyself.name> gcc/ChangeLog: * doc/tm.texi: New Target Hook TARGET_VERSION_COMPATIBLE. * doc/tm.texi.in: Ditto. * target.def: Ditto. * multiple_target.cc (redirect_to_specific_clone): Support redirect_to_specific_clone with TARGET_VERSION_COMPATIBLE. (ipa_target_clone): Keep calling redirect_to_specific_clone even if TARGET_HAS_FMV_TARGET_ATTRIBUTE is defined. --- gcc/doc/tm.texi | 7 +++++ gcc/doc/tm.texi.in | 2 ++ gcc/multiple_target.cc | 58 ++++++++++++++++++++++++++---------------- gcc/target.def | 12 +++++++++ 4 files changed, 57 insertions(+), 22 deletions(-) diff --git a/gcc/doc/tm.texi b/gcc/doc/tm.texi index a96700c0d38..14ceabf15b5 100644 --- a/gcc/doc/tm.texi +++ b/gcc/doc/tm.texi @@ -12258,6 +12258,13 @@ is checked for dispatching earlier. @var{decl1} and @var{decl2} are the two function decls that will be compared. @end deftypefn +@deftypefn {Target Hook} bool TARGET_VERSION_COMPATIBLE (tree @var{caller}, tree @var{callee}) +This hook is used to check if two function versions are compatible to +determine if a specific version of caller can call a specific version of +callee directly. @var{caller} and @var{callee} are the two function decls +that will be checked for compatibility. +@end deftypefn + @deftypefn {Target Hook} tree TARGET_GET_FUNCTION_VERSIONS_DISPATCHER (void *@var{decl}) This hook is used to get the dispatcher function for a set of function versions. The dispatcher function is called to invoke the right function diff --git a/gcc/doc/tm.texi.in b/gcc/doc/tm.texi.in index eccc4d88493..393912fd257 100644 --- a/gcc/doc/tm.texi.in +++ b/gcc/doc/tm.texi.in @@ -7881,6 +7881,8 @@ to by @var{ce_info}. @hook TARGET_COMPARE_VERSION_PRIORITY +@hook TARGET_VERSION_COMPATIBLE + @hook TARGET_GET_FUNCTION_VERSIONS_DISPATCHER @hook TARGET_GENERATE_VERSION_DISPATCHER_BODY diff --git a/gcc/multiple_target.cc b/gcc/multiple_target.cc index d25277c0a93..b355482b020 100644 --- a/gcc/multiple_target.cc +++ b/gcc/multiple_target.cc @@ -445,25 +445,20 @@ expand_target_clones (struct cgraph_node *node, bool definition) } /* When NODE is a target clone, consider all callees and redirect - to a clone with equal target attributes. That prevents multiple - multi-versioning dispatches and a call-chain can be optimized. - - This optimisation might pick the wrong version in some cases, since knowing - that we meet the target requirements for a matching callee version does not - tell us that we won't also meet the target requirements for a higher - priority callee version at runtime. Since this is longstanding behaviour - for x86 and powerpc, we preserve it for those targets, but skip the optimisation - for targets that use the "target_version" attribute for multi-versioning. */ + to a clone with equal target attributes when there is no higher + priority version is available. That prevents multiple + multi-versioning dispatches and a call-chain can be optimized. */ static void redirect_to_specific_clone (cgraph_node *node) { + static const char *fmv_attr = (TARGET_HAS_FMV_TARGET_ATTRIBUTE + ? "target" : "target_version"); cgraph_function_version_info *fv = node->function_version (); if (fv == NULL) return; - gcc_assert (TARGET_HAS_FMV_TARGET_ATTRIBUTE); - tree attr_target = lookup_attribute ("target", DECL_ATTRIBUTES (node->decl)); + tree attr_target = lookup_attribute (fmv_attr, DECL_ATTRIBUTES (node->decl)); if (attr_target == NULL_TREE) return; @@ -474,7 +469,7 @@ redirect_to_specific_clone (cgraph_node *node) if (!fv2) continue; - tree attr_target2 = lookup_attribute ("target", + tree attr_target2 = lookup_attribute (fmv_attr, DECL_ATTRIBUTES (e->callee->decl)); /* Function is not calling proper target clone. */ @@ -484,20 +479,40 @@ redirect_to_specific_clone (cgraph_node *node) while (fv2->prev != NULL) fv2 = fv2->prev; - /* Try to find a clone with equal target attribute. */ + /* Try to find a clone with best target attribute and compatible with + current caller. */ + cgraph_node *best_callee = NULL; for (; fv2 != NULL; fv2 = fv2->next) { cgraph_node *callee = fv2->this_node; - attr_target2 = lookup_attribute ("target", + attr_target2 = lookup_attribute (fmv_attr, DECL_ATTRIBUTES (callee->decl)); - if (attr_target2 != NULL_TREE - && attribute_value_equal (attr_target, attr_target2)) + if (attr_target2 != NULL_TREE) { - e->redirect_callee (callee); - cgraph_edge::redirect_call_stmt_to_callee (e); - break; + if (targetm.compare_version_priority + && targetm.version_compatible) + { + if (targetm.version_compatible (node->decl, callee->decl) + && (best_callee == NULL || + targetm.compare_version_priority (best_callee->decl, + callee->decl) < 0)) + best_callee = callee; + } + else + { + /* For targets does not support compare_version_priority, + we need to check if the target attributes are equal. */ + if (attribute_value_equal (attr_target, attr_target2)) + best_callee = callee; + } } } + + if (best_callee != NULL) + { + e->redirect_callee (best_callee); + cgraph_edge::redirect_call_stmt_to_callee (e); + } } } } @@ -515,9 +530,8 @@ ipa_target_clone (void) for (unsigned i = 0; i < to_dispatch.length (); i++) create_dispatcher_calls (to_dispatch[i]); - if (TARGET_HAS_FMV_TARGET_ATTRIBUTE) - FOR_EACH_FUNCTION (node) - redirect_to_specific_clone (node); + FOR_EACH_FUNCTION (node) + redirect_to_specific_clone (node); return 0; } diff --git a/gcc/target.def b/gcc/target.def index 6c7cdc8126b..57faf7b9cb6 100644 --- a/gcc/target.def +++ b/gcc/target.def @@ -2564,6 +2564,18 @@ is checked for dispatching earlier. @var{decl1} and @var{decl2} are\n\ the two function decls that will be compared.", int, (tree decl1, tree decl2), NULL) +/* Target hook is used to check if two function versions are compatible to + determine if a specific version of caller can call a specific version of + callee directly. CALLER and CALLEE are the two function decls that will + be checked for compatibility. */ +DEFHOOK +(version_compatible, + "This hook is used to check if two function versions are compatible to\n\ +determine if a specific version of caller can call a specific version of\n\ +callee directly. @var{caller} and @var{callee} are the two function decls\n\ +that will be checked for compatibility.", + bool, (tree caller, tree callee), NULL) + /* Target hook is used to generate the dispatcher logic to invoke the right function version at run-time for a given set of function versions. ARG points to the callgraph node of the dispatcher function whose body -- 2.49.0