https://gcc.gnu.org/g:564bd7c0b472d5ce1d112c6111dedc1a480bb3f1

commit 564bd7c0b472d5ce1d112c6111dedc1a480bb3f1
Author: Alfie Richards <alfie.richa...@arm.com>
Date:   Mon Mar 24 13:20:01 2025 +0000

    Refactor FMV frontend hooks and logic.
    
    This change refactors FMV handling in the frontend to allows greater
    reasoning about versions in shared code.
    
    This is needed for target_version semantics and allowing target_clones
    and target_versions to both be used for the declaration there are now
    two questions that need to be answered for the front end.
    
    1. Are these two declarations completely distinct FMV declarations
    (ie. the versions they define have no overlap). If so, they don't match.
    2. Are these two declarations matching and therefore mergeable.
    (ie. two target_clone decls that define the same set of versions, or
    an un-annotated declaration, and a target_clones definition containing the
    default version). If so, the existing merging logic should be used to
    try to merge these and diagnose if it's not possible. If not, then this
    needs to be diagnosed.
    
    To do this the common_function_versions function has been renamed
    distinct_function_versions (meaning, are the versions defined by these
    two functions completely distinct from eachother).
    
    The common function version hook was changed to instead take two
    string_slice's and determine if they define the same version.
    
    There is a new function, called mergeable_version_decls which checks
    if two decls (which define overlapping versions) can be merged.
    For example, if they are two target_clone decls which define the exact
    same set of versions.
    
    This change also records the conflicting version so that it can be
    included in diagnostics.
    
    gcc/ChangeLog:
    
            * attribs.cc (attr_strcmp): Moved to target specific code.
            (sorted_attr_string): Moved to target specific code.
            (common_function_versions): New function.
            * attribs.h (sorted_attr_string): Removed.
            (common_function_versions): New function.
            * config/aarch64/aarch64.cc (aarch64_common_function_versions):
            New function.
            * config/riscv/riscv.cc (riscv_common_function_versions): New 
function.
            * doc/tm.texi: Regenerated.
            * target.def: Change common_function_versions hook.
            * tree.cc (distinct_version_decls): New function.
            (mergeable_version_decls): Ditto.
            * tree.h (distinct_version_decls): New function.
            (mergeable_version_decls): Ditto.
    
    gcc/cp/ChangeLog:
    
            * class.cc (resolve_address_of_overloaded_function): Updated to use
            distinct_version_decls instead of common_function_version hook.
            * cp-tree.h (decls_match): Updated to use
            distinct_version_decls instead of common_function_version hook.
            * decl.cc (decls_match): Refacture to use distinct_version_decls and
            to pass through conflicting_version argument.
            (maybe_version_functions): Updated to use
            distinct_version_decls instead of common_function_version hook.
            (duplicate_decls): Add logic to handle conflicting unmergable decls
            and improve diagnostics for conflicting versions.
            * decl2.cc (check_classfn): Updated to use
            distinct_version_decls instead of common_function_version hook.

Diff:
---
 gcc/attribs.cc                |  75 ++--------------
 gcc/attribs.h                 |   3 +-
 gcc/config/aarch64/aarch64.cc |  16 ++--
 gcc/config/riscv/riscv.cc     |  30 ++++---
 gcc/cp/class.cc               |   4 +-
 gcc/cp/cp-tree.h              |   2 +-
 gcc/cp/decl.cc                |  43 +++++++--
 gcc/cp/decl2.cc               |   2 +-
 gcc/doc/tm.texi               |   4 +-
 gcc/target.def                |   6 +-
 gcc/tree.cc                   | 204 ++++++++++++++++++++++++++++++++++++++++++
 gcc/tree.h                    |   6 ++
 12 files changed, 291 insertions(+), 104 deletions(-)

diff --git a/gcc/attribs.cc b/gcc/attribs.cc
index 80833388ff2e..04a9e743dbe0 100644
--- a/gcc/attribs.cc
+++ b/gcc/attribs.cc
@@ -1086,7 +1086,14 @@ make_attribute (string_slice name, string_slice 
arg_name, tree chain)
   return attr;
 }
 
