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

Reply via email to