diff --git a/gcc/attribs.cc b/gcc/attribs.cc
index c75fd1371fd..9efc327553f 100644
--- a/gcc/attribs.cc
+++ b/gcc/attribs.cc
@@ -1086,8 +1086,6 @@ make_attribute (string_slice name, string_slice arg_name, tree chain)
   return attr;
 }
 
-/* Common functions used for target clone support.  */
-
 /* Comparator function to be used in qsort routine to sort attribute
    specification strings to "target".  */
 
@@ -1177,71 +1175,6 @@ sorted_attr_string (tree arglist)
   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;
-}
-
 /* 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.  Return the decl created.  */
diff --git a/gcc/attribs.h b/gcc/attribs.h
index b8b6838599c..c4a4fb0e50b 100644
--- a/gcc/attribs.h
+++ b/gcc/attribs.h
@@ -54,7 +54,6 @@ 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 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 4a42735acc4..20ae37bdf7d 100644
--- a/gcc/config/aarch64/aarch64.cc
+++ b/gcc/config/aarch64/aarch64.cc
@@ -21160,18 +21160,23 @@ aarch64_get_function_versions_dispatcher (void *decl)
   return dispatch_decl;
 }
 
-/* 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.  */
+/* This function returns true if STR1 and STR2 are version strings for the same
+   function.  */
 
 bool
-aarch64_common_function_versions (tree fn1, tree fn2)
+aarch64_same_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
@@ -32836,8 +32841,8 @@ aarch64_libgcc_floating_mode_supported_p
 #undef TARGET_EMIT_EPILOGUE_FOR_SIBCALL
 #define TARGET_EMIT_EPILOGUE_FOR_SIBCALL aarch64_expand_epilogue
 
-#undef TARGET_OPTION_FUNCTION_VERSIONS
-#define TARGET_OPTION_FUNCTION_VERSIONS aarch64_common_function_versions
+#undef TARGET_OPTION_SAME_FUNCTION_VERSIONS
+#define TARGET_OPTION_SAME_FUNCTION_VERSIONS aarch64_same_function_versions
 
 #undef TARGET_CHECK_TARGET_CLONE_VERSION
 #define TARGET_CHECK_TARGET_CLONE_VERSION aarch64_check_target_clone_version
diff --git a/gcc/config/i386/i386.cc b/gcc/config/i386/i386.cc
index 5ef7c315091..b6c6e01d01d 100644
--- a/gcc/config/i386/i386.cc
+++ b/gcc/config/i386/i386.cc
@@ -28244,9 +28244,6 @@ ix86_libgcc_floating_mode_supported_p
 #undef TARGET_OPTION_PRINT
 #define TARGET_OPTION_PRINT ix86_function_specific_print
 
-#undef TARGET_OPTION_FUNCTION_VERSIONS
-#define TARGET_OPTION_FUNCTION_VERSIONS common_function_versions
-
 #undef TARGET_CAN_INLINE_P
 #define TARGET_CAN_INLINE_P ix86_can_inline_p
 
diff --git a/gcc/config/riscv/riscv.cc b/gcc/config/riscv/riscv.cc
index 182cefbc374..4a6a1161833 100644
--- a/gcc/config/riscv/riscv.cc
+++ b/gcc/config/riscv/riscv.cc
@@ -14080,6 +14080,23 @@ 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_same_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
@@ -14104,20 +14121,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;
-}
-
 /* Checks if the function version specifying string STR parses correctly.
    If it is an invalid string, currently emits a diagnostic at LOC.
    Always returns true.  */
@@ -15976,8 +15979,8 @@ riscv_prefetch_offset_address_p (rtx x, machine_mode mode)
 #undef TARGET_CHECK_TARGET_CLONE_VERSION
 #define TARGET_CHECK_TARGET_CLONE_VERSION riscv_check_target_clone_version
 
-#undef TARGET_OPTION_FUNCTION_VERSIONS
-#define TARGET_OPTION_FUNCTION_VERSIONS riscv_common_function_versions
+#undef TARGET_OPTION_SAME_FUNCTION_VERSIONS
+#define TARGET_OPTION_SAME_FUNCTION_VERSIONS riscv_same_function_versions
 
 #undef TARGET_MANGLE_DECL_ASSEMBLER_NAME
 #define TARGET_MANGLE_DECL_ASSEMBLER_NAME riscv_mangle_decl_assembler_name
