@@ -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. */
@@ -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 *> *);
@@ -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
@@ -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)
{
@@ -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)
@@ -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);
@@ -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), "_")
@@ -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)))
@@ -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})
@@ -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 ();
+}
@@ -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
@@ -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
@@ -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)
{
@@ -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 */