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 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 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. * 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 | 3 +- gcc/cp/decl.cc | 8 +- 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 | 235 ++++++++++++++++++++++++++++++++++ gcc/tree.h | 6 + 13 files changed, 305 insertions(+), 104 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 786362f8876..a8cb1979c94 100644 --- a/gcc/config/aarch64/aarch64.cc +++ b/gcc/config/aarch64/aarch64.cc @@ -20869,13 +20869,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 19304879d4f..2a1139ecf4b 100644 --- a/gcc/config/riscv/riscv.cc +++ b/gcc/config/riscv/riscv.cc @@ -13337,6 +13337,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 @@ -13361,20 +13377,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 5e828b898ac..bdfe8b3dd46 100644 --- a/gcc/cp/class.cc +++ b/gcc/cp/class.cc @@ -9077,8 +9077,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/decl.cc b/gcc/cp/decl.cc index 0697de05e9d..f439f456122 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)) + && distinct_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 (!distinct_version_decls (newdecl, olddecl)) return false; maybe_mark_function_versioned (olddecl); @@ -2104,6 +2104,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 666ab784dee..23fffb6918c 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 253ea6dd77f..ee5bd2871fc 100644 --- a/gcc/doc/tm.texi +++ b/gcc/doc/tm.texi @@ -10956,12 +10956,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 25dd814fe05..9630d3b9848 100644 --- a/gcc/target.def +++ b/gcc/target.def @@ -6908,13 +6908,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 e6ce23fe0bb..f8d585824f0 100644 --- a/gcc/tree.cc +++ b/gcc/tree.cc @@ -15400,6 +15400,241 @@ get_target_version (const tree decl) .strip (); } +bool +distinct_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.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.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.function_versions (v1, v2)) + return false; + + return true; + } + } +} + +/* Check if the target_version/target_clones attributes are mergeable + for two decls. + If they aren't mergeable, diagnose this. + 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 true; + + /* 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 true; + + /* 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 true; + + /* 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.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.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 false; + } + + return true; + } + + /* 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 false; + } + + 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 false; + } + + /* The only remaining case is two target_version annoteted decls. Must + be mergeable as otherwise are distinct. */ + return true; +} + void tree_cc_finalize (void) { diff --git a/gcc/tree.h b/gcc/tree.h index b9a25b68d62..ef6884b7fad 100644 --- a/gcc/tree.h +++ b/gcc/tree.h @@ -7068,4 +7068,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); +/* Checks if two overlapping decls are mergeable.. */ +extern bool diagnose_versioned_decls (tree, tree); + #endif /* GCC_TREE_H */ -- 2.34.1