diff --git a/gcc/config/rs6000/rs6000.cc b/gcc/config/rs6000/rs6000.cc
index d00d5f27121..1049c446c40 100644
--- a/gcc/config/rs6000/rs6000.cc
+++ b/gcc/config/rs6000/rs6000.cc
@@ -1730,9 +1730,6 @@ static const scoped_attribute_specs *const rs6000_attribute_table[] =
 #define TARGET_GET_FUNCTION_VERSIONS_DISPATCHER				\
   rs6000_get_function_versions_dispatcher
 
-#undef TARGET_OPTION_FUNCTION_VERSIONS
-#define TARGET_OPTION_FUNCTION_VERSIONS common_function_versions
-
 #undef TARGET_HARD_REGNO_NREGS
 #define TARGET_HARD_REGNO_NREGS rs6000_hard_regno_nregs_hook
 #undef TARGET_HARD_REGNO_MODE_OK
diff --git a/gcc/cp/class.cc b/gcc/cp/class.cc
index df8d3c9321e..b56cc518a11 100644
--- a/gcc/cp/class.cc
+++ b/gcc/cp/class.cc
@@ -9127,8 +9127,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)))
+	    && !disjoint_version_decls (fn, TREE_PURPOSE (match)))
           break;
 
       if (match)
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index f75e057c2ef..7927b4375ec 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -1212,7 +1212,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))
+	  && disjoint_version_decls (newdecl, olddecl))
 	{
 	  if (record_versions)
 	    maybe_version_functions (newdecl, olddecl);
@@ -1295,7 +1295,7 @@ maybe_mark_function_versioned (tree decl)
 bool
 maybe_version_functions (tree newdecl, tree olddecl)
 {
-  if (!targetm.target_option.function_versions (newdecl, olddecl))
+  if (!disjoint_version_decls (newdecl, olddecl))
     return false;
 
   maybe_mark_function_versioned (olddecl);
@@ -2107,6 +2107,10 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden)
       /* Leave it to update_binding to merge or report error.  */
       return NULL_TREE;
     }
+  /* Check if the two decls are non-mergeable versioned decls.  */
+  else if (!TARGET_HAS_FMV_TARGET_ATTRIBUTE
+	   && diagnose_versioned_decls (olddecl, newdecl))
+    return error_mark_node;
   else
     {
       const char *errmsg = redeclaration_error_message (newdecl, olddecl);
diff --git a/gcc/cp/decl2.cc b/gcc/cp/decl2.cc
index f87d7d68547..dfaef30dbfc 100644
--- a/gcc/cp/decl2.cc
+++ b/gcc/cp/decl2.cc
@@ -877,7 +877,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)
+	  && !disjoint_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 d2b1e712628..218bf3abbde 100644
--- a/gcc/doc/tm.texi
+++ b/gcc/doc/tm.texi
@@ -10978,12 +10978,9 @@ 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
-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
-different target machines.
+@deftypefn {Target Hook} bool TARGET_OPTION_SAME_FUNCTION_VERSIONS (string_slice @var{fn1}, string_slice @var{fn2})
+This target hook returns @code{true} if the target/target-version strings
+@var{fn1} and @var{fn2} imply the same function version.
 @end deftypefn
 
 @deftypefn {Target Hook} bool TARGET_CAN_INLINE_P (tree @var{caller}, tree @var{callee})
diff --git a/gcc/doc/tm.texi.in b/gcc/doc/tm.texi.in
index 30a9400f0f9..57653a133a6 100644
--- a/gcc/doc/tm.texi.in
+++ b/gcc/doc/tm.texi.in
@@ -7140,7 +7140,7 @@ with the target specific attributes.  The default value is @code{','}.
 
 @hook TARGET_OPTION_OVERRIDE
 
-@hook TARGET_OPTION_FUNCTION_VERSIONS
+@hook TARGET_OPTION_SAME_FUNCTION_VERSIONS
 
 @hook TARGET_CAN_INLINE_P
 
