From: Alfie Richards <alfie.richa...@arm.com>

This change refactors FMV handling in the frontend to allows greater
reasoning about versions in shared code.

This is needed for allowing target_clones and target_versions to be used
together in a function set, as there is then two distinct concerns when
encountering two declarations that previously were conflated:

1. Are these two declarations completely disjoint FMV declarations
(ie. the sets of versions they define have no overlap). If so, they don't
conflict so there is no need to merge and both can be pushed.
2. For two declarations that aren't completely dijoint, are they 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, continue to the existing merging logic
to try to merge these and diagnose if it's not possible.
If not, then diagnose the confliciting declarations.

To do this the common_function_versions function has been renamed
disjoint_function_versions (meaning, are the version sets defined by these
two decl's completely distinct from eachother).

A new hook called same_function_version is introduces taking two
string_slice's (each representing a single version) and determining if they
define the same version.

A new function, called diagnose_versioned_decls is added, which checks
if two decls (with overlapping version sets) can be merged and diagnose when
they cannot be (only in terms of the attributes, the existing logic is used to
detect other mergability conflicts like redefinition).

This only effects targets with TARGET_HAS_FMV_TARGET_ATTRIBUTE set to false.
(ie. aarch64 and riscv), the existing logic for i86 and ppc is unchanged.
This also means the same function version hook is only used for aarch64 and
riscv.

gcc/ChangeLog:

        * attribs.h (common_function_versions): Removed.
        * attribs.cc (common_function_versions): Removed.
        * config/aarch64/aarch64.cc (aarch64_common_function_versions): Removed.
        (aarch64_same_function_versions): New function to check if two version
        strings imply the same version.
        (TARGET_OPTION_FUNCTION_VERSIONS): Removed.
        (TARGET_OPTION_SAME_FUNCTION_VERSIONS): New macro.
        * config/i386/i386.cc (TARGET_OPTION_FUNCTION_VERSIONS): Removed.
        * config/rs6000/rs6000.cc (TARGET_OPTION_FUNCTION_VERSIONS): Removed.
        * config/riscv/riscv.cc (riscv_same_function_versions):  New function
        to check if two version strings imply the same version.
        (riscv_common_function_versions): Removed.
        (TARGET_OPTION_FUNCTION_VERSIONS): Removed.
        (TARGET_OPTION_SAME_FUNCTION_VERSIONS): New macro.
        * doc/tm.texi: Regenerated.
        * target.def: Remove common_version hook and add same_function_version
        hook.
        * doc/tm.texi.in: Ditto.
        * tree.cc (distinct_version_decls): New function.
        (mergeable_version_decls): Ditto.
        * tree.h (distinct_version_decls): New function.
        (mergeable_version_decls): Ditto.
        * hooks.h (hook_stringslice_stringslice_unreachable): New function.
        * hooks.cc (hook_stringslice_stringslice_unreachable): New function.

gcc/cp/ChangeLog:

        * class.cc (resolve_address_of_overloaded_function): Updated to use
        dijoint_versions_decls instead of common_function_version hook.
        * decl.cc (decls_match): Refacture to use disjoint_version_decls and
        to pass through conflicting_version argument.
        (maybe_version_functions): Updated to use
        disjoint_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
        disjoint_version_decls instead of common_function_version hook.
---
 gcc/attribs.cc                |  67 ----------
 gcc/attribs.h                 |   1 -
 gcc/config/aarch64/aarch64.cc |  25 ++--
 gcc/config/i386/i386.cc       |   3 -
 gcc/config/riscv/riscv.cc     |  35 ++---
 gcc/config/rs6000/rs6000.cc   |   3 -
 gcc/cp/class.cc               |   3 +-
 gcc/cp/decl.cc                |   8 +-
 gcc/cp/decl2.cc               |   2 +-
 gcc/doc/tm.texi               |   9 +-
 gcc/doc/tm.texi.in            |   2 +-
 gcc/hooks.cc                  |   7 +
 gcc/hooks.h                   |   1 +
 gcc/target.def                |  21 ++-
 gcc/tree.cc                   | 243 ++++++++++++++++++++++++++++++++++
 gcc/tree.h                    |   5 +
 16 files changed, 310 insertions(+), 125 deletions(-)

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 766da917732..d89c9df3df2 100644
--- a/gcc/config/aarch64/aarch64.cc
+++ b/gcc/config/aarch64/aarch64.cc
@@ -21161,18 +21161,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
@@ -32837,8 +32842,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 1ca6c612137..273ad312a54 100644
--- a/gcc/config/i386/i386.cc
+++ b/gcc/config/i386/i386.cc
@@ -28163,9 +28163,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 e01aa37f947..e45dc0e68f5 100644
--- a/gcc/config/riscv/riscv.cc
+++ b/gcc/config/riscv/riscv.cc
@@ -14044,6 +14044,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
@@ -14068,20 +14085,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.  */
@@ -15854,8 +15857,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 5dd5d767c19..616498e23ea 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -1211,7 +1211,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);
@@ -1294,7 +1294,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);
@@ -2106,6 +2106,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 c6c9dfc7723..7e5225cf03e 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)
+         && !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 4650e805846..65e2ce8ad42 100644
--- a/gcc/doc/tm.texi
+++ b/gcc/doc/tm.texi
@@ -10977,12 +10977,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 adb8edc7353..1b95525a902 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 05fd3d097e0..e3fc7f4fe58 100644
--- a/gcc/tree.cc
+++ b/gcc/tree.cc
@@ -15486,6 +15486,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 annoteted 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  */
-- 
2.34.1


Reply via email to