This patch implements TARGET_COMPARE_VERSION_PRIORITY and TARGET_OPTION_FUNCTION_VERSIONS for RISC-V.
The TARGET_COMPARE_VERSION_PRIORITY is implemented to compare the priority of two function versions based on the rules defined in the RISC-V C-API Doc PR #85: https://github.com/riscv-non-isa/riscv-c-api-doc/pull/85/files#diff-79a93ca266139524b8b642e582ac20999357542001f1f4666fbb62b6fb7a5824R721 If multiple versions have equal priority, we select the function with the most number of feature bits generated by riscv_minimal_hwprobe_feature_bits. When it comes to the same number of feature bits, we diff two versions and select the one with the least significant bit set. Since a feature appears earlier in the feature_bits might be more important to performance. The TARGET_OPTION_FUNCTION_VERSIONS is implemented to check whether the two function versions are the same. This Implementation reuses the code in TARGET_COMPARE_VERSION_PRIORITY and check it returns 0, which means the equal priority. Co-Developed-by: Hank Chang <hank.ch...@sifive.com> gcc/ChangeLog: * config/riscv/riscv-target-attr.cc (riscv_target_attr_parser::update_settings): never free the arch string. * config/riscv/riscv.cc (parse_features_for_version): New function. (compare_fmv_features): New function. (riscv_compare_version_priority): New function. (riscv_common_function_versions): New function. (TARGET_COMPARE_VERSION_PRIORITY): Implement it. (TARGET_OPTION_FUNCTION_VERSIONS): Implement it. * config/riscv/riscv.h (TARGET_HAS_FMV_TARGET_ATTRIBUTE): Define it to 0 to use "target_version" for function versioning. --- gcc/config/riscv/riscv-target-attr.cc | 4 - gcc/config/riscv/riscv.cc | 127 ++++++++++++++++++++++++++ gcc/config/riscv/riscv.h | 2 + 3 files changed, 129 insertions(+), 4 deletions(-) diff --git a/gcc/config/riscv/riscv-target-attr.cc b/gcc/config/riscv/riscv-target-attr.cc index 087fbae77b0..4c85ad60b72 100644 --- a/gcc/config/riscv/riscv-target-attr.cc +++ b/gcc/config/riscv/riscv-target-attr.cc @@ -239,10 +239,6 @@ riscv_target_attr_parser::update_settings (struct gcc_options *opts) const { std::string local_arch = m_subset_list->to_string (true); const char* local_arch_str = local_arch.c_str (); - struct cl_target_option *default_opts - = TREE_TARGET_OPTION (target_option_default_node); - if (opts->x_riscv_arch_string != default_opts->x_riscv_arch_string) - free (CONST_CAST (void *, (const void *) opts->x_riscv_arch_string)); opts->x_riscv_arch_string = xstrdup (local_arch_str); riscv_set_arch_by_subset_list (m_subset_list, opts); diff --git a/gcc/config/riscv/riscv.cc b/gcc/config/riscv/riscv.cc index 3ac40234345..947864fc3a6 100644 --- a/gcc/config/riscv/riscv.cc +++ b/gcc/config/riscv/riscv.cc @@ -12574,6 +12574,127 @@ riscv_c_mode_for_floating_type (enum tree_index ti) return default_mode_for_floating_type (ti); } +/* This parses the attribute arguments to target_version in DECL and modifies + the feature mask and priority required to select those targets. */ +static void +parse_features_for_version (tree decl, + struct riscv_feature_bits &res, + int &priority) +{ + tree version_attr = lookup_attribute ("target_version", + DECL_ATTRIBUTES (decl)); + if (version_attr == NULL_TREE) + { + res.length = 0; + priority = 0; + return; + } + + const char *version_string = TREE_STRING_POINTER (TREE_VALUE (TREE_VALUE + (version_attr))); + gcc_assert (version_string != NULL); + if (strcmp (version_string, "default") == 0) + { + res.length = 0; + priority = 0; + return; + } + struct cl_target_option cur_target; + cl_target_option_save (&cur_target, &global_options, + &global_options_set); + /* Always set to default option before parsing "arch=+..." */ + struct cl_target_option *default_opts + = TREE_TARGET_OPTION (target_option_default_node); + cl_target_option_restore (&global_options, &global_options_set, + default_opts); + + riscv_process_target_attr (version_string, + DECL_SOURCE_LOCATION (decl)); + + priority = global_options.x_riscv_fmv_priority; + const char *arch_string = global_options.x_riscv_arch_string; + bool parse_res + = riscv_minimal_hwprobe_feature_bits (arch_string, &res, + DECL_SOURCE_LOCATION (decl)); + gcc_assert (parse_res); + + if (arch_string != default_opts->x_riscv_arch_string) + free (CONST_CAST (void *, (const void *) arch_string)); + + cl_target_option_restore (&global_options, &global_options_set, + &cur_target); +} + +/* Compare priorities of two feature masks. Return: + 1: mask1 is higher priority + -1: mask2 is higher priority + 0: masks are equal. + Since riscv_feature_bits has total 128 bits to be used as mask, when counting + the total 1s in the mask, the 1s in group1 needs to multiply a weight. */ +static int +compare_fmv_features (const struct riscv_feature_bits &mask1, + const struct riscv_feature_bits &mask2, + int prio1, int prio2) +{ + unsigned length1 = mask1.length, length2 = mask2.length; + /* 1. Compare length, for length == 0 means default version, which should be + the lowest priority). */ + if (length1 != length2) + return length1 > length2 ? 1 : -1; + /* 2. Compare the priority. */ + if (prio1 != prio2) + return prio1 > prio2 ? 1 : -1; + /* 3. Compare the total number of 1s in the mask. */ + unsigned pop1 = 0, pop2 = 0; + for (unsigned i = 0; i < length1; i++) + { + pop1 += __builtin_popcountll (mask1.features[i]); + pop2 += __builtin_popcountll (mask2.features[i]); + } + if (pop1 != pop2) + return pop1 > pop2 ? 1 : -1; + /* 4. Compare the mask bit by bit order. */ + for (unsigned i = 0; i < length1; i++) + { + unsigned long long xor_mask = mask1.features[i] ^ mask2.features[i]; + if (xor_mask == 0) + continue; + return TEST_BIT (mask1.features[i], __builtin_ctzll (xor_mask)) ? 1 : -1; + } + /* 5. If all bits are equal, return 0. */ + return 0; +} + +/* Compare priorities of two version decls. Return: + 1: mask1 is higher priority + -1: mask2 is higher priority + 0: masks are equal. */ +int +riscv_compare_version_priority (tree decl1, tree decl2) +{ + struct riscv_feature_bits mask1, mask2; + int prio1, prio2; + + parse_features_for_version (decl1, mask1, prio1); + parse_features_for_version (decl2, mask2, prio2); + + 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; +} + /* On riscv we have an ABI defined safe buffer. This constant is used to determining the probe offset for alloca. */ @@ -12948,6 +13069,12 @@ riscv_stack_clash_protection_alloca_probe_range (void) #undef TARGET_C_MODE_FOR_FLOATING_TYPE #define TARGET_C_MODE_FOR_FLOATING_TYPE riscv_c_mode_for_floating_type +#undef TARGET_COMPARE_VERSION_PRIORITY +#define TARGET_COMPARE_VERSION_PRIORITY riscv_compare_version_priority + +#undef TARGET_OPTION_FUNCTION_VERSIONS +#define TARGET_OPTION_FUNCTION_VERSIONS riscv_common_function_versions + struct gcc_target targetm = TARGET_INITIALIZER; #include "gt-riscv.h" diff --git a/gcc/config/riscv/riscv.h b/gcc/config/riscv/riscv.h index 2ff9c1024f3..8a8b08b6b51 100644 --- a/gcc/config/riscv/riscv.h +++ b/gcc/config/riscv/riscv.h @@ -1303,4 +1303,6 @@ extern void riscv_remove_unneeded_save_restore_calls (void); the target attribute. */ #define TARGET_CLONES_ATTR_SEPARATOR '#' +#define TARGET_HAS_FMV_TARGET_ATTRIBUTE 0 + #endif /* ! GCC_RISCV_H */ -- 2.45.2