-/* Common functions used for target clone support.  */
+/* Used for targets with target_version semantics.  */
+
+bool
+common_function_versions (string_slice fn1 ATTRIBUTE_UNUSED,
+                         string_slice fn2 ATTRIBUTE_UNUSED)
+{
+  gcc_unreachable ();
+}
 
 /* Comparator function to be used in qsort routine to sort attribute
    specification strings to "target".  */
@@ -1176,72 +1183,6 @@ sorted_attr_string (tree arglist)
   XDELETEVEC (attr_str);
   return ret_str;
 }
-
-
-/* This function returns true if FN1 and FN2 are versions of the same function,
-   that is, the target strings of the function decls are different.  This 
assumes
-   that FN1 and FN2 have the same signature.  */
-
-bool
-common_function_versions (tree fn1, tree fn2)
-{
-  tree attr1, attr2;
-  char *target1, *target2;
-  bool result;
-
-  if (TREE_CODE (fn1) != FUNCTION_DECL
-      || TREE_CODE (fn2) != FUNCTION_DECL)
-    return false;
-
-  attr1 = lookup_attribute ("target", DECL_ATTRIBUTES (fn1));
-  attr2 = lookup_attribute ("target", DECL_ATTRIBUTES (fn2));
-
-  /* At least one function decl should have the target attribute specified.  */
-  if (attr1 == NULL_TREE && attr2 == NULL_TREE)
-    return false;
-
-  /* Diagnose missing target attribute if one of the decls is already
-     multi-versioned.  */
-  if (attr1 == NULL_TREE || attr2 == NULL_TREE)
-    {
-      if (DECL_FUNCTION_VERSIONED (fn1) || DECL_FUNCTION_VERSIONED (fn2))
-       {
-         if (attr2 != NULL_TREE)
-           {
-             std::swap (fn1, fn2);
-             attr1 = attr2;
-           }
-         auto_diagnostic_group d;
-         error_at (DECL_SOURCE_LOCATION (fn2),
-                   "missing %<target%> attribute for multi-versioned %qD",
-                   fn2);
-         inform (DECL_SOURCE_LOCATION (fn1),
-                 "previous declaration of %qD", fn1);
-         /* Prevent diagnosing of the same error multiple times.  */
-         DECL_ATTRIBUTES (fn2)
-           = tree_cons (get_identifier ("target"),
-                        copy_node (TREE_VALUE (attr1)),
-                        DECL_ATTRIBUTES (fn2));
-       }
-      return false;
-    }
-
-  target1 = sorted_attr_string (TREE_VALUE (attr1));
-  target2 = sorted_attr_string (TREE_VALUE (attr2));
-
-  /* The sorted target strings must be different for fn1 and fn2
-     to be versions.  */
-  if (strcmp (target1, target2) == 0)
-    result = false;
-  else
-    result = true;
-
-  XDELETEVEC (target1);
-  XDELETEVEC (target2);
-
-  return result;
-}
-
 bool
 reject_target_clone_version (string_slice str ATTRIBUTE_UNUSED,
                             location_t loc ATTRIBUTE_UNUSED)
diff --git a/gcc/attribs.h b/gcc/attribs.h
index b8b6838599cc..fc343c0eab51 100644
--- a/gcc/attribs.h
+++ b/gcc/attribs.h
@@ -54,7 +54,8 @@ extern struct scoped_attributes *
   register_scoped_attributes (const scoped_attribute_specs &, bool = false);
 
 extern char *sorted_attr_string (tree);
-extern bool common_function_versions (tree, tree);
+extern bool common_function_versions (string_slice, string_slice);
+extern bool reject_target_clone_version (string_slice, location_t);
 extern tree make_dispatcher_decl (const tree);
 extern bool is_function_default_version (const tree);
 extern void handle_ignored_attributes_option (vec<char *> *);
