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 distinct 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 distinct, 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 distinct_function_versions (meaning, are the version sets defined by these two decl's completely distinct from eachother). The common function version hook was modified to instead take two string_slice's (each representing a single version) and determine if they define the same version. A new function, called mergeable_version_decls is added, which checks if two decls (with overlapping version sets) can be merged (only in terms of the attributes, the existing logic is used to detect other mergability conflicts like redefinition). This change also records the conflicting version string so that it can be included in diagnostics. 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 common version hook is only used for aarch64 and riscv. gcc/ChangeLog: * attribs.cc (common_function_versions): Change to an error, existing logic moved to distinct_version_decls. * attribs.h (common_function_versions): Change arguments. * 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. * 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 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 | 74 ++---------- gcc/attribs.h | 3 +- gcc/config/aarch64/aarch64.cc | 16 ++- gcc/config/riscv/riscv.cc | 30 ++--- gcc/cp/class.cc | 4 +- gcc/cp/cp-tree.h | 2 +- gcc/cp/decl.cc | 43 +++++-- gcc/cp/decl2.cc | 2 +- gcc/doc/tm.texi | 11 +- gcc/hooks.cc | 7 ++ gcc/hooks.h | 1 + gcc/target.def | 13 +-- gcc/tree.cc | 204 ++++++++++++++++++++++++++++++++++ gcc/tree.h | 6 + 14 files changed, 305 insertions(+), 111 deletions(-) diff --git a/gcc/attribs.cc b/gcc/attribs.cc index c75fd1371fd..06785eaa136 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". */ @@ -1177,71 +1184,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..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 83b8fc719f9..60bcf9aa49a 100644 --- a/gcc/config/aarch64/aarch64.cc +++ b/gcc/config/aarch64/aarch64.cc @@ -20784,13 +20784,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 a6281d233ac..6ce9f164828 100644 --- a/gcc/config/riscv/riscv.cc +++ b/gcc/config/riscv/riscv.cc @@ -13173,6 +13173,22 @@ compare_fmv_features (const struct riscv_feature_bits &mask1, return 0; } +/* This function returns true if v1 and v2 specify the same function + version. */ +bool +riscv_common_function_versions (string_slice v1, string_slice v2) +{ + struct riscv_feature_bits mask1, mask2; + int prio1, prio2; + + /* Invalid features should have already been rejected by this point so + providing no location should be okay. */ + parse_features_for_version (v1, UNKNOWN_LOCATION, mask1, prio1); + parse_features_for_version (v2, UNKNOWN_LOCATION, mask2, prio2); + + return compare_fmv_features (mask1, mask2, prio1, prio2) == 0; +} + /* Compare priorities of two version decls. Return: 1: mask1 is higher priority -1: mask2 is higher priority @@ -13197,20 +13213,6 @@ riscv_compare_version_priority (tree decl1, tree decl2) return compare_fmv_features (mask1, mask2, prio1, prio2); } -/* This function returns true if FN1 and FN2 are versions of the same function, - that is, the target_version attributes of the function decls are different. - This assumes that FN1 and FN2 have the same signature. */ - -bool -riscv_common_function_versions (tree fn1, tree fn2) -{ - if (TREE_CODE (fn1) != FUNCTION_DECL - || TREE_CODE (fn2) != FUNCTION_DECL) - return false; - - return riscv_compare_version_priority (fn1, fn2) != 0; -} - bool riscv_reject_target_clone_version (string_slice str, location_t loc) { diff --git a/gcc/cp/class.cc b/gcc/cp/class.cc index c28d9e5b3ab..4f195ae06cd 100644 --- a/gcc/cp/class.cc +++ b/gcc/cp/class.cc @@ -37,6 +37,7 @@ along with GCC; see the file COPYING3. If not see #include "gimplify.h" #include "intl.h" #include "asan.h" +#include "attribs.h" /* Id for dumping the class hierarchy. */ int class_dump_id; @@ -8999,8 +9000,7 @@ resolve_address_of_overloaded_function (tree target_type, decls_match will return false as they are different. */ for (match = TREE_CHAIN (matches); match; match = TREE_CHAIN (match)) if (!decls_match (fn, TREE_PURPOSE (match)) - && !targetm.target_option.function_versions - (fn, TREE_PURPOSE (match))) + && !distinct_version_decls (fn, TREE_PURPOSE (match))) break; if (match) diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index 898054c2891..ccd9547c2c8 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -7121,7 +7121,7 @@ extern void note_iteration_stmt_body_end (bool); extern void determine_local_discriminator (tree, tree = NULL_TREE); extern bool member_like_constrained_friend_p (tree); extern bool fns_correspond (tree, tree); -extern int decls_match (tree, tree, bool = true); +extern int decls_match (tree, tree, bool = true, string_slice* = NULL); extern bool maybe_version_functions (tree, tree); extern bool validate_constexpr_redeclaration (tree, tree); extern bool merge_default_template_args (tree, tree, bool); diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc index ec4b1755741..4a374fa29e3 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 1f0fc92858e..a6e9b2fcc0f 100644 --- a/gcc/doc/tm.texi +++ b/gcc/doc/tm.texi @@ -10960,12 +10960,11 @@ 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_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{fn1} and @var{fn2} are function +versions if and only if they imply different target specific attributes, +that is, they are compiled for different target machines. @end deftypefn @deftypefn {Target Hook} bool TARGET_CAN_INLINE_P (tree @var{caller}, tree @var{callee}) diff --git a/gcc/hooks.cc b/gcc/hooks.cc index 0decbf14afc..8b06de943f8 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 @@ -584,3 +585,9 @@ hook_stringslice_locationt_false (string_slice, location_t) { return false; } + +bool +hook_stringslice_stringslice_unreachable (string_slice, string_slice) +{ + gcc_unreachable (); +} diff --git a/gcc/hooks.h b/gcc/hooks.h index 0de7c53675b..171456407e1 100644 --- a/gcc/hooks.h +++ b/gcc/hooks.h @@ -138,5 +138,6 @@ extern opt_machine_mode hook_optmode_mode_uhwi_none (machine_mode, unsigned HOST_WIDE_INT); extern bool hook_stringslice_locationt_false (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 206c7a6395e..28b5f95434e 100644 --- a/gcc/target.def +++ b/gcc/target.def @@ -6912,13 +6912,12 @@ 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\ -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 target hook returns @code{true} if @var{fn1} and @var{fn2} are\n\ +versions of the same function. @var{fn1} and @var{fn2} are function\n\ +versions if and only if they imply different target specific attributes,\n\ +that is, they are compiled for different target machines.", + 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 705f0ae663a..435e9e472d4 100644 --- a/gcc/tree.cc +++ b/gcc/tree.cc @@ -15413,6 +15413,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 76fea1253fd..e553297c689 100644 --- a/gcc/tree.h +++ b/gcc/tree.h @@ -7065,4 +7065,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 */ -- 2.34.1