> On 27 Mar 2025, at 16:45, Alfie Richards <alfie.richa...@arm.com> wrote:
>
>
> 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. */
These comments are intended for the function
riscv_compare_version_priority. Please rearrange them accordingly.
Thanks,
Yangyu Chen
> +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 */