diff --git a/gcc/hooks.cc b/gcc/hooks.cc
index 865820d80b1..820c81e31e4 100644
--- a/gcc/hooks.cc
+++ b/gcc/hooks.cc
@@ -27,6 +27,7 @@
 #include "coretypes.h"
 #include "tm.h"
 #include "hooks.h"
+#include "vec.h"
 
 /* Generic hook that does absolutely zappo.  */
 void
@@ -593,3 +594,9 @@ hook_stringslice_locationtptr_true (string_slice, location_t *)
 {
   return true;
 }
+
+bool
+hook_stringslice_stringslice_unreachable (string_slice, string_slice)
+{
+  gcc_unreachable ();
+}
diff --git a/gcc/hooks.h b/gcc/hooks.h
index 08542d7a24e..a7021f532a5 100644
--- a/gcc/hooks.h
+++ b/gcc/hooks.h
@@ -139,5 +139,6 @@ extern opt_machine_mode hook_optmode_mode_uhwi_none (machine_mode,
 						     unsigned HOST_WIDE_INT);
 
 extern bool hook_stringslice_locationtptr_true (string_slice, location_t *);
+extern bool hook_stringslice_stringslice_unreachable (string_slice, string_slice);
 
 #endif
diff --git a/gcc/target.def b/gcc/target.def
index 02172c52d20..b6f95f3ac7a 100644
--- a/gcc/target.def
+++ b/gcc/target.def
@@ -6925,19 +6925,14 @@ changed via the optimize attribute or pragma, see\n\
  void, (void),
  hook_void_void)
 
-/* This function returns true if DECL1 and DECL2 are versions of the same
-   function.  DECL1 and 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 different target machines.  */
-DEFHOOK
-(function_versions,
- "This target hook returns @code{true} if @var{DECL1} and @var{DECL2} 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)
+/* This function returns true if FN1 and FN2 define the same version of a
+   function.  */
+DEFHOOK
+(same_function_versions,
+ "This target hook returns @code{true} if the target/target-version strings\n\
+@var{fn1} and @var{fn2} imply the same function version.",
+ bool, (string_slice fn1, string_slice fn2),
+ hook_stringslice_stringslice_unreachable)
 
 /* Function to determine if one function can inline another function.  */
 #undef HOOK_PREFIX
diff --git a/gcc/tree.cc b/gcc/tree.cc
index 2e114d01110..cad57fedc7a 100644
--- a/gcc/tree.cc
+++ b/gcc/tree.cc
@@ -15490,6 +15490,249 @@ get_target_version (const tree decl)
 	   .strip ();
 }
 
