aboutsummaryrefslogtreecommitdiffstats
path: root/gcc-4.6/gcc/mversn-dispatch.c
diff options
context:
space:
mode:
Diffstat (limited to 'gcc-4.6/gcc/mversn-dispatch.c')
-rw-r--r--gcc-4.6/gcc/mversn-dispatch.c735
1 files changed, 726 insertions, 9 deletions
diff --git a/gcc-4.6/gcc/mversn-dispatch.c b/gcc-4.6/gcc/mversn-dispatch.c
index 7c878b2..dc9c1a2 100644
--- a/gcc-4.6/gcc/mversn-dispatch.c
+++ b/gcc-4.6/gcc/mversn-dispatch.c
@@ -135,6 +135,8 @@ along with GCC; see the file COPYING3. If not see
#include "output.h"
#include "vecprim.h"
#include "gimple-pretty-print.h"
+#include "target.h"
+#include "cfgloop.h"
typedef struct cgraph_node* NODEPTR;
DEF_VEC_P (NODEPTR);
@@ -212,8 +214,7 @@ function_args_count (tree fntype)
return num;
}
-/* Return the variable name (global/constructor) to use for the
- version_selector function with name of DECL by appending SUFFIX. */
+/* Return a new name by appending SUFFIX to the DECL name. */
static char *
make_name (tree decl, const char *suffix)
@@ -226,7 +227,8 @@ make_name (tree decl, const char *suffix)
name_len = strlen (name) + strlen (suffix) + 2;
global_var_name = (char *) xmalloc (name_len);
- snprintf (global_var_name, name_len, "%s_%s", name, suffix);
+ /* Use '.' to concatenate names as it is demangler friendly. */
+ snprintf (global_var_name, name_len, "%s.%s", name, suffix);
return global_var_name;
}
@@ -246,9 +248,9 @@ static char*
make_feature_test_global_name (tree decl, bool is_constructor)
{
if (is_constructor)
- return make_name (decl, "version_selector_constructor");
+ return make_name (decl, "version.selector.constructor");
- return make_name (decl, "version_selector_global");
+ return make_name (decl, "version.selector.global");
}
/* This function creates a new VAR_DECL with attributes set
@@ -865,6 +867,9 @@ empty_function_body (tree fndecl)
e = make_edge (new_bb, EXIT_BLOCK_PTR, 0);
gcc_assert (e != NULL);
+ if (dump_file)
+ dump_function_to_file (current_function_decl, dump_file, TDF_BLOCKS);
+
current_function_decl = old_current_function_decl;
pop_cfun ();
return new_bb;
@@ -954,6 +959,12 @@ clone_function (tree orig_fndecl, const char *name_suffix)
cgraph_call_function_insertion_hooks (new_version);
cgraph_mark_needed_node (new_version);
+
+ free_dominance_info (CDI_DOMINATORS);
+ free_dominance_info (CDI_POST_DOMINATORS);
+ calculate_dominance_info (CDI_DOMINATORS);
+ calculate_dominance_info (CDI_POST_DOMINATORS);
+
pop_cfun ();
current_function_decl = old_current_function_decl;
@@ -1034,9 +1045,9 @@ make_specialized_call_to_clone (gimple generic_stmt, int side)
gcc_assert (generic_fndecl != NULL);
if (side == 0)
- new_name = make_name (generic_fndecl, "clone_0");
+ new_name = make_name (generic_fndecl, "clone.0");
else
- new_name = make_name (generic_fndecl, "clone_1");
+ new_name = make_name (generic_fndecl, "clone.1");
slot = htab_find_slot_with_hash (name_decl_htab, new_name,
htab_hash_string (new_name), NO_INSERT);
@@ -1232,8 +1243,8 @@ clone_and_dispatch_function (struct cgraph_node *orig_node, tree *clone_0,
current_function_decl = orig_fndecl;
/* Make 2 clones for true and false function. */
- clone_0_decl = clone_function (orig_fndecl, "clone_0");
- clone_1_decl = clone_function (orig_fndecl, "clone_1");
+ clone_0_decl = clone_function (orig_fndecl, "clone.0");
+ clone_1_decl = clone_function (orig_fndecl, "clone.1");
*clone_0 = clone_0_decl;
*clone_1 = clone_1_decl;
@@ -1764,3 +1775,709 @@ struct gimple_opt_pass pass_tree_convert_builtin_dispatch =
TODO_update_ssa | TODO_verify_ssa
}
};
+
+/* This function generates gimple code in NEW_BB to check if COND_VAR
+ is equal to WHICH_VERSION and return FN_VER pointer if it is equal.
+ The basic block returned is the block where the control flows if
+ the equality is false. */
+
+static basic_block
+make_bb_flow (basic_block new_bb, tree cond_var, tree fn_ver,
+ int which_version, tree bindings)
+{
+ tree result_var;
+ tree convert_expr;
+
+ basic_block bb1, bb2, bb3;
+ edge e12, e23;
+
+ gimple if_else_stmt;
+ gimple if_stmt;
+ gimple return_stmt;
+ gimple_seq gseq = bb_seq (new_bb);
+
+ /* Check if the value of cond_var is equal to which_version. */
+ if_else_stmt = gimple_build_cond (EQ_EXPR, cond_var,
+ build_int_cst (NULL, which_version),
+ NULL_TREE, NULL_TREE);
+
+ mark_symbols_for_renaming (if_else_stmt);
+ gimple_seq_add_stmt (&gseq, if_else_stmt);
+ gimple_set_block (if_else_stmt, bindings);
+ gimple_set_bb (if_else_stmt, new_bb);
+
+ result_var = create_tmp_var (ptr_type_node, NULL);
+ add_referenced_var (result_var);
+
+ convert_expr = build1 (CONVERT_EXPR, ptr_type_node, fn_ver);
+ if_stmt = gimple_build_assign (result_var, convert_expr);
+ mark_symbols_for_renaming (if_stmt);
+ gimple_seq_add_stmt (&gseq, if_stmt);
+ gimple_set_block (if_stmt, bindings);
+
+ return_stmt = gimple_build_return (result_var);
+ mark_symbols_for_renaming (return_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 (if_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, 0);
+
+ return bb3;
+}
+
+/* Given the pointer to the condition function COND_FUNC_ARG, whose return
+ value decides the version that gets executed, and the pointers to the
+ function versions, FN_VER_LIST, this function generates control-flow to
+ return the appropriate function version pointer based on the return value
+ of the conditional function. The condition function is assumed to return
+ values 0, 1, 2, ... */
+
+static gimple_seq
+get_selector_gimple_seq (tree cond_func_arg, tree fn_ver_list, tree default_ver,
+ basic_block new_bb, tree bindings)
+{
+ basic_block final_bb;
+
+ gimple return_stmt, default_stmt;
+ gimple_seq gseq = NULL;
+ gimple_seq gseq_final = NULL;
+ gimple call_cond_stmt;
+
+ tree result_var;
+ tree convert_expr;
+ tree p;
+ tree cond_var;
+
+ int which_version;
+
+ /* Call the condition function once and store the outcome in cond_var. */
+ cond_var = create_tmp_var (integer_type_node, NULL);
+ call_cond_stmt = gimple_build_call (cond_func_arg, 0);
+ gimple_call_set_lhs (call_cond_stmt, cond_var);
+ add_referenced_var (cond_var);
+ mark_symbols_for_renaming (call_cond_stmt);
+
+ gimple_seq_add_stmt (&gseq, call_cond_stmt);
+ gimple_set_block (call_cond_stmt, bindings);
+ gimple_set_bb (call_cond_stmt, new_bb);
+
+ set_bb_seq (new_bb, gseq);
+
+ final_bb = new_bb;
+
+ which_version = 0;
+ for (p = fn_ver_list; p != NULL_TREE; p = TREE_CHAIN (p))
+ {
+ tree ver = TREE_PURPOSE (p);
+ /* Return this version's pointer, VER, if the value returned by the
+ condition funciton is equal to WHICH_VERSION. */
+ final_bb = make_bb_flow (final_bb, cond_var, ver, which_version,
+ bindings);
+ which_version++;
+ }
+
+ result_var = create_tmp_var (ptr_type_node, NULL);
+ add_referenced_var (result_var);
+
+ /* Return the default version function pointer as the default. */
+ convert_expr = build1 (CONVERT_EXPR, ptr_type_node, default_ver);
+ default_stmt = gimple_build_assign (result_var, convert_expr);
+ mark_symbols_for_renaming (default_stmt);
+ gimple_seq_add_stmt (&gseq_final, default_stmt);
+ gimple_set_block (default_stmt, bindings);
+ gimple_set_bb (default_stmt, final_bb);
+
+ return_stmt = gimple_build_return (result_var);
+ mark_symbols_for_renaming (return_stmt);
+ gimple_seq_add_stmt (&gseq_final, return_stmt);
+ gimple_set_bb (return_stmt, final_bb);
+
+ set_bb_seq (final_bb, gseq_final);
+
+ return gseq;
+}
+
+/* Make the ifunc selector function which calls function pointed to by
+ COND_FUNC_ARG and checks the value to return the appropriate function
+ version pointer. */
+
+static tree
+make_selector_function (const char *name, tree cond_func_arg,
+ tree fn_ver_list, tree default_ver)
+{
+ tree decl, type, t;
+ basic_block new_bb;
+ tree old_current_function_decl;
+ tree decl_name;
+
+ /* The selector function should return a (void *). */
+ type = build_function_type_list (ptr_type_node, NULL_TREE);
+
+ decl = build_fn_decl (name, type);
+
+ decl_name = get_identifier (name);
+ SET_DECL_ASSEMBLER_NAME (decl, decl_name);
+ DECL_NAME (decl) = decl_name;
+ gcc_assert (cgraph_node (decl) != NULL);
+
+ TREE_USED (decl) = 1;
+ DECL_ARTIFICIAL (decl) = 1;
+ DECL_IGNORED_P (decl) = 0;
+ TREE_PUBLIC (decl) = 0;
+ DECL_UNINLINABLE (decl) = 1;
+ DECL_EXTERNAL (decl) = 0;
+ DECL_CONTEXT (decl) = NULL_TREE;
+ DECL_INITIAL (decl) = make_node (BLOCK);
+ DECL_STATIC_CONSTRUCTOR (decl) = 0;
+ TREE_READONLY (decl) = 0;
+ DECL_PURE_P (decl) = 0;
+
+ /* Build result decl and add to function_decl. */
+ t = build_decl (UNKNOWN_LOCATION, RESULT_DECL, NULL_TREE, ptr_type_node);
+ DECL_ARTIFICIAL (t) = 1;
+ DECL_IGNORED_P (t) = 1;
+ DECL_RESULT (decl) = t;
+
+ gimplify_function_tree (decl);
+
+ old_current_function_decl = current_function_decl;
+ push_cfun (DECL_STRUCT_FUNCTION (decl));
+ current_function_decl = decl;
+ init_empty_tree_cfg_for_function (DECL_STRUCT_FUNCTION (decl));
+
+ cfun->curr_properties |=
+ (PROP_gimple_lcf | PROP_gimple_leh | PROP_cfg | PROP_referenced_vars |
+ PROP_ssa);
+
+ new_bb = create_empty_bb (ENTRY_BLOCK_PTR);
+ make_edge (ENTRY_BLOCK_PTR, new_bb, EDGE_FALLTHRU);
+ make_edge (new_bb, EXIT_BLOCK_PTR, 0);
+
+ /* This call is very important if this pass runs when the IR is in
+ SSA form. It breaks things in strange ways otherwise. */
+ init_tree_ssa (DECL_STRUCT_FUNCTION (decl));
+ init_ssa_operands ();
+
+ /* Make the body of thr selector function. */
+ get_selector_gimple_seq (cond_func_arg, fn_ver_list, default_ver, new_bb,
+ DECL_INITIAL (decl));
+
+ cgraph_add_new_function (decl, true);
+ cgraph_call_function_insertion_hooks (cgraph_node (decl));
+ cgraph_mark_needed_node (cgraph_node (decl));
+
+ if (dump_file)
+ dump_function_to_file (decl, dump_file, TDF_BLOCKS);
+
+ pop_cfun ();
+ current_function_decl = old_current_function_decl;
+ return decl;
+}
+
+/* Makes a function attribute of the form NAME(ARG_NAME) and chains
+ it to CHAIN. */
+
+static tree
+make_attribute (const char *name, const char *arg_name, tree chain)
+{
+ tree attr_name;
+ tree attr_arg_name;
+ tree attr_args;
+ tree attr;
+
+ attr_name = get_identifier (name);
+ attr_arg_name = build_string (strlen (arg_name), arg_name);
+ attr_args = tree_cons (NULL_TREE, attr_arg_name, NULL_TREE);
+ attr = tree_cons (attr_name, attr_args, chain);
+ return attr;
+}
+
+/* This creates the ifunc function IFUNC_NAME whose selector function is
+ SELECTOR_NAME. */
+
+static tree
+make_ifunc_function (const char* ifunc_name, const char *selector_name,
+ tree fn_type)
+{
+ tree type;
+ tree decl;
+
+ /* The signature of the ifunc function is set to the
+ type of any version. */
+ type = build_function_type (TREE_TYPE (fn_type), TYPE_ARG_TYPES (fn_type));
+ decl = build_fn_decl (ifunc_name, type);
+
+ DECL_CONTEXT (decl) = NULL_TREE;
+ DECL_INITIAL (decl) = error_mark_node;
+
+ /* Set ifunc attribute */
+ DECL_ATTRIBUTES (decl)
+ = make_attribute ("ifunc", selector_name, DECL_ATTRIBUTES (decl));
+
+ assemble_alias (decl, get_identifier (selector_name));
+
+ return decl;
+}
+
+/* Copy the decl attributes from from_decl to to_decl, except
+ DECL_ARTIFICIAL and TREE_PUBLIC. */
+
+static void
+copy_decl_attributes (tree to_decl, tree from_decl)
+{
+ TREE_READONLY (to_decl) = TREE_READONLY (from_decl);
+ TREE_USED (to_decl) = TREE_USED (from_decl);
+ DECL_ARTIFICIAL (to_decl) = 1;
+ DECL_IGNORED_P (to_decl) = DECL_IGNORED_P (from_decl);
+ TREE_PUBLIC (to_decl) = 0;
+ DECL_CONTEXT (to_decl) = DECL_CONTEXT (from_decl);
+ DECL_EXTERNAL (to_decl) = DECL_EXTERNAL (from_decl);
+ DECL_COMDAT (to_decl) = DECL_COMDAT (from_decl);
+ DECL_COMDAT_GROUP (to_decl) = DECL_COMDAT_GROUP (from_decl);
+ DECL_VIRTUAL_P (to_decl) = DECL_VIRTUAL_P (from_decl);
+ DECL_WEAK (to_decl) = DECL_WEAK (from_decl);
+}
+
+/* This function does the mult-version run-time dispatch using IFUNC. Given
+ NUM_VERSIONS versions of a function with the decls in FN_VER_LIST along
+ with a default version in DEFAULT_VER. Also given is a condition function,
+ COND_FUNC_ADDR, whose return value decides the version that gets executed.
+ This function generates the necessary code to dispatch the right function
+ version and returns this a GIMPLE_SEQ. The decls of the ifunc function and
+ the selector function that are created are stored in IFUNC_DECL and
+ SELECTOR_DECL. */
+
+static gimple_seq
+dispatch_using_ifunc (int num_versions, tree orig_func_decl,
+ tree cond_func_addr, tree fn_ver_list,
+ tree default_ver, tree *selector_decl,
+ tree *ifunc_decl)
+{
+ char *selector_name;
+ char *ifunc_name;
+ tree ifunc_function;
+ tree selector_function;
+ tree return_type;
+ VEC (tree, heap) *nargs = NULL;
+ tree arg;
+ gimple ifunc_call_stmt;
+ gimple return_stmt;
+ gimple_seq gseq = NULL;
+
+ gcc_assert (cond_func_addr != NULL
+ && num_versions > 0
+ && orig_func_decl != NULL
+ && fn_ver_list != NULL);
+
+ /* The return type of any function version. */
+ return_type = TREE_TYPE (TREE_TYPE (orig_func_decl));
+
+ nargs = VEC_alloc (tree, heap, 4);
+
+ for (arg = DECL_ARGUMENTS (orig_func_decl);
+ arg; arg = TREE_CHAIN (arg))
+ {
+ VEC_safe_push (tree, heap, nargs, arg);
+ add_referenced_var (arg);
+ }
+
+ /* Assign names to ifunc and ifunc_selector functions. */
+ selector_name = make_name (orig_func_decl, "ifunc.selector");
+ ifunc_name = make_name (orig_func_decl, "ifunc");
+
+ /* Make a selector function which returns the appropriate function
+ version pointer based on the outcome of the condition function
+ execution. */
+ selector_function = make_selector_function (selector_name, cond_func_addr,
+ fn_ver_list, default_ver);
+ *selector_decl = selector_function;
+
+ /* Make a new ifunc function. */
+ ifunc_function = make_ifunc_function (ifunc_name, selector_name,
+ TREE_TYPE (orig_func_decl));
+ *ifunc_decl = ifunc_function;
+
+ /* Make selector and ifunc shadow the attributes of the original function. */
+ copy_decl_attributes (ifunc_function, orig_func_decl);
+ copy_decl_attributes (selector_function, orig_func_decl);
+
+ ifunc_call_stmt = gimple_build_call_vec (ifunc_function, nargs);
+ gimple_seq_add_stmt (&gseq, ifunc_call_stmt);
+
+ /* Make function return the value of it is a non-void type. */
+ if (TREE_CODE (return_type) != VOID_TYPE)
+ {
+ tree lhs_var;
+ tree lhs_var_ssa_name;
+ tree result_decl;
+
+ result_decl = DECL_RESULT (orig_func_decl);
+
+ if (result_decl
+ && aggregate_value_p (result_decl, orig_func_decl)
+ && !TREE_ADDRESSABLE (result_decl))
+ {
+ /* Build a RESULT_DECL rather than a VAR_DECL for this case.
+ See tree-nrv.c: tree_nrv. It checks if the DECL_RESULT and the
+ return value are the same. */
+ lhs_var = build_decl (UNKNOWN_LOCATION, RESULT_DECL, NULL,
+ return_type);
+ DECL_ARTIFICIAL (lhs_var) = 1;
+ DECL_IGNORED_P (lhs_var) = 1;
+ TREE_READONLY (lhs_var) = 0;
+ DECL_EXTERNAL (lhs_var) = 0;
+ TREE_STATIC (lhs_var) = 0;
+ TREE_USED (lhs_var) = 1;
+
+ add_referenced_var (lhs_var);
+ DECL_RESULT (orig_func_decl) = lhs_var;
+ }
+ else if (!TREE_ADDRESSABLE (return_type)
+ && COMPLETE_TYPE_P (return_type))
+ {
+ lhs_var = create_tmp_var (return_type, NULL);
+ add_referenced_var (lhs_var);
+ }
+ else
+ {
+ lhs_var = create_tmp_var_raw (return_type, NULL);
+ TREE_ADDRESSABLE (lhs_var) = 1;
+ gimple_add_tmp_var (lhs_var);
+ add_referenced_var (lhs_var);
+ }
+
+ if (AGGREGATE_TYPE_P (return_type)
+ || TREE_CODE (return_type) == COMPLEX_TYPE)
+ {
+ gimple_call_set_lhs (ifunc_call_stmt, lhs_var);
+ return_stmt = gimple_build_return (lhs_var);
+ }
+ else
+ {
+ lhs_var_ssa_name = make_ssa_name (lhs_var, ifunc_call_stmt);
+ gimple_call_set_lhs (ifunc_call_stmt, lhs_var_ssa_name);
+ return_stmt = gimple_build_return (lhs_var_ssa_name);
+ }
+ }
+ else
+ {
+ return_stmt = gimple_build_return (NULL_TREE);
+ }
+
+ mark_symbols_for_renaming (ifunc_call_stmt);
+ mark_symbols_for_renaming (return_stmt);
+ gimple_seq_add_stmt (&gseq, return_stmt);
+
+ VEC_free (tree, heap, nargs);
+ return gseq;
+}
+
+/* Empty the function body of function fndecl. Retain just one basic block
+ along with the ENTRY and EXIT block. Return the retained basic block. */
+
+static basic_block
+purge_function_body (tree fndecl)
+{
+ basic_block bb, new_bb;
+ edge first_edge, last_edge;
+ tree old_current_function_decl;
+
+ old_current_function_decl = current_function_decl;
+ push_cfun (DECL_STRUCT_FUNCTION (fndecl));
+ current_function_decl = fndecl;
+
+ /* Set new_bb to be the first block after ENTRY_BLOCK_PTR. */
+
+ first_edge = VEC_index (edge, ENTRY_BLOCK_PTR->succs, 0);
+ new_bb = first_edge->dest;
+ gcc_assert (new_bb != NULL);
+
+ for (bb = ENTRY_BLOCK_PTR; bb != NULL;)
+ {
+ edge_iterator ei;
+ edge e;
+ basic_block bb_next;
+ bb_next = bb->next_bb;
+ if (bb == EXIT_BLOCK_PTR)
+ VEC_truncate (edge, EXIT_BLOCK_PTR->preds, 0);
+ else if (bb == ENTRY_BLOCK_PTR)
+ VEC_truncate (edge, ENTRY_BLOCK_PTR->succs, 0);
+ else
+ {
+ remove_phi_nodes (bb);
+ if (bb_seq (bb) != NULL)
+ {
+ gimple_stmt_iterator i;
+ for (i = gsi_start_bb (bb); !gsi_end_p (i);)
+ {
+ gimple stmt = gsi_stmt (i);
+ unlink_stmt_vdef (stmt);
+ reset_debug_uses (stmt);
+ gsi_remove (&i, true);
+ release_defs (stmt);
+ }
+ }
+ FOR_EACH_EDGE (e, ei, bb->succs)
+ {
+ n_edges--;
+ ggc_free (e);
+ }
+ VEC_truncate (edge, bb->succs, 0);
+ VEC_truncate (edge, bb->preds, 0);
+ bb->prev_bb = NULL;
+ bb->next_bb = NULL;
+ if (bb == new_bb)
+ {
+ bb = bb_next;
+ continue;
+ }
+ bb->il.gimple = NULL;
+ SET_BASIC_BLOCK (bb->index, NULL);
+ n_basic_blocks--;
+ }
+ bb = bb_next;
+ }
+
+
+ /* This is to allow iterating over the basic blocks. */
+ new_bb->next_bb = EXIT_BLOCK_PTR;
+ EXIT_BLOCK_PTR->prev_bb = new_bb;
+
+ new_bb->prev_bb = ENTRY_BLOCK_PTR;
+ ENTRY_BLOCK_PTR->next_bb = new_bb;
+
+ gcc_assert (find_edge (new_bb, EXIT_BLOCK_PTR) == NULL);
+ last_edge = make_edge (new_bb, EXIT_BLOCK_PTR, 0);
+ gcc_assert (last_edge);
+
+ gcc_assert (find_edge (ENTRY_BLOCK_PTR, new_bb) == NULL);
+ last_edge = make_edge (ENTRY_BLOCK_PTR, new_bb, EDGE_FALLTHRU);
+ gcc_assert (last_edge);
+
+ free_dominance_info (CDI_DOMINATORS);
+ free_dominance_info (CDI_POST_DOMINATORS);
+ calculate_dominance_info (CDI_DOMINATORS);
+ calculate_dominance_info (CDI_POST_DOMINATORS);
+
+ current_function_decl = old_current_function_decl;
+ pop_cfun ();
+
+ return new_bb;
+}
+
+/* Returns true if function FUNC_DECL contains abnormal goto statements. */
+
+static bool
+function_can_make_abnormal_goto (tree func_decl)
+{
+ basic_block bb;
+ FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (func_decl))
+ {
+ gimple_stmt_iterator gsi;
+ for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ gimple stmt = gsi_stmt (gsi);
+ if (stmt_can_make_abnormal_goto (stmt))
+ return true;
+ }
+ }
+ return false;
+}
+
+/* Has an entry for every cloned function and auxiliaries that have been
+ generated by auto cloning. These cannot be further cloned. */
+
+htab_t cloned_function_decls_htab = NULL;
+
+/* Adds function FUNC_DECL to the cloned_function_decls_htab. */
+
+static void
+mark_function_not_cloneable (tree func_decl)
+{
+ void **slot;
+
+ slot = htab_find_slot_with_hash (cloned_function_decls_htab, func_decl,
+ htab_hash_pointer (func_decl), INSERT);
+ gcc_assert (*slot == NULL);
+ *slot = func_decl;
+}
+
+/* Entry point for the auto clone pass. Calls the target hook to determine if
+ this function must be cloned. */
+
+static unsigned int
+do_auto_clone (void)
+{
+ tree opt_node = NULL_TREE;
+ int num_versions = 0;
+ int i = 0;
+ tree fn_ver_addr_chain = NULL_TREE;
+ tree default_ver = NULL_TREE;
+ tree cond_func_decl = NULL_TREE;
+ tree cond_func_addr;
+ tree default_decl;
+ basic_block empty_bb;
+ gimple_seq gseq = NULL;
+ gimple_stmt_iterator gsi;
+ tree selector_decl;
+ tree ifunc_decl;
+ void **slot;
+ struct cgraph_node *node;
+
+ node = cgraph_node (current_function_decl);
+
+ if (lookup_attribute ("noclone", DECL_ATTRIBUTES (current_function_decl))
+ != NULL)
+ {
+ if (dump_file)
+ fprintf (dump_file, "Not cloning, noclone attribute set\n");
+ return 0;
+ }
+
+ if (lookup_attribute ("target", DECL_ATTRIBUTES (current_function_decl))
+ != NULL)
+ {
+ if (dump_file)
+ fprintf (dump_file, "Not cloning, target attribute set\n");
+ return 0;
+ }
+
+ /* No cloning of constructors and destructors. */
+ if (DECL_STATIC_CONSTRUCTOR (current_function_decl)
+ || DECL_STATIC_DESTRUCTOR (current_function_decl))
+ return 0;
+
+ /* Check if function size is within permissible limits for cloning. */
+ if (node->global.size
+ > PARAM_VALUE (PARAM_MAX_FUNCTION_SIZE_FOR_AUTO_CLONING))
+ {
+ if (dump_file)
+ fprintf (dump_file, "Function size exceeds auto cloning threshold.\n");
+ return 0;
+ }
+
+ if (cloned_function_decls_htab == NULL)
+ cloned_function_decls_htab = htab_create (10, htab_hash_pointer,
+ htab_eq_pointer, NULL);
+
+
+ /* If this function is a clone or other, like the selector function, pass. */
+ slot = htab_find_slot_with_hash (cloned_function_decls_htab,
+ current_function_decl,
+ htab_hash_pointer (current_function_decl),
+ INSERT);
+
+ if (*slot != NULL)
+ return 0;
+
+ if (profile_status == PROFILE_READ
+ && !hot_function_p (cgraph_node (current_function_decl)))
+ return 0;
+
+ /* Ignore functions with abnormal gotos, not correct to clone them. */
+ if (function_can_make_abnormal_goto (current_function_decl))
+ return 0;
+
+ if (!targetm.mversion_function)
+ return 0;
+
+ /* Call the target hook to see if this function needs to be versioned. */
+ num_versions = targetm.mversion_function (current_function_decl, &opt_node,
+ &cond_func_decl);
+
+ /* Nothing more to do if versions are not to be created. */
+ if (num_versions == 0)
+ return 0;
+
+ mark_function_not_cloneable (cond_func_decl);
+ copy_decl_attributes (cond_func_decl, current_function_decl);
+
+ /* Make as many clones as requested. */
+ for (i = 0; i < num_versions; ++i)
+ {
+ tree cloned_decl;
+ char clone_name[100];
+
+ sprintf (clone_name, "autoclone.%d", i);
+ cloned_decl = clone_function (current_function_decl, clone_name);
+ fn_ver_addr_chain = tree_cons (build_fold_addr_expr (cloned_decl),
+ NULL, fn_ver_addr_chain);
+ gcc_assert (cloned_decl != NULL);
+ mark_function_not_cloneable (cloned_decl);
+ DECL_FUNCTION_SPECIFIC_TARGET (cloned_decl)
+ = TREE_PURPOSE (opt_node);
+ opt_node = TREE_CHAIN (opt_node);
+ }
+
+ /* The current function is replaced by an ifunc call to the right version.
+ Make another clone for the default. */
+ default_decl = clone_function (current_function_decl, "autoclone.original");
+ mark_function_not_cloneable (default_decl);
+ /* Empty the body of the current function. */
+ empty_bb = purge_function_body (current_function_decl);
+ default_ver = build_fold_addr_expr (default_decl);
+ cond_func_addr = build_fold_addr_expr (cond_func_decl);
+
+ /* Get the gimple sequence to replace the current function's body with a
+ ifunc dispatch call to the right version. */
+ gseq = dispatch_using_ifunc (num_versions, current_function_decl,
+ cond_func_addr, fn_ver_addr_chain,
+ default_ver, &selector_decl, &ifunc_decl);
+
+ mark_function_not_cloneable (selector_decl);
+ mark_function_not_cloneable (ifunc_decl);
+
+ for (gsi = gsi_start (gseq); !gsi_end_p (gsi); gsi_next (&gsi))
+ gimple_set_bb (gsi_stmt (gsi), empty_bb);
+
+ set_bb_seq (empty_bb, gseq);
+
+ if (dump_file)
+ dump_function_to_file (current_function_decl, dump_file, TDF_BLOCKS);
+
+ update_ssa (TODO_update_ssa_no_phi);
+
+ return 0;
+}
+
+static bool
+gate_auto_clone (void)
+{
+ /* Turned on at -O2 and above. */
+ return optimize >= 2;
+}
+
+struct gimple_opt_pass pass_auto_clone =
+{
+ {
+ GIMPLE_PASS,
+ "auto_clone", /* name */
+ gate_auto_clone, /* gate */
+ do_auto_clone, /* execute */
+ NULL, /* sub */
+ NULL, /* next */
+ 0, /* static_pass_number */
+ TV_MVERSN_DISPATCH, /* tv_id */
+ PROP_cfg, /* properties_required */
+ PROP_cfg, /* properties_provided */
+ 0, /* properties_destroyed */
+ 0, /* todo_flags_start */
+ TODO_dump_func | /* todo_flags_finish */
+ TODO_cleanup_cfg | TODO_dump_cgraph |
+ TODO_update_ssa | TODO_verify_ssa
+ }
+};