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.
---
 gcc/attribs.cc                |  75 ++-----------
 gcc/attribs.h                 |   3 +-
 gcc/config/aarch64/aarch64.cc |  16 ++-
 gcc/config/riscv/riscv.cc     |  32 +++---
 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, 293 insertions(+), 104 deletions(-)

diff --git a/gcc/attribs.cc b/gcc/attribs.cc
index 80833388ff2..13ddee3376b 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 b8b6838599c..fc343c0eab5 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 1b9d91d268a..d542941b2d9 100644
--- a/gcc/config/aarch64/aarch64.cc
+++ b/gcc/config/aarch64/aarch64.cc
@@ -20792,13 +20792,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 b7588ec297a..6bc1200f258 100644
--- a/gcc/config/riscv/riscv.cc
+++ b/gcc/config/riscv/riscv.cc
@@ -13177,6 +13177,24 @@ compare_fmv_features (const struct riscv_feature_bits &mask1,
      1: mask1 is higher priority
     -1: mask2 is higher priority
      0: masks are equal.  */
+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;
+}
+
+/* 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.  */
+
 int
 riscv_compare_version_priority (tree decl1, tree decl2)
 {
@@ -13197,20 +13215,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 ea19604ede2..b35fc34a291 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;
@@ -8998,8 +8999,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 51fda134403..9741313af45 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -7120,7 +7120,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 b1b0cb4f89e..83f24b1ddb2 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 21156f1dd3b..299385cb654 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 2746c2505aa..3d25213fb6b 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 e01eb2a2413..98bcf09fa76 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 932e161da14..0095d70c375 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 796e858cf26..f76301ab260 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