+/* Returns true if FN1 and FN2 define dijoint function versions in an FMV
+   function set.  That is, the two declarations are completely non-overlapping.
+   For target_version semantics, that means if one is a target clone and one is
+   a target version, the target_version must not be defined by the target_clone,
+   and for two target_clones, they must not define any of the same version.
+
+   FN1 and FN2 should be function decls.  */
+
+bool
+disjoint_version_decls (tree fn1, tree fn2)
+{
+  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.same_function_versions (v1, v2))
+		      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.same_function_versions (v1, v2))
+		  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.same_function_versions (v1, v2))
+	    return false;
+
+	  return true;
+	}
+    }
+}
+
+/* Check if the target_version/target_clones attributes are mergeable
+   for two decls, and if so returns false.
+   If they aren't mergeable, diagnose this and return true.
+   Only works for target_version semantics.  */
+bool
+diagnose_versioned_decls (tree old_decl, tree new_decl)
+{
+  gcc_assert (!TARGET_HAS_FMV_TARGET_ATTRIBUTE);
+
+  string_slice old_target_attr = get_target_version (old_decl);
+  string_slice new_target_attr = get_target_version (new_decl);
+
+  tree old_target_clones_attr = lookup_attribute ("target_clones",
+						  DECL_ATTRIBUTES (old_decl));
+  tree new_target_clones_attr = lookup_attribute ("target_clones",
+						  DECL_ATTRIBUTES (new_decl));
+
+  /* If none of these are annotated, then it is mergeable.  */
+  if (!old_target_attr.is_valid ()
+      && !old_target_attr.is_valid ()
+      && !old_target_clones_attr
+      && !new_target_clones_attr)
+    return false;
+
+  /* If fn1 is unnanotated and fn2 contains default, then is mergeable.  */
+  if (!old_target_attr.is_valid ()
+      && !old_target_clones_attr
+      && is_function_default_version (new_decl))
+    return false;
+
+  /* If fn2 is unnanotated and fn1 contains default, then is mergeable.  */
+  if (!new_target_attr.is_valid ()
+      && !new_target_clones_attr
+      && is_function_default_version (old_decl))
+    return false;
+
+  /* In the case where both are annotated with target_clones, only mergeable if
+     the two sets of target_clones imply the same set of versions.  */
+  if (old_target_clones_attr && new_target_clones_attr)
+    {
+      auto_vec<string_slice> fn1_versions = get_clone_versions (old_decl);
+      auto_vec<string_slice> fn2_versions = get_clone_versions (new_decl);
+
+      bool mergeable = true;
+
+      if (fn1_versions.length () != fn2_versions.length ())
+	mergeable = false;
+
+      /* Check both inclusion directions.  */
+      for (auto fn1v : fn1_versions)
+	{
+	  bool matched = false;
+	  for (auto fn2v : fn2_versions)
+	    if (targetm.target_option.same_function_versions (fn1v, fn2v))
+	      matched = true;
+	  if (!matched)
+	    mergeable = false;
+	}
+
+      for (auto fn2v : fn2_versions)
+	{
+	  bool matched = false;
+	  for (auto fn1v : fn1_versions)
+	    if (targetm.target_option.same_function_versions (fn1v, fn2v))
+	      matched = true;
+	  if (!matched)
+	    mergeable = false;
+	}
+
+      if (!mergeable)
+	{
+	  error_at (DECL_SOURCE_LOCATION (new_decl),
+		    "%qD conflicts with overlapping %<target_clone%> "
+		    "declaration",
+		    new_decl);
+	  inform (DECL_SOURCE_LOCATION (old_decl),
+		  "previous declaration of %qD", old_decl);
+	  return true;
+	}
+
+      return false;
+    }
+
+  /* If olddecl is target clones and newdecl is a target_version.
+     As they are not distinct this implies newdecl redefines a version of
+     olddecl.  Not mergeable.  */
+  if (new_target_clones_attr)
+    {
+      gcc_assert (old_target_attr.is_valid ());
+
+      error_at (DECL_SOURCE_LOCATION (new_decl),
+		"%qD conflicts for version %qB",
+		new_decl, &old_target_attr);
+      inform (DECL_SOURCE_LOCATION (old_decl),
+	      "previous declaration of %qD",
+	      old_decl);
+      return true;
+    }
+
+  if (old_target_clones_attr)
+    {
+      gcc_assert (new_target_attr.is_valid ());
+
+      error_at (DECL_SOURCE_LOCATION (new_decl),
+		"%qD conflicts with a previous declaration for version %qB",
+		new_decl, &new_target_attr);
+      inform (DECL_SOURCE_LOCATION (old_decl),
+	      "previous declaration of %qD",
+	      old_decl);
+      return true;
+    }
+
+  /* The only remaining case is two target_version annotated decls.  Must
+     be mergeable as otherwise are distinct.  */
+  return false;
+}
+
 void
 tree_cc_finalize (void)
 {
diff --git a/gcc/tree.h b/gcc/tree.h
index b22dce5214a..b1d14341666 100644
--- a/gcc/tree.h
+++ b/gcc/tree.h
@@ -7116,4 +7116,9 @@ extern auto_vec<string_slice> get_clone_versions
 extern auto_vec<string_slice> get_clone_attr_versions
   (const tree, int *, bool = true);
 
+/* Checks if two decls define any overlapping versions.  */
+extern bool disjoint_version_decls (tree, tree);
+/* Checks if two overlapping decls are not mergeable.  */
+extern bool diagnose_versioned_decls (tree, tree);
+
 #endif  /* GCC_TREE_H  */