diff --git a/gcc/config/aarch64/aarch64.cc b/gcc/config/aarch64/aarch64.cc
index a0420188bb8c..be8422ceb39f 100644
--- a/gcc/config/aarch64/aarch64.cc
+++ b/gcc/config/aarch64/aarch64.cc
@@ -20801,13 +20801,19 @@ aarch64_get_function_versions_dispatcher (void *decl)
    This assumes that FN1 and FN2 have the same signature.  */
 
 bool
-aarch64_common_function_versions (tree fn1, tree fn2)
+aarch64_common_function_versions (string_slice str1, string_slice str2)
 {
-  if (TREE_CODE (fn1) != FUNCTION_DECL
-      || TREE_CODE (fn2) != FUNCTION_DECL)
-    return false;
+  enum aarch_parse_opt_result parse_res;
+  aarch64_fmv_feature_mask feature_mask1;
+  aarch64_fmv_feature_mask feature_mask2;
+  parse_res = aarch64_parse_fmv_features (str1, NULL,
+                                         &feature_mask1, NULL);
+  gcc_assert (parse_res == AARCH_PARSE_OK);
+  parse_res = aarch64_parse_fmv_features (str2, NULL,
+                                         &feature_mask2, NULL);
+  gcc_assert (parse_res == AARCH_PARSE_OK);
 
-  return (aarch64_compare_version_priority (fn1, fn2) != 0);
+  return feature_mask1 == feature_mask2;
 }
 
 /* Implement TARGET_FUNCTION_ATTRIBUTE_INLINABLE_P.  Use an opt-out
diff --git a/gcc/config/riscv/riscv.cc b/gcc/config/riscv/riscv.cc
index b7588ec297ae..e49ef2afa624 100644
--- a/gcc/config/riscv/riscv.cc
+++ b/gcc/config/riscv/riscv.cc
@@ -13173,6 +13173,22 @@ compare_fmv_features (const struct riscv_feature_bits 
&mask1,
   return 0;
 }
 
+/* This function returns true if v1 and v2 specify the same function
+   version.  */
+bool
+riscv_common_function_versions (string_slice v1, string_slice v2)
+{
+  struct riscv_feature_bits mask1, mask2;
+  int prio1, prio2;
+
+  /* Invalid features should have already been rejected by this point so
+     providing no location should be okay.  */
+  parse_features_for_version (v1, UNKNOWN_LOCATION, mask1, prio1);
+  parse_features_for_version (v2, UNKNOWN_LOCATION, mask2, prio2);
+
+  return compare_fmv_features (mask1, mask2, prio1, prio2) == 0;
+}
+
 /* Compare priorities of two version decls.  Return:
      1: mask1 is higher priority
     -1: mask2 is higher priority
@@ -13197,20 +13213,6 @@ riscv_compare_version_priority (tree decl1, tree decl2)
   return compare_fmv_features (mask1, mask2, prio1, prio2);
 }
 
-/* This function returns true if FN1 and FN2 are versions of the same function,
-   that is, the target_version attributes of the function decls are different.
-   This assumes that FN1 and FN2 have the same signature.  */
-
-bool
-riscv_common_function_versions (tree fn1, tree fn2)
-{
-  if (TREE_CODE (fn1) != FUNCTION_DECL
-      || TREE_CODE (fn2) != FUNCTION_DECL)
-    return false;
-
-  return riscv_compare_version_priority (fn1, fn2) != 0;
-}
-
 bool
 riscv_reject_target_clone_version (string_slice str, location_t loc)
 {
diff --git a/gcc/cp/class.cc b/gcc/cp/class.cc
index c28d9e5b3ab4..4f195ae06cd0 100644
--- a/gcc/cp/class.cc
+++ b/gcc/cp/class.cc
@@ -37,6 +37,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "gimplify.h"
 #include "intl.h"
 #include "asan.h"
+#include "attribs.h"
 
 /* Id for dumping the class hierarchy.  */
 int class_dump_id;
@@ -8999,8 +9000,7 @@ resolve_address_of_overloaded_function (tree target_type,
         decls_match will return false as they are different.  */
       for (match = TREE_CHAIN (matches); match; match = TREE_CHAIN (match))
        if (!decls_match (fn, TREE_PURPOSE (match))
-           && !targetm.target_option.function_versions
-              (fn, TREE_PURPOSE (match)))
+           && !distinct_version_decls (fn, TREE_PURPOSE (match)))
           break;
 
       if (match)
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 5bfc08191f6d..6649e0f86f43 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -7121,7 +7121,7 @@ extern void note_iteration_stmt_body_end  (bool);
 extern void determine_local_discriminator      (tree, tree = NULL_TREE);
 extern bool member_like_constrained_friend_p   (tree);
 extern bool fns_correspond                     (tree, tree);
-extern int decls_match                         (tree, tree, bool = true);
+extern int decls_match                         (tree, tree, bool = true, 
string_slice* = NULL);
 extern bool maybe_version_functions            (tree, tree);
 extern bool validate_constexpr_redeclaration   (tree, tree);
 extern bool merge_default_template_args                (tree, tree, bool);
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index fb2b8e6acdf5..c53a8ac4567a 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -1120,7 +1120,10 @@ fns_correspond (tree newdecl, tree olddecl)
    `const int&'.  */
 
 int
-decls_match (tree newdecl, tree olddecl, bool record_versions /* = true */)
+decls_match (tree newdecl,
+            tree olddecl,
+            bool record_versions /* = true */,
+            string_slice *conflicting_version)
 {
   int types_match;
 
@@ -1213,7 +1216,7 @@ decls_match (tree newdecl, tree olddecl, bool 
record_versions /* = true */)
       if (types_match
          && !DECL_EXTERN_C_P (newdecl)
          && !DECL_EXTERN_C_P (olddecl)
-         && targetm.target_option.function_versions (newdecl, olddecl))
+         && distinct_version_decls (newdecl, olddecl, conflicting_version))
        {
          if (record_versions)
            maybe_version_functions (newdecl, olddecl);
@@ -1296,7 +1299,7 @@ maybe_mark_function_versioned (tree decl)
 bool
 maybe_version_functions (tree newdecl, tree olddecl)
 {
-  if (!targetm.target_option.function_versions (newdecl, olddecl))
+  if (!distinct_version_decls (newdecl, olddecl))
     return false;
 
   maybe_mark_function_versioned (olddecl);
@@ -1686,11 +1689,12 @@ duplicate_decls (tree newdecl, tree olddecl, bool 
hiding, bool was_hidden)
   tree new_template_info;
   location_t olddecl_loc = DECL_SOURCE_LOCATION (olddecl);
   location_t newdecl_loc = DECL_SOURCE_LOCATION (newdecl);
+  string_slice conflicting_version = string_slice::invalid ();
 
   if (newdecl == olddecl)
     return olddecl;
 
-  types_match = decls_match (newdecl, olddecl);
+  types_match = decls_match (newdecl, olddecl, true, &conflicting_version);
 
   /* If either the type of the new decl or the type of the old decl is an
      error_mark_node, then that implies that we have already issued an
@@ -2106,6 +2110,16 @@ duplicate_decls (tree newdecl, tree olddecl, bool 
hiding, bool was_hidden)
       /* Leave it to update_binding to merge or report error.  */
       return NULL_TREE;
     }
+  else if (!TARGET_HAS_FMV_TARGET_ATTRIBUTE
+          && !mergeable_version_decls (newdecl, olddecl))
+    {
+      /* newdecl defines an overlapping FMV version with olddecl but they
+        cannot be merged so are conflicting.  */
+      gcc_assert (conflicting_version.is_valid ());
+      error_at (newdecl_loc, "conflicting %qB versions", &conflicting_version);
+      inform (olddecl_loc, "previous definition");
+      return error_mark_node;
+    }
   else
     {
       const char *errmsg = redeclaration_error_message (newdecl, olddecl);
@@ -2114,10 +2128,23 @@ duplicate_decls (tree newdecl, tree olddecl, bool 
hiding, bool was_hidden)
          auto_diagnostic_group d;
          error_at (newdecl_loc, errmsg, newdecl);
          if (DECL_NAME (olddecl) != NULL_TREE)
-           inform (olddecl_loc,
-                   (DECL_INITIAL (olddecl) && namespace_bindings_p ())
-                   ? G_("%q#D previously defined here")
-                   : G_("%q#D previously declared here"), olddecl);
+           {
+             /* If conflicting_version is set then this collision is between
+                two FMV annotated functions.  */
+             if (conflicting_version.is_valid ())
+               inform (olddecl_loc,
+                       (DECL_INITIAL (olddecl) && namespace_bindings_p ())
+                       ? G_("%qB version of %q#D previously defined here")
+                       : G_("%qB version of %q#D previously declared here"),
+                       &conflicting_version,
+                       olddecl);
+             else
+               inform (olddecl_loc,
+                       (DECL_INITIAL (olddecl) && namespace_bindings_p ())
+                       ? G_("%q#D previously defined here")
+                       : G_("%q#D previously declared here"),
+                       olddecl);
+           }
          if (cxx_dialect >= cxx26
              && DECL_NAME (newdecl)
              && id_equal (DECL_NAME (newdecl), "_")
diff --git a/gcc/cp/decl2.cc b/gcc/cp/decl2.cc
index 21156f1dd3b7..299385cb6543 100644
--- a/gcc/cp/decl2.cc
+++ b/gcc/cp/decl2.cc
@@ -876,7 +876,7 @@ check_classfn (tree ctype, tree function, tree 
template_parms)
       if (same_type_p (TREE_TYPE (TREE_TYPE (function)),
                       TREE_TYPE (TREE_TYPE (fndecl)))
          && compparms (p1, p2)
-         && !targetm.target_option.function_versions (function, fndecl)
+         && !distinct_version_decls (function, fndecl)
          && (!is_template
              || comp_template_parms (template_parms,
                                      DECL_TEMPLATE_PARMS (fndecl)))
diff --git a/gcc/doc/tm.texi b/gcc/doc/tm.texi
index 2746c2505aa1..3d25213fb6b6 100644
--- a/gcc/doc/tm.texi
+++ b/gcc/doc/tm.texi
@@ -10960,8 +10960,8 @@ changed via the optimize attribute or pragma, see
 @code{TARGET_OVERRIDE_OPTIONS_AFTER_CHANGE}
 @end deftypefn
 
-@deftypefn {Target Hook} bool TARGET_OPTION_FUNCTION_VERSIONS (tree 
@var{decl1}, tree @var{decl2})
-This target hook returns @code{true} if @var{DECL1} and @var{DECL2} are
+@deftypefn {Target Hook} bool TARGET_OPTION_FUNCTION_VERSIONS (string_slice 
@var{fn1}, string_slice @var{fn2})
+This target hook returns @code{true} if @var{fn1} and @var{fn2} are
 versions of the same function.  @var{DECL1} and @var{DECL2} are function
 versions if and only if they have the same function signature and
 different target specific attributes, that is, they are compiled for
diff --git a/gcc/target.def b/gcc/target.def
index e01eb2a2413c..98bcf09fa764 100644
--- a/gcc/target.def
+++ b/gcc/target.def
@@ -6912,13 +6912,13 @@ changed via the optimize attribute or pragma, see\n\
    that is, they are compiled for different target machines.  */
 DEFHOOK
 (function_versions,
- "This target hook returns @code{true} if @var{DECL1} and @var{DECL2} are\n\
+ "This target hook returns @code{true} if @var{fn1} and @var{fn2} are\n\
 versions of the same function.  @var{DECL1} and @var{DECL2} are function\n\
 versions if and only if they have the same function signature and\n\
 different target specific attributes, that is, they are compiled for\n\
 different target machines.",
- bool, (tree decl1, tree decl2),
- hook_bool_tree_tree_false)
+ bool, (string_slice fn1, string_slice fn2),
+ NULL)
 
 /* Function to determine if one function can inline another function.  */
 #undef HOOK_PREFIX
diff --git a/gcc/tree.cc b/gcc/tree.cc
index 932e161da148..0095d70c375d 100644
--- a/gcc/tree.cc
+++ b/gcc/tree.cc
@@ -15411,6 +15411,210 @@ get_target_version (const tree decl)
           .strip ();
 }
 
+bool
+distinct_version_decls (tree fn1, tree fn2, string_slice *conflicting_version)
+{
+  if (TREE_CODE (fn1) != FUNCTION_DECL
+      || TREE_CODE (fn2) != FUNCTION_DECL)
+    return false;
+
+  if (TARGET_HAS_FMV_TARGET_ATTRIBUTE)
+    {
+      tree attr1 = lookup_attribute ("target", DECL_ATTRIBUTES (fn1));
+      tree attr2 = lookup_attribute ("target", DECL_ATTRIBUTES (fn2));
+
+      /* At least one function decl should have the target attribute
+        specified.  */
+      if (attr1 == NULL_TREE && attr2 == NULL_TREE)
+       return false;
+
+      /* Diagnose missing target attribute if one of the decls is already
+        multi-versioned.  */
+      if (attr1 == NULL_TREE || attr2 == NULL_TREE)
+       {
+         if (DECL_FUNCTION_VERSIONED (fn1) || DECL_FUNCTION_VERSIONED (fn2))
+           {
+             if (attr2 != NULL_TREE)
+               {
+                 std::swap (fn1, fn2);
+                 attr1 = attr2;
+               }
+             auto_diagnostic_group d;
+             error_at (DECL_SOURCE_LOCATION (fn2),
+                       "missing %<target%> attribute for multi-versioned %qD",
+                       fn2);
+             inform (DECL_SOURCE_LOCATION (fn1),
+                     "previous declaration of %qD", fn1);
+             /* Prevent diagnosing of the same error multiple times.  */
+             DECL_ATTRIBUTES (fn2)
+               = tree_cons (get_identifier ("target"),
+                            copy_node (TREE_VALUE (attr1)),
+                            DECL_ATTRIBUTES (fn2));
+           }
+         return false;
+       }
+
+      char *target1 = sorted_attr_string (TREE_VALUE (attr1));
+      char *target2 = sorted_attr_string (TREE_VALUE (attr2));
+
+      /* The sorted target strings must be different for fn1 and fn2
+        to be versions.  */
+      bool result = strcmp (target1, target2) != 0;
+
+      XDELETEVEC (target1);
+      XDELETEVEC (target2);
+
+      return result;
+    }
+  else
+    {
+      /* As this is symmetric, can remove the case where fn2 is target clone
+        and fn1 is target version by swapping here.  */
+      if (lookup_attribute ("target_clones", DECL_ATTRIBUTES (fn2)))
+       std::swap (fn1, fn2);
+
+      if (lookup_attribute ("target_clones", DECL_ATTRIBUTES (fn1)))
+       {
+         auto_vec<string_slice> fn1_versions = get_clone_versions (fn1);
+         /* fn1 is target_clone.  */
+         if (lookup_attribute ("target_clones", DECL_ATTRIBUTES (fn2)))
+           {
+             /* Both are target_clone.  */
+             auto_vec<string_slice> fn2_versions = get_clone_versions (fn2);
+             for (string_slice v1 : fn1_versions)
+               {
+                 for (string_slice v2 : fn2_versions)
+                   if (targetm.target_option.function_versions (v1, v2))
+                     {
+                       if (conflicting_version)
+                         *conflicting_version= v1;
+                       return false;
+                     }
+               }
+             return true;
+           }
+         else
+           {
+             string_slice v2 = get_target_version (fn2);
+
+             /* target and target_clones is always conflicting for target
+                semantics.  */
+             if (TARGET_HAS_FMV_TARGET_ATTRIBUTE)
+               return false;
+
+             /* Only fn1 is target clone.  */
+             if (!v2.is_valid ())
+               v2 = "default";
+             for (string_slice v1 : fn1_versions)
+               if (targetm.target_option.function_versions (v1, v2))
+                 {
+                   if (conflicting_version)
+                     *conflicting_version= v1;
+                   return false;
+                 }
+             return true;
+           }
+       }
+      else
+       {
+         /* Both are target_version.  */
+         string_slice v1 = get_target_version (fn1);
+         string_slice v2 = get_target_version (fn2);
+
+         if (!v1.is_valid () && !v2.is_valid ())
+           return false;
+
+         if (!v1.is_valid ())
+           v1 = "default";
+         if (!v2.is_valid ())
+           v2 = "default";
+
+         if (targetm.target_option.function_versions (v1, v2))
+           {
+             if (conflicting_version)
+               *conflicting_version = v1;
+             return false;
+           }
+
+         return true;
+       }
+    }
+}
+
+/* check if the target_version/target_clones attributes are mergeable
+   for two decls.  */
+bool
+mergeable_version_decls (tree fn1, tree fn2)
+{
+  gcc_assert (!TARGET_HAS_FMV_TARGET_ATTRIBUTE);
+
+  string_slice fn1_target_attr = get_target_version (fn1);
+  string_slice fn2_target_attr = get_target_version (fn2);
+
+  tree fn1_target_clones_attr = lookup_attribute ("target_clones",
+                                                 DECL_ATTRIBUTES (fn1));
+  tree fn2_target_clones_attr = lookup_attribute ("target_clones",
+                                                 DECL_ATTRIBUTES (fn2));
+
+  /* If none of these are annotated, then it is mergeable.  */
+  if (!fn1_target_attr.is_valid ()
+      && !fn1_target_attr.is_valid ()
+      && !fn1_target_clones_attr
+      && !fn2_target_clones_attr)
+    return true;
+
+  /* If fn1 is unnanotated and fn2 contains default, then is mergeable.  */
+  if (!fn1_target_attr.is_valid ()
+      && !fn1_target_clones_attr
+      && is_function_default_version (fn2))
+    return true;
+
+  if (fn1_target_clones_attr && fn2_target_clones_attr)
+    {
+      auto_vec<string_slice> fn1_versions = get_clone_versions (fn1);
+      auto_vec<string_slice> fn2_versions = get_clone_versions (fn2);
+
+      if (fn1_versions.length () != fn2_versions.length ())
+       return false;
+
+      /* Check both inclusion directions.  */
+      for (auto fn1v : fn1_versions)
+       {
+         bool matched = false;
+         for (auto fn2v : fn2_versions)
+           if (targetm.target_option.function_versions (fn1v, fn2v))
+             matched = true;
+         if (!matched)
+           return false;
+       }
+
+      for (auto fn2v : fn2_versions)
+       {
+         bool matched = false;
+         for (auto fn1v : fn1_versions)
+           if (targetm.target_option.function_versions (fn1v, fn2v))
+             matched = true;
+         if (!matched)
+           return false;
+       }
+
+      return true;
+    }
+
+  /* If olddecl is target clones but not newdecl, never mergeable.  */
+  if (fn1_target_clones_attr || fn2_target_clones_attr)
+    return false;
+
+  if (!fn1_target_attr.is_valid ())
+    fn1_target_attr = "default";
+  if (!fn2_target_attr.is_valid ())
+    fn2_target_attr = "default";
+
+  /* Mergeable if define the same version.  */
+  return targetm.target_option.function_versions (fn1_target_attr,
+                                                 fn2_target_attr);
+}
+
 void
 tree_cc_finalize (void)
 {
diff --git a/gcc/tree.h b/gcc/tree.h
index 796e858cf26a..f76301ab260f 100644
--- a/gcc/tree.h
+++ b/gcc/tree.h
@@ -7054,4 +7054,10 @@ extern auto_vec<string_slice> get_clone_attr_versions 
(const tree, int *,
                                                       location_t loc,
                                                       bool = true);
 
+/* Checks if two decls define any overlapping versions.  If they do updates
+   the string slice with the overlapping version.  */
+extern bool distinct_version_decls (tree, tree, string_slice * = NULL);
+/* Checks if two overlapping decls are mergeable..  */
+extern bool mergeable_version_decls (tree, tree);
+
 #endif  /* GCC_TREE_H  */

Reply via email to