This patch adds support for function multi-versioning to the RISC-V
using the target_clones and target_versions attributes, which follow
the RISC-V C-API Docs [1] and the existing proposal about priority
syntax [2].
This patch copies many codes from commit 0cfde688e213 ("[aarch64]
Add function multiversioning support") and modifies them to fit the
RISC-V port. Some key differences are introduced in previously
submitted patches [3] and [4], commit [5] and [6].
To test this patch with the GLIBC dynamic loader, you should apply
patch [7] for GLIBC to ensure the dynamic loader will initialize
the gp register correctly.
[1]
https://github.com/riscv-non-isa/riscv-c-api-doc/blob/c6c5d6d9cf96b342293315a5dff3d25e96ef8191/src/c-api.adoc#__attribute__targetattr-string
[2] https://github.com/riscv-non-isa/riscv-c-api-doc/pull/85
[3]
https://patchwork.sourceware.org/project/gcc/patch/[email protected]/
[4]
https://patchwork.sourceware.org/project/gcc/patch/[email protected]/
[5]
https://github.com/cyyself/gcc/commit/618352340e1e78057d1e9864c87d8641a2522f80
[6]
https://github.com/cyyself/gcc/commit/30e011886299996b2e1f922b13ec4192bbde6e94
[7]
https://patchwork.sourceware.org/project/glibc/patch/[email protected]/
Co-Developed-by: Hank Chang <[email protected]>
gcc/ChangeLog:
* common/config/riscv/riscv-common.cc
(struct riscv_ext_bitmask_table_t): New struct.
(riscv_minimal_hwprobe_feature_bits): New function.
* config/riscv/riscv-protos.h
(riscv_option_valid_version_attribute_p): New function.
(riscv_process_target_attr): New function.
* config/riscv/riscv-subset.h
(riscv_minimal_hwprobe_feature_bits): New function.
* config/riscv/riscv-target-attr.cc
(riscv_target_attr_parser::handle_priority): New function.
(riscv_target_attr_parser::update_settings): Update priority
attribute and never free the arch string.
(riscv_process_one_target_attr): Add const qualifier to arg_str
and split arg_str with ';'.
(riscv_process_target_attr): New implementation which consumes
the const char *args instead of tree.
(riscv_option_valid_attribute_p): Reapply any target_version
attribute after target attribute.
(riscv_process_target_version_attr): New function.
* config/riscv/riscv.cc (riscv_can_inline_p): Refuse to inline
when callee is versioned but caller is not.
(parse_features_for_version): New function.
(compare_fmv_features): New function.
(riscv_compare_version_priority): New function.
(riscv_common_function_versions): New function.
(add_condition_to_bb): New function.
(dispatch_function_versions): New function.
(get_suffixed_assembler_name): New function.
(make_resolver_func): New function.
(riscv_mangle_decl_assembler_name): New function.
(riscv_generate_version_dispatcher_body): New function.
(riscv_get_function_versions_dispatcher): New function.
(TARGET_OPTION_VALID_VERSION_ATTRIBUTE_P): Implement it.
(TARGET_OPTION_FUNCTION_VERSIONS): Implement it.
(TARGET_COMPARE_VERSION_PRIORITY): Implement it.
(TARGET_GENERATE_VERSION_DISPATCHER_BODY): Implement it.
(TARGET_GET_FUNCTION_VERSIONS_DISPATCHER): Implement it.
(TARGET_MANGLE_DECL_ASSEMBLER_NAME): Implement it.
* config/riscv/riscv.opt: Add TargetVariable riscv_fmv_priority.
* defaults.h (TARGET_CLONES_ATTR_SEPARATOR): Define new macro.
* multiple_target.cc (get_attr_str): Use
TARGET_CLONES_ATTR_SEPARATOR to separate attributes.
(separate_attrs): Likewise.
* config/riscv/riscv.h
(TARGET_CLONES_ATTR_SEPARATOR): Implement it.
(TARGET_HAS_FMV_TARGET_ATTRIBUTE): Implement it.
* config/riscv/feature_bits.h: New file.
---
gcc/common/config/riscv/riscv-common.cc | 145 +++++
gcc/config/riscv/feature_bits.h | 44 ++
gcc/config/riscv/riscv-protos.h | 4 +
gcc/config/riscv/riscv-subset.h | 5 +
gcc/config/riscv/riscv-target-attr.cc | 210 +++++--
gcc/config/riscv/riscv.cc | 757 ++++++++++++++++++++++++
gcc/config/riscv/riscv.h | 7 +
gcc/config/riscv/riscv.opt | 3 +
gcc/defaults.h | 4 +
gcc/multiple_target.cc | 19 +-
10 files changed, 1157 insertions(+), 41 deletions(-)
create mode 100644 gcc/config/riscv/feature_bits.h
diff --git a/gcc/common/config/riscv/riscv-common.cc
b/gcc/common/config/riscv/riscv-common.cc
index 2adebe0b6f2..2fc21424ebe 100644
--- a/gcc/common/config/riscv/riscv-common.cc
+++ b/gcc/common/config/riscv/riscv-common.cc
@@ -19,6 +19,7 @@ along with GCC; see the file COPYING3. If not see
#include <sstream>
#include <vector>
+#include <queue>
#define INCLUDE_STRING
#define INCLUDE_SET
@@ -1760,6 +1761,75 @@ static const riscv_ext_flag_table_t
riscv_ext_flag_table[] =
{NULL, NULL, NULL, 0}
};
+/* Types for recording extension to RISC-V C-API bitmask. */
+struct riscv_ext_bitmask_table_t {
+ const char *ext;
+ int groupid;
+ int bit_position;
+};
+
+/* Mapping table between extension to RISC-V C-API extension bitmask.
+ This table should sort the extension by Linux hwprobe order to get the
+ minimal feature bits. */
+static const riscv_ext_bitmask_table_t riscv_ext_bitmask_table[] =
+{
+ {"i", 0, 8},
+ {"m", 0, 12},
+ {"a", 0, 0},
+ {"f", 0, 5},
+ {"d", 0, 3},
+ {"c", 0, 2},
+ {"v", 0, 21},
+ {"zba", 0, 27},
+ {"zbb", 0, 28},
+ {"zbs", 0, 33},
+ {"zicboz", 0, 37},
+ {"zbc", 0, 29},
+ {"zbkb", 0, 30},
+ {"zbkc", 0, 31},
+ {"zbkx", 0, 32},
+ {"zknd", 0, 41},
+ {"zkne", 0, 42},
+ {"zknh", 0, 43},
+ {"zksed", 0, 44},
+ {"zksh", 0, 45},
+ {"zkt", 0, 46},
+ {"zvbb", 0, 48},
+ {"zvbc", 0, 49},
+ {"zvkb", 0, 52},
+ {"zvkg", 0, 53},
+ {"zvkned", 0, 54},
+ {"zvknha", 0, 55},
+ {"zvknhb", 0, 56},
+ {"zvksed", 0, 57},
+ {"zvksh", 0, 58},
+ {"zvkt", 0, 59},
+ {"zfh", 0, 35},
+ {"zfhmin", 0, 36},
+ {"zihintntl", 0, 39},
+ {"zvfh", 0, 50},
+ {"zvfhmin", 0, 51},
+ {"zfa", 0, 34},
+ {"ztso", 0, 47},
+ {"zacas", 0, 26},
+ {"zicond", 0, 38},
+ {"zihintpause", 0, 40},
+ {"zve32x", 0, 60},
+ {"zve32f", 0, 61},
+ {"zve64x", 0, 62},
+ {"zve64f", 0, 63},
+ {"zve64d", 1, 0},
+ {"zimop", 1, 1},
+ {"zca", 1, 2},
+ {"zcb", 1, 3},
+ {"zcd", 1, 4},
+ {"zcf", 1, 5},
+ {"zcmop", 1, 6},
+ {"zawrs", 1, 7},
+
+ {NULL, -1, -1}
+};
+
/* Apply SUBSET_LIST to OPTS if OPTS is not null. */
void
@@ -1826,6 +1896,81 @@ riscv_x_target_flags_isa_mask (void)
return mask;
}
+/* Get the minimal feature bits in Linux hwprobe of the given ISA string.
+
+ Used for generating Function Multi-Versioning (FMV) dispatcher for RISC-V.
+
+ The minimal feature bits refer to using the earliest extension that appeared
+ in the Linux hwprobe to support the specified ISA string. This ensures that
+ older kernels, which may lack certain implied extensions, can still run the
+ FMV dispatcher correctly. */
+
+bool
+riscv_minimal_hwprobe_feature_bits (const char *isa,
+ struct riscv_feature_bits *res,
+ location_t loc)
+{
+ riscv_subset_list *subset_list;
+ subset_list = riscv_subset_list::parse (isa, loc);
+ if (!subset_list)
+ return false;
+
+ /* Initialize the result feature bits to zero. */
+ res->length = RISCV_FEATURE_BITS_LENGTH;
+ for (int i = 0; i < RISCV_FEATURE_BITS_LENGTH; ++i)
+ res->features[i] = 0;
+
+ /* Use a std::set to record all visited implied extensions. */
+ std::set <std::string> implied_exts;
+
+ /* Iterate through the extension bitmask table in Linux hwprobe order to get
+ the minimal covered feature bits. Avoiding some sub-extensions which will
+ be implied by the super-extensions like V implied Zve32x. */
+ const riscv_ext_bitmask_table_t *ext_bitmask_tab;
+ for (ext_bitmask_tab = &riscv_ext_bitmask_table[0];
+ ext_bitmask_tab->ext;
+ ++ext_bitmask_tab)
+ {
+ /* Skip the extension if it is not in the subset list or already implied
+ by previous extension. */
+ if (subset_list->lookup (ext_bitmask_tab->ext) == NULL
+ || implied_exts.count (ext_bitmask_tab->ext))
+ continue;
+
+ res->features[ext_bitmask_tab->groupid]
+ |= 1ULL << ext_bitmask_tab->bit_position;
+
+ /* Find the sub-extension using BFS and set the corresponding bit. */
+ std::queue <const char *> search_q;
+ search_q.push (ext_bitmask_tab->ext);
+
+ while (!search_q.empty ())
+ {
+ const char * search_ext = search_q.front ();
+ search_q.pop ();
+
+ /* Iterate through the implied extension table. */
+ const riscv_implied_info_t *implied_info;
+ for (implied_info = &riscv_implied_info[0];
+ implied_info->ext;
+ ++implied_info)
+ {
+ /* When the search extension matches the implied extension and
+ the implied extension has not been visited, mark the implied
+ extension in the implied_exts set and push it into the
+ queue. */
+ if (implied_info->match (subset_list, search_ext)
+ && implied_exts.count (implied_info->implied_ext) == 0)
+ {
+ implied_exts.insert (implied_info->implied_ext);
+ search_q.push (implied_info->implied_ext);
+ }
+ }
+ }
+ }
+ return true;
+}
+
/* Parse a RISC-V ISA string into an option mask. Must clear or set all arch
dependent mask bits, in case more than one -march string is passed. */
diff --git a/gcc/config/riscv/feature_bits.h b/gcc/config/riscv/feature_bits.h
new file mode 100644
index 00000000000..19b7630e339
--- /dev/null
+++ b/gcc/config/riscv/feature_bits.h
@@ -0,0 +1,44 @@
+/* Definition of RISC-V feature bits corresponding to
+ libgcc/config/riscv/feature_bits.c
+ Copyright (C) 2024 Free Software Foundation, Inc.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#ifndef GCC_RISCV_FEATURE_BITS_H
+#define GCC_RISCV_FEATURE_BITS_H
+
+#define RISCV_FEATURE_BITS_LENGTH 2
+
+struct riscv_feature_bits {
+ unsigned length;
+ unsigned long long features[RISCV_FEATURE_BITS_LENGTH];
+};
+
+#define RISCV_VENDOR_FEATURE_BITS_LENGTH 1
+
+struct riscv_vendor_feature_bits {
+ unsigned length;
+ unsigned long long features[RISCV_VENDOR_FEATURE_BITS_LENGTH];
+};
+
+struct riscv_cpu_model {
+ unsigned mvendorid;
+ unsigned long long marchid;
+ unsigned long long mimpid;
+};
+
+#endif /* GCC_RISCV_FEATURE_BITS_H */
diff --git a/gcc/config/riscv/riscv-protos.h b/gcc/config/riscv/riscv-protos.h
index d690162bb0c..a35316f1228 100644
--- a/gcc/config/riscv/riscv-protos.h
+++ b/gcc/config/riscv/riscv-protos.h
@@ -799,6 +799,10 @@ extern bool riscv_use_divmod_expander (void);
void riscv_init_cumulative_args (CUMULATIVE_ARGS *, tree, rtx, tree, int);
extern bool
riscv_option_valid_attribute_p (tree, tree, tree, int);
+extern bool
+riscv_option_valid_version_attribute_p (tree, tree, tree, int);
+extern bool
+riscv_process_target_attr (const char *, location_t);
extern void
riscv_override_options_internal (struct gcc_options *);
extern void riscv_option_override (void);
diff --git a/gcc/config/riscv/riscv-subset.h b/gcc/config/riscv/riscv-subset.h
index 1914a5317d7..75008be6613 100644
--- a/gcc/config/riscv/riscv-subset.h
+++ b/gcc/config/riscv/riscv-subset.h
@@ -22,6 +22,8 @@ along with GCC; see the file COPYING3. If not see
#ifndef GCC_RISCV_SUBSET_H
#define GCC_RISCV_SUBSET_H
+#include "feature_bits.h"
+
#define RISCV_DONT_CARE_VERSION -1
/* Subset info. */
@@ -120,6 +122,9 @@ public:
extern const riscv_subset_list *riscv_cmdline_subset_list (void);
extern void
riscv_set_arch_by_subset_list (riscv_subset_list *, struct gcc_options *);
+extern bool riscv_minimal_hwprobe_feature_bits (const char *,
+ struct riscv_feature_bits *,
+ location_t);
extern bool
riscv_ext_is_subset (struct cl_target_option *, struct cl_target_option *);
extern int riscv_x_target_flags_isa_mask (void);
diff --git a/gcc/config/riscv/riscv-target-attr.cc
b/gcc/config/riscv/riscv-target-attr.cc
index bf14ade5ce0..6a28281b0c9 100644
--- a/gcc/config/riscv/riscv-target-attr.cc
+++ b/gcc/config/riscv/riscv-target-attr.cc
@@ -30,6 +30,8 @@ along with GCC; see the file COPYING3. If not see
#include "diagnostic.h"
#include "opts.h"
#include "riscv-subset.h"
+#include "stringpool.h"
+#include "attribs.h"
namespace {
class riscv_target_attr_parser
@@ -39,6 +41,7 @@ public:
: m_found_arch_p (false)
, m_found_tune_p (false)
, m_found_cpu_p (false)
+ , m_found_priority_p (false)
, m_subset_list (nullptr)
, m_loc (loc)
, m_cpu_info (nullptr)
@@ -49,6 +52,7 @@ public:
bool handle_arch (const char *);
bool handle_cpu (const char *);
bool handle_tune (const char *);
+ bool handle_priority (const char *);
void update_settings (struct gcc_options *opts) const;
private:
@@ -58,10 +62,12 @@ private:
bool m_found_arch_p;
bool m_found_tune_p;
bool m_found_cpu_p;
+ bool m_found_priority_p;
riscv_subset_list *m_subset_list;
location_t m_loc;
const riscv_cpu_info *m_cpu_info;
const char *m_tune;
+ int m_priority;
};
}
@@ -80,7 +86,8 @@ struct riscv_attribute_info
static const struct riscv_attribute_info riscv_attributes[]
= {{"arch", &riscv_target_attr_parser::handle_arch},
{"cpu", &riscv_target_attr_parser::handle_cpu},
- {"tune", &riscv_target_attr_parser::handle_tune}};
+ {"tune", &riscv_target_attr_parser::handle_tune},
+ {"priority", &riscv_target_attr_parser::handle_priority}};
bool
riscv_target_attr_parser::parse_arch (const char *str)
@@ -210,6 +217,22 @@ riscv_target_attr_parser::handle_tune (const char *str)
return true;
}
+bool
+riscv_target_attr_parser::handle_priority (const char *str)
+{
+ if (m_found_priority_p)
+ error_at (m_loc, "%<target()%> attribute: priority appears more than
once");
+ m_found_priority_p = true;
+
+ if (sscanf (str, "%d", &m_priority) != 1)
+ {
+ error_at (m_loc, "%<target()%> attribute: invalid priority %qs", str);
+ return false;
+ }
+
+ return true;
+}
+
void
riscv_target_attr_parser::update_settings (struct gcc_options *opts) const
{
@@ -217,10 +240,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);
@@ -236,13 +255,16 @@ riscv_target_attr_parser::update_settings (struct
gcc_options *opts) const
if (m_cpu_info)
opts->x_riscv_tune_string = m_cpu_info->tune;
}
+
+ if (m_found_priority_p)
+ opts->x_riscv_fmv_priority = m_priority;
}
/* Parse ARG_STR which contains the definition of one target attribute.
Show appropriate errors if any or return true if the attribute is valid. */
static bool
-riscv_process_one_target_attr (char *arg_str,
+riscv_process_one_target_attr (const char *arg_str,
location_t loc,
riscv_target_attr_parser &attr_parser)
{
@@ -271,6 +293,12 @@ riscv_process_one_target_attr (char *arg_str,
arg[0] = '\0';
++arg;
+
+ /* Skip splitter ';' if it exists. */
+ char *splitter = strchr (arg, ';');
+ if (splitter)
+ splitter[0] = '\0';
+
for (const auto &attr : riscv_attributes)
{
/* If the names don't match up, or the user has given an argument
@@ -304,35 +332,13 @@ num_occurrences_in_str (char c, char *str)
return res;
}
-/* Parse the tree in ARGS that contains the target attribute information
+/* Parse the string ARGS that contains the target attribute information
and update the global target options space. */
-static bool
-riscv_process_target_attr (tree args, location_t loc)
+bool
+riscv_process_target_attr (const char *args, location_t loc)
{
- if (TREE_CODE (args) == TREE_LIST)
- {
- do
- {
- tree head = TREE_VALUE (args);
- if (head)
- {
- if (!riscv_process_target_attr (head, loc))
- return false;
- }
- args = TREE_CHAIN (args);
- } while (args);
-
- return true;
- }
-
- if (TREE_CODE (args) != STRING_CST)
- {
- error_at (loc, "attribute %<target%> argument not a string");
- return false;
- }
-
- size_t len = strlen (TREE_STRING_POINTER (args));
+ size_t len = strlen (args);
/* No need to emit warning or error on empty string here, generic code
already
handle this case. */
@@ -341,9 +347,14 @@ riscv_process_target_attr (tree args, location_t loc)
return false;
}
+ if (strcmp (args, "default") == 0)
+ {
+ return true;
+ }
+
std::unique_ptr<char[]> buf (new char[len+1]);
char *str_to_check = buf.get ();
- strcpy (str_to_check, TREE_STRING_POINTER (args));
+ strcpy (str_to_check, args);
/* Used to catch empty spaces between semi-colons i.e.
attribute ((target ("attr1;;attr2"))). */
@@ -366,7 +377,7 @@ riscv_process_target_attr (tree args, location_t loc)
if (num_attrs != num_semicolons + 1)
{
error_at (loc, "malformed %<target(\"%s\")%> attribute",
- TREE_STRING_POINTER (args));
+ args);
return false;
}
@@ -376,6 +387,37 @@ riscv_process_target_attr (tree args, location_t loc)
return true;
}
+/* Parse the tree in ARGS that contains the target attribute information
+ and update the global target options space. */
+
+static bool
+riscv_process_target_attr (tree args, location_t loc)
+{
+ if (TREE_CODE (args) == TREE_LIST)
+ {
+ do
+ {
+ tree head = TREE_VALUE (args);
+ if (head)
+ {
+ if (!riscv_process_target_attr (head, loc))
+ return false;
+ }
+ args = TREE_CHAIN (args);
+ } while (args);
+
+ return true;
+ }
+
+ if (TREE_CODE (args) != STRING_CST)
+ {
+ error_at (loc, "attribute %<target%> argument not a string");
+ return false;
+ }
+
+ return riscv_process_target_attr (TREE_STRING_POINTER (args), loc);
+}
+
/* Implement TARGET_OPTION_VALID_ATTRIBUTE_P.
This is used to process attribute ((target ("..."))).
Note, that riscv_set_current_function() has not been called before,
@@ -412,6 +454,20 @@ riscv_option_valid_attribute_p (tree fndecl, tree, tree
args, int)
/* Now we can parse the attributes and set &global_options accordingly. */
ret = riscv_process_target_attr (args, loc);
+ if (ret)
+ {
+ tree version_attr = lookup_attribute ("target_version",
+ DECL_ATTRIBUTES (fndecl));
+ if (version_attr != NULL_TREE)
+ {
+ // Reapply any target_version attribute after target attribute.
+ // This should be equivalent to applying the target_version once
+ // after processing all target attributes.
+ tree version_args = TREE_VALUE (version_attr);
+ riscv_process_target_attr (version_args,
+ DECL_SOURCE_LOCATION (fndecl));
+ }
+ }
if (ret)
{
riscv_override_options_internal (&global_options);
@@ -424,3 +480,89 @@ riscv_option_valid_attribute_p (tree fndecl, tree, tree
args, int)
cl_target_option_restore (&global_options, &global_options_set, &cur_target);
return ret;
}
+
+/* Parse the tree in ARGS that contains the target_version attribute
+ information and update the global target options space. */
+
+static bool
+riscv_process_target_version_attr (tree args, location_t loc)
+{
+ if (TREE_CODE (args) == TREE_LIST)
+ {
+ if (TREE_CHAIN (args))
+ {
+ error ("attribute %<target_version%> has multiple values");
+ return false;
+ }
+ args = TREE_VALUE (args);
+ }
+
+ if (!args || TREE_CODE (args) != STRING_CST)
+ {
+ error ("attribute %<target_version%> argument not a string");
+ return false;
+ }
+
+ const char *str = TREE_STRING_POINTER (args);
+ if (strcmp (str, "default") == 0)
+ return true;
+
+ riscv_target_attr_parser attr_parser (loc);
+ if (!riscv_process_one_target_attr (str, loc, attr_parser))
+ return false;
+
+ attr_parser.update_settings (&global_options);
+ return true;
+}
+
+
+/* Implement TARGET_OPTION_VALID_VERSION_ATTRIBUTE_P. This is used to
+ process attribute ((target_version ("..."))). */
+
+bool
+riscv_option_valid_version_attribute_p (tree fndecl, tree, tree args, int)
+{
+ struct cl_target_option cur_target;
+ bool ret;
+ tree new_target;
+ tree existing_target = DECL_FUNCTION_SPECIFIC_TARGET (fndecl);
+ location_t loc = DECL_SOURCE_LOCATION (fndecl);
+
+ /* Save the current target options to restore at the end. */
+ cl_target_option_save (&cur_target, &global_options, &global_options_set);
+
+ /* If fndecl already has some target attributes applied to it, unpack
+ them so that we add this attribute on top of them, rather than
+ overwriting them. */
+ if (existing_target)
+ {
+ struct cl_target_option *existing_options
+ = TREE_TARGET_OPTION (existing_target);
+
+ if (existing_options)
+ cl_target_option_restore (&global_options, &global_options_set,
+ existing_options);
+ }
+ else
+ cl_target_option_restore (&global_options, &global_options_set,
+ TREE_TARGET_OPTION (target_option_current_node));
+
+ ret = riscv_process_target_attr (args, loc);
+
+ /* Set up any additional state. */
+ if (ret)
+ {
+ riscv_override_options_internal (&global_options);
+ new_target = build_target_option_node (&global_options,
+ &global_options_set);
+ }
+ else
+ new_target = NULL;
+
+ if (fndecl && ret)
+ DECL_FUNCTION_SPECIFIC_TARGET (fndecl) = new_target;
+
+ cl_target_option_restore (&global_options, &global_options_set, &cur_target);
+
+ return ret;
+}
diff --git a/gcc/config/riscv/riscv.cc b/gcc/config/riscv/riscv.cc
index 3ac40234345..f091d022e43 100644
--- a/gcc/config/riscv/riscv.cc
+++ b/gcc/config/riscv/riscv.cc
@@ -77,6 +77,9 @@ along with GCC; see the file COPYING3. If not see
#include "tree-dfa.h"
#include "target-globals.h"
#include "riscv-v.h"
+#include "cgraph.h"
+#include "langhooks.h"
+#include "gimplify.h"
/* This file should be included last. */
#include "target-def.h"
@@ -7666,6 +7669,10 @@ riscv_compute_frame_info (void)
static bool
riscv_can_inline_p (tree caller, tree callee)
{
+ /* Do not inline when callee is versioned but caller is not. */
+ if (DECL_FUNCTION_VERSIONED (callee) && ! DECL_FUNCTION_VERSIONED (caller))
+ return false;
+
tree callee_tree = DECL_FUNCTION_SPECIFIC_TARGET (callee);
tree caller_tree = DECL_FUNCTION_SPECIFIC_TARGET (caller);
@@ -12574,6 +12581,735 @@ 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 (int 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 (int 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;
+}
+
+/* This adds a condition to the basic_block NEW_BB in function FUNCTION_DECL
+ to return a pointer to VERSION_DECL if all feature bits specified in
+ FEATURE_MASK are not set in MASK_VAR. This function will be called during
+ version dispatch to decide which function version to execute. It returns
+ the basic block at the end, to which more conditions can be added. */
+static basic_block
+add_condition_to_bb (tree function_decl, tree version_decl,
+ const struct riscv_feature_bits *features,
+ tree mask_var, basic_block new_bb)
+{
+ gimple *return_stmt;
+ tree convert_expr, result_var;
+ gimple *convert_stmt;
+ gimple *if_else_stmt;
+
+ basic_block bb1, bb2, bb3;
+ edge e12, e23;
+
+ gimple_seq gseq;
+
+ push_cfun (DECL_STRUCT_FUNCTION (function_decl));
+
+ gcc_assert (new_bb != NULL);
+ gseq = bb_seq (new_bb);
+
+ convert_expr = build1 (CONVERT_EXPR, ptr_type_node,
+ build_fold_addr_expr (version_decl));
+ result_var = create_tmp_var (ptr_type_node);
+ convert_stmt = gimple_build_assign (result_var, convert_expr);
+ return_stmt = gimple_build_return (result_var);
+
+ if (features->length == 0)
+ {
+ /* Default version. */
+ gimple_seq_add_stmt (&gseq, convert_stmt);
+ gimple_seq_add_stmt (&gseq, return_stmt);
+ set_bb_seq (new_bb, gseq);
+ gimple_set_bb (convert_stmt, new_bb);
+ gimple_set_bb (return_stmt, new_bb);
+ pop_cfun ();
+ return new_bb;
+ }
+
+ tree zero_llu = build_int_cst (long_long_unsigned_type_node, 0);
+ tree cond_status = create_tmp_var (boolean_type_node);
+ tree mask_array_ele_var = create_tmp_var (long_long_unsigned_type_node);
+ tree and_expr_var = create_tmp_var (long_long_unsigned_type_node);
+ tree eq_expr_var = create_tmp_var (boolean_type_node);
+
+ /* cond_status = true. */
+ gimple *cond_init_stmt = gimple_build_assign (cond_status,
boolean_true_node);
+ gimple_set_block (cond_init_stmt, DECL_INITIAL (function_decl));
+ gimple_set_bb (cond_init_stmt, new_bb);
+ gimple_seq_add_stmt (&gseq, cond_init_stmt);
+
+ for (int i = 0; i < RISCV_FEATURE_BITS_LENGTH; i++)
+ {
+ tree index_expr = build_int_cst (unsigned_type_node, i);
+ /* mask_array_ele_var = mask_var[i] */
+ tree mask_array_ref = build4 (ARRAY_REF, long_long_unsigned_type_node,
+ mask_var, index_expr, NULL_TREE, NULL_TREE);
+
+ gimple *mask_stmt = gimple_build_assign (mask_array_ele_var,
+ mask_array_ref);
+ gimple_set_block (mask_stmt, DECL_INITIAL (function_decl));
+ gimple_set_bb (mask_stmt, new_bb);
+ gimple_seq_add_stmt (&gseq, mask_stmt);
+ /* and_expr_var = mask_array_ele_var & features[i] */
+ tree and_expr = build2 (BIT_AND_EXPR,
+ long_long_unsigned_type_node,
+ mask_array_ele_var,
+ build_int_cst (long_long_unsigned_type_node,
+ features->features[i]));
+ gimple *and_stmt = gimple_build_assign (and_expr_var, and_expr);
+ gimple_set_block (and_stmt, DECL_INITIAL (function_decl));
+ gimple_set_bb (and_stmt, new_bb);
+ gimple_seq_add_stmt (&gseq, and_stmt);
+ /* eq_expr_var = and_expr_var == 0. */
+ tree eq_expr = build2 (EQ_EXPR, boolean_type_node,
+ and_expr_var, zero_llu);
+ gimple *eq_stmt = gimple_build_assign (eq_expr_var, eq_expr);
+ gimple_set_block (eq_stmt, DECL_INITIAL (function_decl));
+ gimple_set_bb (eq_stmt, new_bb);
+ gimple_seq_add_stmt (&gseq, eq_stmt);
+ /* cond_status = cond_status & eq_expr_var. */
+ tree cond_expr = build2 (BIT_AND_EXPR, boolean_type_node,
+ cond_status, eq_expr_var);
+ gimple *cond_stmt = gimple_build_assign (cond_status, cond_expr);
+ gimple_set_block (cond_stmt, DECL_INITIAL (function_decl));
+ gimple_set_bb (cond_stmt, new_bb);
+ gimple_seq_add_stmt (&gseq, cond_stmt);
+ }
+ if_else_stmt = gimple_build_cond (EQ_EXPR, cond_status, boolean_true_node,
+ NULL_TREE, NULL_TREE);
+ gimple_set_block (if_else_stmt, DECL_INITIAL (function_decl));
+ gimple_set_bb (if_else_stmt, new_bb);
+ gimple_seq_add_stmt (&gseq, if_else_stmt);
+
+ gimple_seq_add_stmt (&gseq, convert_stmt);
+ gimple_seq_add_stmt (&gseq, return_stmt);
+ set_bb_seq (new_bb, gseq);
+
+ bb1 = new_bb;
+ e12 = split_block (bb1, if_else_stmt);
+ bb2 = e12->dest;
+ e12->flags &= ~EDGE_FALLTHRU;
+ e12->flags |= EDGE_TRUE_VALUE;
+
+ e23 = split_block (bb2, return_stmt);
+
+ gimple_set_bb (convert_stmt, bb2);
+ gimple_set_bb (return_stmt, bb2);
+
+ bb3 = e23->dest;
+ make_edge (bb1, bb3, EDGE_FALSE_VALUE);
+
+ remove_edge (e23);
+ make_edge (bb2, EXIT_BLOCK_PTR_FOR_FN (cfun), 0);
+
+ pop_cfun ();
+
+ return bb3;
+}
+
+/* This function generates the dispatch function for
+ multi-versioned functions. DISPATCH_DECL is the function which will
+ contain the dispatch logic. FNDECLS are the function choices for
+ dispatch, and is a tree chain. EMPTY_BB is the basic block pointer
+ in DISPATCH_DECL in which the dispatch code is generated. */
+
+static int
+dispatch_function_versions (tree dispatch_decl,
+ void *fndecls_p,
+ basic_block *empty_bb)
+{
+ gimple *ifunc_cpu_init_stmt;
+ gimple_seq gseq;
+ vec<tree> *fndecls;
+
+ gcc_assert (dispatch_decl != NULL
+ && fndecls_p != NULL
+ && empty_bb != NULL);
+
+ push_cfun (DECL_STRUCT_FUNCTION (dispatch_decl));
+
+ gseq = bb_seq (*empty_bb);
+ /* Function version dispatch is via IFUNC. IFUNC resolvers fire before
+ constructors, so explicity call __init_riscv_feature_bits here. */
+ tree init_fn_type = build_function_type_list (void_type_node,
+ long_unsigned_type_node,
+ ptr_type_node,
+ NULL);
+ tree init_fn_id = get_identifier ("__init_riscv_feature_bits");
+ tree init_fn_decl = build_decl (UNKNOWN_LOCATION, FUNCTION_DECL,
+ init_fn_id, init_fn_type);
+ ifunc_cpu_init_stmt = gimple_build_call (init_fn_decl, 0);
+ gimple_seq_add_stmt (&gseq, ifunc_cpu_init_stmt);
+ gimple_set_bb (ifunc_cpu_init_stmt, *empty_bb);
+
+ /* Build the struct type for __riscv_feature_bits. */
+ tree global_type = lang_hooks.types.make_type (RECORD_TYPE);
+ tree features_type = build_array_type_nelts (long_long_unsigned_type_node,
+ RISCV_FEATURE_BITS_LENGTH);
+ tree field1 = build_decl (UNKNOWN_LOCATION, FIELD_DECL,
+ get_identifier ("length"),
+ unsigned_type_node);
+ tree field2 = build_decl (UNKNOWN_LOCATION, FIELD_DECL,
+ get_identifier ("features"),
+ features_type);
+ DECL_FIELD_CONTEXT (field1) = global_type;
+ DECL_FIELD_CONTEXT (field2) = global_type;
+ TYPE_FIELDS (global_type) = field1;
+ DECL_CHAIN (field1) = field2;
+ layout_type (global_type);
+
+ tree global_var = build_decl (UNKNOWN_LOCATION, VAR_DECL,
+ get_identifier ("__riscv_feature_bits"),
+ global_type);
+ DECL_EXTERNAL (global_var) = 1;
+ tree mask_var = create_tmp_var (features_type);
+ tree feature_ele_var = create_tmp_var (long_long_unsigned_type_node);
+ tree noted_var = create_tmp_var (long_long_unsigned_type_node);
+
+
+ for (int i = 0; i < RISCV_FEATURE_BITS_LENGTH; i++)
+ {
+ tree index_expr = build_int_cst (unsigned_type_node, i);
+ /* feature_ele_var = __riscv_feature_bits.features[i] */
+ tree component_expr = build3 (COMPONENT_REF, features_type,
+ global_var, field2, NULL_TREE);
+ tree feature_array_ref = build4 (ARRAY_REF, long_long_unsigned_type_node,
+ component_expr, index_expr,
+ NULL_TREE, NULL_TREE);
+ gimple *feature_stmt = gimple_build_assign (feature_ele_var,
+ feature_array_ref);
+ gimple_set_block (feature_stmt, DECL_INITIAL (dispatch_decl));
+ gimple_set_bb (feature_stmt, *empty_bb);
+ gimple_seq_add_stmt (&gseq, feature_stmt);
+ /* noted_var = ~feature_ele_var. */
+ tree not_expr = build1 (BIT_NOT_EXPR, long_long_unsigned_type_node,
+ feature_ele_var);
+ gimple *not_stmt = gimple_build_assign (noted_var, not_expr);
+ gimple_set_block (not_stmt, DECL_INITIAL (dispatch_decl));
+ gimple_set_bb (not_stmt, *empty_bb);
+ gimple_seq_add_stmt (&gseq, not_stmt);
+ /* mask_var[i] = ~feature_ele_var. */
+ tree mask_array_ref = build4 (ARRAY_REF, long_long_unsigned_type_node,
+ mask_var, index_expr, NULL_TREE, NULL_TREE);
+ gimple *mask_assign_stmt = gimple_build_assign (mask_array_ref,
+ noted_var);
+ gimple_set_block (mask_assign_stmt, DECL_INITIAL (dispatch_decl));
+ gimple_set_bb (mask_assign_stmt, *empty_bb);
+ gimple_seq_add_stmt (&gseq, mask_assign_stmt);
+ }
+
+ set_bb_seq (*empty_bb, gseq);
+
+ pop_cfun ();
+
+ /* fndecls_p is actually a vector. */
+ fndecls = static_cast<vec<tree> *> (fndecls_p);
+
+ /* At least one more version other than the default. */
+ unsigned int num_versions = fndecls->length ();
+ gcc_assert (num_versions >= 2);
+
+ struct function_version_info
+ {
+ tree version_decl;
+ int prio;
+ struct riscv_feature_bits features;
+ } *function_versions;
+
+ function_versions = (struct function_version_info *)
+ XNEWVEC (struct function_version_info, (num_versions));
+
+ unsigned int actual_versions = 0;
+
+ for (tree version_decl : *fndecls)
+ {
+ function_versions[actual_versions].version_decl = version_decl;
+ // Get attribute string, parse it and find the right features.
+ parse_features_for_version (version_decl,
+ function_versions[actual_versions].features,
+ function_versions[actual_versions].prio);
+ actual_versions++;
+ }
+
+
+ auto compare_feature_version_info = [](const void *p1, const void *p2)
+ {
+ const function_version_info v1 = *(const function_version_info *)p1;
+ const function_version_info v2 = *(const function_version_info *)p2;
+ return - compare_fmv_features (v1.features, v2.features,
+ v1.prio, v2.prio);
+ };
+
+ /* Sort the versions according to descending order of dispatch priority. */
+ qsort (function_versions, actual_versions,
+ sizeof (struct function_version_info), compare_feature_version_info);
+
+ for (unsigned int i = 0; i < actual_versions; ++i)
+ {
+ *empty_bb = add_condition_to_bb (dispatch_decl,
+ function_versions[i].version_decl,
+ &function_versions[i].features,
+ mask_var,
+ *empty_bb);
+ }
+
+ free (function_versions);
+ return 0;
+}
+
+/* Return an identifier for the base assembler name of a versioned function.
+ This is computed by taking the default version's assembler name, and
+ stripping off the ".default" suffix if it's already been appended. */
+
+static tree
+get_suffixed_assembler_name (tree default_decl, const char *suffix)
+{
+ std::string name = IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (default_decl));
+
+ auto size = name.size ();
+ if (size >= 8 && name.compare (size - 8, 8, ".default") == 0)
+ name.resize (size - 8);
+ name += suffix;
+ return get_identifier (name.c_str ());
+}
+
+/* Make the resolver function decl to dispatch the versions of
+ a multi-versioned function, DEFAULT_DECL. IFUNC_ALIAS_DECL is
+ ifunc alias that will point to the created resolver. Create an
+ empty basic block in the resolver and store the pointer in
+ EMPTY_BB. Return the decl of the resolver function. */
+
+static tree
+make_resolver_func (const tree default_decl,
+ const tree ifunc_alias_decl,
+ basic_block *empty_bb)
+{
+ tree decl, type, t;
+
+ /* Create resolver function name based on default_decl. We need to remove an
+ existing ".default" suffix if this has already been appended. */
+ tree decl_name = get_suffixed_assembler_name (default_decl, ".resolver");
+ const char *resolver_name = IDENTIFIER_POINTER (decl_name);
+
+ /* The resolver function should have signature
+ (void *) resolver (uint64_t, void *) */
+ type = build_function_type_list (ptr_type_node,
+ uint64_type_node,
+ ptr_type_node,
+ NULL_TREE);
+
+ decl = build_fn_decl (resolver_name, type);
+ SET_DECL_ASSEMBLER_NAME (decl, decl_name);
+
+ DECL_NAME (decl) = decl_name;
+ TREE_USED (decl) = 1;
+ DECL_ARTIFICIAL (decl) = 1;
+ DECL_IGNORED_P (decl) = 1;
+ TREE_PUBLIC (decl) = 0;
+ DECL_UNINLINABLE (decl) = 1;
+
+ /* Resolver is not external, body is generated. */
+ DECL_EXTERNAL (decl) = 0;
+ DECL_EXTERNAL (ifunc_alias_decl) = 0;
+
+ DECL_CONTEXT (decl) = NULL_TREE;
+ DECL_INITIAL (decl) = make_node (BLOCK);
+ DECL_STATIC_CONSTRUCTOR (decl) = 0;
+
+ if (DECL_COMDAT_GROUP (default_decl)
+ || TREE_PUBLIC (default_decl))
+ {
+ /* In this case, each translation unit with a call to this
+ versioned function will put out a resolver. Ensure it
+ is comdat to keep just one copy. */
+ DECL_COMDAT (decl) = 1;
+ make_decl_one_only (decl, DECL_ASSEMBLER_NAME (decl));
+ }
+ else
+ TREE_PUBLIC (ifunc_alias_decl) = 0;
+
+ /* Build result decl and add to function_decl. */
+ t = build_decl (UNKNOWN_LOCATION, RESULT_DECL, NULL_TREE, ptr_type_node);
+ DECL_CONTEXT (t) = decl;
+ DECL_ARTIFICIAL (t) = 1;
+ DECL_IGNORED_P (t) = 1;
+ DECL_RESULT (decl) = t;
+
+ /* Build parameter decls and add to function_decl. */
+ tree arg1 = build_decl (UNKNOWN_LOCATION, PARM_DECL,
+ get_identifier ("hwcap"),
+ uint64_type_node);
+ tree arg2 = build_decl (UNKNOWN_LOCATION, PARM_DECL,
+ get_identifier ("hwprobe_func"),
+ ptr_type_node);
+ DECL_CONTEXT (arg1) = decl;
+ DECL_CONTEXT (arg2) = decl;
+ DECL_ARTIFICIAL (arg1) = 1;
+ DECL_ARTIFICIAL (arg2) = 1;
+ DECL_IGNORED_P (arg1) = 1;
+ DECL_IGNORED_P (arg2) = 1;
+ DECL_ARG_TYPE (arg1) = uint64_type_node;
+ DECL_ARG_TYPE (arg2) = ptr_type_node;
+ DECL_ARGUMENTS (decl) = arg1;
+ TREE_CHAIN (arg1) = arg2;
+
+ gimplify_function_tree (decl);
+ push_cfun (DECL_STRUCT_FUNCTION (decl));
+ *empty_bb = init_lowered_empty_function (decl, false,
+ profile_count::uninitialized ());
+
+ cgraph_node::add_new_function (decl, true);
+ symtab->call_cgraph_insertion_hooks (cgraph_node::get_create (decl));
+
+ pop_cfun ();
+
+ gcc_assert (ifunc_alias_decl != NULL);
+ /* Mark ifunc_alias_decl as "ifunc" with resolver as resolver_name. */
+ DECL_ATTRIBUTES (ifunc_alias_decl)
+ = make_attribute ("ifunc", resolver_name,
+ DECL_ATTRIBUTES (ifunc_alias_decl));
+
+ /* Create the alias for dispatch to resolver here. */
+ cgraph_node::create_same_body_alias (ifunc_alias_decl, decl);
+ return decl;
+}
+
+/* Implement TARGET_MANGLE_DECL_ASSEMBLER_NAME, to add function multiversioning
+ suffixes. */
+
+tree
+riscv_mangle_decl_assembler_name (tree decl, tree id)
+{
+ /* For function version, add the target suffix to the assembler name. */
+ if (TREE_CODE (decl) == FUNCTION_DECL
+ && DECL_FUNCTION_VERSIONED (decl))
+ {
+ std::string name = IDENTIFIER_POINTER (id) + std::string (".");
+ tree target_attr = lookup_attribute ("target_version",
+ DECL_ATTRIBUTES (decl));
+
+ if (target_attr == NULL_TREE)
+ {
+ name += "default";
+ return get_identifier (name.c_str ());
+ }
+
+ const char *version_string = TREE_STRING_POINTER (TREE_VALUE (TREE_VALUE
+ (target_attr)));
+
+ /* Replace non-alphanumeric characters with underscores as the suffix.
*/
+ for (const char *c = version_string; *c; c++)
+ name += ISALNUM (*c) == 0 ? '_' : *c;
+
+ if (DECL_ASSEMBLER_NAME_SET_P (decl))
+ SET_DECL_RTL (decl, NULL);
+
+ id = get_identifier (name.c_str ());
+ }
+
+ return id;
+}
+
+/* Implement TARGET_GENERATE_VERSION_DISPATCHER_BODY. */
+
+tree
+riscv_generate_version_dispatcher_body (void *node_p)
+{
+ tree resolver_decl;
+ basic_block empty_bb;
+ tree default_ver_decl;
+ struct cgraph_node *versn;
+ struct cgraph_node *node;
+
+ struct cgraph_function_version_info *node_version_info = NULL;
+ struct cgraph_function_version_info *versn_info = NULL;
+
+ node = (cgraph_node *)node_p;
+
+ node_version_info = node->function_version ();
+ gcc_assert (node->dispatcher_function
+ && node_version_info != NULL);
+
+ if (node_version_info->dispatcher_resolver)
+ return node_version_info->dispatcher_resolver;
+
+ /* The first version in the chain corresponds to the default version. */
+ default_ver_decl = node_version_info->next->this_node->decl;
+
+ /* node is going to be an alias, so remove the finalized bit. */
+ node->definition = false;
+
+ resolver_decl = make_resolver_func (default_ver_decl,
+ node->decl, &empty_bb);
+
+ node_version_info->dispatcher_resolver = resolver_decl;
+
+ push_cfun (DECL_STRUCT_FUNCTION (resolver_decl));
+
+ auto_vec<tree, 2> fn_ver_vec;
+
+ for (versn_info = node_version_info->next; versn_info;
+ versn_info = versn_info->next)
+ {
+ versn = versn_info->this_node;
+ /* Check for virtual functions here again, as by this time it should
+ have been determined if this function needs a vtable index or
+ not. This happens for methods in derived classes that override
+ virtual methods in base classes but are not explicitly marked as
+ virtual. */
+ if (DECL_VINDEX (versn->decl))
+ sorry ("virtual function multiversioning not supported");
+
+ fn_ver_vec.safe_push (versn->decl);
+ }
+
+ dispatch_function_versions (resolver_decl, &fn_ver_vec, &empty_bb);
+ cgraph_edge::rebuild_edges ();
+ pop_cfun ();
+
+ /* Fix up symbol names. First we need to obtain the base name, which may
+ have already been mangled. */
+ tree base_name = get_suffixed_assembler_name (default_ver_decl, "");
+
+ /* We need to redo the version mangling on the non-default versions for the
+ target_clones case. Redoing the mangling for the target_version case is
+ redundant but does no harm. We need to skip the default version, because
+ expand_clones will append ".default" later; fortunately that suffix is the
+ one we want anyway. */
+ for (versn_info = node_version_info->next->next; versn_info;
+ versn_info = versn_info->next)
+ {
+ tree version_decl = versn_info->this_node->decl;
+ tree name = riscv_mangle_decl_assembler_name (version_decl,
+ base_name);
+ symtab->change_decl_assembler_name (version_decl, name);
+ }
+
+ /* We also need to use the base name for the ifunc declaration. */
+ symtab->change_decl_assembler_name (node->decl, base_name);
+
+ return resolver_decl;
+}
+
+/* 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. Returns the decl of the dispatcher function. */
+
+tree
+riscv_get_function_versions_dispatcher (void *decl)
+{
+ tree fn = (tree) decl;
+ struct cgraph_node *node = NULL;
+ struct cgraph_node *default_node = NULL;
+ struct cgraph_function_version_info *node_v = NULL;
+ struct cgraph_function_version_info *first_v = NULL;
+
+ tree dispatch_decl = NULL;
+
+ struct cgraph_function_version_info *default_version_info = NULL;
+
+ gcc_assert (fn != NULL && DECL_FUNCTION_VERSIONED (fn));
+
+ node = cgraph_node::get (fn);
+ gcc_assert (node != NULL);
+
+ node_v = node->function_version ();
+ gcc_assert (node_v != NULL);
+
+ if (node_v->dispatcher_resolver != NULL)
+ return node_v->dispatcher_resolver;
+
+ /* Find the default version and make it the first node. */
+ first_v = node_v;
+ /* Go to the beginning of the chain. */
+ while (first_v->prev != NULL)
+ first_v = first_v->prev;
+ default_version_info = first_v;
+
+ while (default_version_info != NULL)
+ {
+ struct riscv_feature_bits res;
+ int priority; /* Unused. */
+ parse_features_for_version (default_version_info->this_node->decl,
+ res, priority);
+ if (res.length == 0)
+ break;
+ default_version_info = default_version_info->next;
+ }
+
+ /* If there is no default node, just return NULL. */
+ if (default_version_info == NULL)
+ return NULL;
+
+ /* Make default info the first node. */
+ if (first_v != default_version_info)
+ {
+ default_version_info->prev->next = default_version_info->next;
+ if (default_version_info->next)
+ default_version_info->next->prev = default_version_info->prev;
+ first_v->prev = default_version_info;
+ default_version_info->next = first_v;
+ default_version_info->prev = NULL;
+ }
+
+ default_node = default_version_info->this_node;
+
+ if (targetm.has_ifunc_p ())
+ {
+ struct cgraph_function_version_info *it_v = NULL;
+ struct cgraph_node *dispatcher_node = NULL;
+ struct cgraph_function_version_info *dispatcher_version_info = NULL;
+
+ /* Right now, the dispatching is done via ifunc. */
+ dispatch_decl = make_dispatcher_decl (default_node->decl);
+ TREE_NOTHROW (dispatch_decl) = TREE_NOTHROW (fn);
+
+ dispatcher_node = cgraph_node::get_create (dispatch_decl);
+ gcc_assert (dispatcher_node != NULL);
+ dispatcher_node->dispatcher_function = 1;
+ dispatcher_version_info
+ = dispatcher_node->insert_new_function_version ();
+ dispatcher_version_info->next = default_version_info;
+ dispatcher_node->definition = 1;
+
+ /* Set the dispatcher for all the versions. */
+ it_v = default_version_info;
+ while (it_v != NULL)
+ {
+ it_v->dispatcher_resolver = dispatch_decl;
+ it_v = it_v->next;
+ }
+ }
+ else
+ {
+ error_at (DECL_SOURCE_LOCATION (default_node->decl),
+ "multiversioning needs %<ifunc%> which is not supported "
+ "on this target");
+ }
+
+ return dispatch_decl;
+}
+
/* On riscv we have an ABI defined safe buffer. This constant is used to
determining the probe offset for alloca. */
@@ -12600,6 +13336,10 @@ riscv_stack_clash_protection_alloca_probe_range (void)
#undef TARGET_OPTION_VALID_ATTRIBUTE_P
#define TARGET_OPTION_VALID_ATTRIBUTE_P riscv_option_valid_attribute_p
+#undef TARGET_OPTION_VALID_VERSION_ATTRIBUTE_P
+#define TARGET_OPTION_VALID_VERSION_ATTRIBUTE_P \
+ riscv_option_valid_version_attribute_p
+
#undef TARGET_LEGITIMIZE_ADDRESS
#define TARGET_LEGITIMIZE_ADDRESS riscv_legitimize_address
@@ -12948,6 +13688,23 @@ 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_OPTION_FUNCTION_VERSIONS
+#define TARGET_OPTION_FUNCTION_VERSIONS riscv_common_function_versions
+
+#undef TARGET_COMPARE_VERSION_PRIORITY
+#define TARGET_COMPARE_VERSION_PRIORITY riscv_compare_version_priority
+
+#undef TARGET_GENERATE_VERSION_DISPATCHER_BODY
+#define TARGET_GENERATE_VERSION_DISPATCHER_BODY \
+ riscv_generate_version_dispatcher_body
+
+#undef TARGET_GET_FUNCTION_VERSIONS_DISPATCHER
+#define TARGET_GET_FUNCTION_VERSIONS_DISPATCHER \
+ riscv_get_function_versions_dispatcher
+
+#undef TARGET_MANGLE_DECL_ASSEMBLER_NAME
+#define TARGET_MANGLE_DECL_ASSEMBLER_NAME riscv_mangle_decl_assembler_name
+
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 ca1b8329cdc..8a8b08b6b51 100644
--- a/gcc/config/riscv/riscv.h
+++ b/gcc/config/riscv/riscv.h
@@ -1298,4 +1298,11 @@ extern void riscv_remove_unneeded_save_restore_calls
(void);
STACK_BOUNDARY / BITS_PER_UNIT) \
: (crtl->outgoing_args_size + STACK_POINTER_OFFSET))
+/* According to the RISC-V C API, the arch string may contains ','. To avoid
+ the conflict with the default separator, we choose '#' as the separator for
+ the target attribute. */
+#define TARGET_CLONES_ATTR_SEPARATOR '#'
+
+#define TARGET_HAS_FMV_TARGET_ATTRIBUTE 0
+
#endif /* ! GCC_RISCV_H */
diff --git a/gcc/config/riscv/riscv.opt b/gcc/config/riscv/riscv.opt
index 6360ed3984d..61def798ca0 100644
--- a/gcc/config/riscv/riscv.opt
+++ b/gcc/config/riscv/riscv.opt
@@ -523,6 +523,9 @@ Mask(XSFVCP) Var(riscv_sifive_subext)
Mask(XSFCEASE) Var(riscv_sifive_subext)
+TargetVariable
+int riscv_fmv_priority = 0
+
Enum
Name(isa_spec_class) Type(enum riscv_isa_spec_class)
Supported ISA specs (for use with the -misa-spec= option):
diff --git a/gcc/defaults.h b/gcc/defaults.h
index ac2d25852ab..918e3ec2f24 100644
--- a/gcc/defaults.h
+++ b/gcc/defaults.h
@@ -874,6 +874,10 @@ see the files COPYING3 and COPYING.RUNTIME respectively.
If not, see
#define TARGET_HAS_FMV_TARGET_ATTRIBUTE 1
#endif
+/* Select a attribute separator for function multiversioning. */
+#ifndef TARGET_CLONES_ATTR_SEPARATOR
+#define TARGET_CLONES_ATTR_SEPARATOR ','
+#endif
/* Select a format to encode pointers in exception handling data. We
prefer those that result in fewer dynamic relocations. Assume no
diff --git a/gcc/multiple_target.cc b/gcc/multiple_target.cc
index 1fdd279da04..c1e358dfc1e 100644
--- a/gcc/multiple_target.cc
+++ b/gcc/multiple_target.cc
@@ -180,7 +180,7 @@ create_dispatcher_calls (struct cgraph_node *node)
}
}
-/* Create string with attributes separated by comma.
+/* Create string with attributes separated by TARGET_CLONES_ATTR_SEPARATOR.
Return number of attributes. */
static int
@@ -194,17 +194,21 @@ get_attr_str (tree arglist, char *attr_str)
{
const char *str = TREE_STRING_POINTER (TREE_VALUE (arg));
size_t len = strlen (str);
- for (const char *p = strchr (str, ','); p; p = strchr (p + 1, ','))
+ for (const char *p = strchr (str, TARGET_CLONES_ATTR_SEPARATOR);
+ p;
+ p = strchr (p + 1, TARGET_CLONES_ATTR_SEPARATOR))
argnum++;
memcpy (attr_str + str_len_sum, str, len);
- attr_str[str_len_sum + len] = TREE_CHAIN (arg) ? ',' : '\0';
+ attr_str[str_len_sum + len]
+ = TREE_CHAIN (arg) ? TARGET_CLONES_ATTR_SEPARATOR : '\0';
str_len_sum += len + 1;
argnum++;
}
return argnum;
}
-/* Return number of attributes separated by comma and put them into ARGS.
+/* Return number of attributes separated by TARGET_CLONES_ATTR_SEPARATOR
+ and put them into ARGS.
If there is no DEFAULT attribute return -1.
If there is an empty string in attribute return -2.
If there are multiple DEFAULT attributes return -3.
@@ -215,9 +219,10 @@ separate_attrs (char *attr_str, char **attrs, int attrnum)
{
int i = 0;
int default_count = 0;
+ static const char separator_str[] = { TARGET_CLONES_ATTR_SEPARATOR, 0 };
- for (char *attr = strtok (attr_str, ",");
- attr != NULL; attr = strtok (NULL, ","))
+ for (char *attr = strtok (attr_str, separator_str);
+ attr != NULL; attr = strtok (NULL, separator_str))
{
if (strcmp (attr, "default") == 0)
{
@@ -305,7 +310,7 @@ static bool
expand_target_clones (struct cgraph_node *node, bool definition)
{
int i;
- /* Parsing target attributes separated by comma. */
+ /* Parsing target attributes separated by TARGET_CLONES_ATTR_SEPARATOR. */
tree attr_target = lookup_attribute ("target_clones",
DECL_ATTRIBUTES (node->decl));
/* No targets specified. */
--
2.45.2