Index: gcc/gcc/tree-inline.c |
diff --git a/gcc/gcc/tree-inline.c b/gcc/gcc/tree-inline.c |
index 83d0eeb7d11a3775a7ab03c2b2d104d08ead5512..dbb3d7d8fdaa2240b517745faea3bbccd382f568 100644 |
--- a/gcc/gcc/tree-inline.c |
+++ b/gcc/gcc/tree-inline.c |
@@ -1,5 +1,5 @@ |
/* Tree inlining. |
- Copyright 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 |
+ Copyright 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 |
Free Software Foundation, Inc. |
Contributed by Alexandre Oliva <aoliva@redhat.com> |
@@ -32,7 +32,6 @@ along with GCC; see the file COPYING3. If not see |
#include "params.h" |
#include "input.h" |
#include "insn-config.h" |
-#include "varray.h" |
#include "hashtab.h" |
#include "langhooks.h" |
#include "basic-block.h" |
@@ -65,7 +64,7 @@ along with GCC; see the file COPYING3. If not see |
MODIFY_EXPRs that store to a dedicated returned-value variable. |
The duplicated eh_region info of the copy will later be appended |
to the info for the caller; the eh_region info in copied throwing |
- statements and RESX_EXPRs is adjusted accordingly. |
+ statements and RESX statements are adjusted accordingly. |
Cloning: (only in C++) We have one body for a con/de/structor, and |
multiple function decls, each with a unique parameter list. |
@@ -119,8 +118,7 @@ eni_weights eni_time_weights; |
/* Prototypes. */ |
-static tree declare_return_variable (copy_body_data *, tree, tree, tree *); |
-static bool inlinable_function_p (tree); |
+static tree declare_return_variable (copy_body_data *, tree, tree); |
static void remap_block (tree *, copy_body_data *); |
static void copy_bind_expr (tree *, int *, copy_body_data *); |
static tree mark_local_for_remap_r (tree *, int *, void *); |
@@ -133,6 +131,7 @@ static tree copy_decl_to_var (tree, copy_body_data *); |
static tree copy_result_decl_to_var (tree, copy_body_data *); |
static tree copy_decl_maybe_to_var (tree, copy_body_data *); |
static gimple remap_gimple_stmt (gimple, copy_body_data *); |
+static bool delete_unreachable_blocks_update_callgraph (copy_body_data *id); |
/* Insert a tree->tree mapping for ID. Despite the name suggests |
that the trees should be variables, it is used for more than that. */ |
@@ -148,6 +147,36 @@ insert_decl_map (copy_body_data *id, tree key, tree value) |
*pointer_map_insert (id->decl_map, value) = value; |
} |
+/* Insert a tree->tree mapping for ID. This is only used for |
+ variables. */ |
+ |
+static void |
+insert_debug_decl_map (copy_body_data *id, tree key, tree value) |
+{ |
+ if (!gimple_in_ssa_p (id->src_cfun)) |
+ return; |
+ |
+ if (!MAY_HAVE_DEBUG_STMTS) |
+ return; |
+ |
+ if (!target_for_debug_bind (key)) |
+ return; |
+ |
+ gcc_assert (TREE_CODE (key) == PARM_DECL); |
+ gcc_assert (TREE_CODE (value) == VAR_DECL); |
+ |
+ if (!id->debug_map) |
+ id->debug_map = pointer_map_create (); |
+ |
+ *pointer_map_insert (id->debug_map, key) = value; |
+} |
+ |
+/* If nonzero, we're remapping the contents of inlined debug |
+ statements. If negative, an error has occurred, such as a |
+ reference to a variable that isn't available in the inlined |
+ context. */ |
+static int processing_debug_stmt = 0; |
+ |
/* Construct new SSA name for old NAME. ID is the inline context. */ |
static tree |
@@ -162,12 +191,18 @@ remap_ssa_name (tree name, copy_body_data *id) |
if (n) |
return unshare_expr (*n); |
+ if (processing_debug_stmt) |
+ { |
+ processing_debug_stmt = -1; |
+ return name; |
+ } |
+ |
/* Do not set DEF_STMT yet as statement is not copied yet. We do that |
in copy_bb. */ |
new_tree = remap_decl (SSA_NAME_VAR (name), id); |
/* We might've substituted constant or another SSA_NAME for |
- the variable. |
+ the variable. |
Replace the SSA name representing RESULT_DECL by variable during |
inlining: this saves us from need to introduce PHI node in a case |
@@ -200,7 +235,7 @@ remap_ssa_name (tree name, copy_body_data *id) |
{ |
gimple_stmt_iterator gsi = gsi_last_bb (id->entry_bb); |
gimple init_stmt; |
- |
+ |
init_stmt = gimple_build_assign (new_tree, |
fold_convert (TREE_TYPE (new_tree), |
integer_zero_node)); |
@@ -227,22 +262,26 @@ tree |
remap_decl (tree decl, copy_body_data *id) |
{ |
tree *n; |
- tree fn; |
/* We only remap local variables in the current function. */ |
- fn = id->src_fn; |
/* See if we have remapped this declaration. */ |
n = (tree *) pointer_map_contains (id->decl_map, decl); |
+ if (!n && processing_debug_stmt) |
+ { |
+ processing_debug_stmt = -1; |
+ return decl; |
+ } |
+ |
/* If we didn't already have an equivalent for this declaration, |
create one now. */ |
if (!n) |
{ |
/* Make a copy of the variable or label. */ |
tree t = id->copy_decl (decl, id); |
- |
+ |
/* Remember it, so that if we encounter this local entity again |
we can reuse this copy. Do this early because remap_type may |
need this decl for TYPE_STUB_DECL. */ |
@@ -272,23 +311,16 @@ remap_decl (tree decl, copy_body_data *id) |
&& (TREE_CODE (t) == VAR_DECL |
|| TREE_CODE (t) == RESULT_DECL || TREE_CODE (t) == PARM_DECL)) |
{ |
- tree def = gimple_default_def (id->src_cfun, decl); |
get_var_ann (t); |
- if (TREE_CODE (decl) != PARM_DECL && def) |
- { |
- tree map = remap_ssa_name (def, id); |
- /* Watch out RESULT_DECLs whose SSA names map directly |
- to them. */ |
- if (TREE_CODE (map) == SSA_NAME |
- && gimple_nop_p (SSA_NAME_DEF_STMT (map))) |
- set_default_def (t, map); |
- } |
add_referenced_var (t); |
} |
return t; |
} |
- return unshare_expr (*n); |
+ if (id->do_not_unshare) |
+ return *n; |
+ else |
+ return unshare_expr (*n); |
} |
static tree |
@@ -304,6 +336,10 @@ remap_type_1 (tree type, copy_body_data *id) |
new_tree = build_pointer_type_for_mode (remap_type (TREE_TYPE (type), id), |
TYPE_MODE (type), |
TYPE_REF_CAN_ALIAS_ALL (type)); |
+ if (TYPE_ATTRIBUTES (type) || TYPE_QUALS (type)) |
+ new_tree = build_type_attribute_qual_variant (new_tree, |
+ TYPE_ATTRIBUTES (type), |
+ TYPE_QUALS (type)); |
insert_decl_map (id, type, new_tree); |
return new_tree; |
} |
@@ -312,6 +348,10 @@ remap_type_1 (tree type, copy_body_data *id) |
new_tree = build_reference_type_for_mode (remap_type (TREE_TYPE (type), id), |
TYPE_MODE (type), |
TYPE_REF_CAN_ALIAS_ALL (type)); |
+ if (TYPE_ATTRIBUTES (type) || TYPE_QUALS (type)) |
+ new_tree = build_type_attribute_qual_variant (new_tree, |
+ TYPE_ATTRIBUTES (type), |
+ TYPE_QUALS (type)); |
insert_decl_map (id, type, new_tree); |
return new_tree; |
} |
@@ -447,7 +487,7 @@ remapped_type (tree type, copy_body_data *id) |
/* The type only needs remapping if it's variably modified. */ |
/* Decide if DECL can be put into BLOCK_NONLOCAL_VARs. */ |
- |
+ |
static bool |
can_be_nonlocal (tree decl, copy_body_data *id) |
{ |
@@ -493,18 +533,18 @@ remap_decls (tree decls, VEC(tree,gc) **nonlocalized_list, copy_body_data *id) |
for (old_var = decls; old_var; old_var = TREE_CHAIN (old_var)) |
{ |
tree new_var; |
- tree origin_var = DECL_ORIGIN (old_var); |
if (can_be_nonlocal (old_var, id)) |
{ |
if (TREE_CODE (old_var) == VAR_DECL |
+ && ! DECL_EXTERNAL (old_var) |
&& (var_ann (old_var) || !gimple_in_ssa_p (cfun))) |
cfun->local_decls = tree_cons (NULL_TREE, old_var, |
cfun->local_decls); |
if ((!optimize || debug_info_level > DINFO_LEVEL_TERSE) |
&& !DECL_IGNORED_P (old_var) |
&& nonlocalized_list) |
- VEC_safe_push (tree, gc, *nonlocalized_list, origin_var); |
+ VEC_safe_push (tree, gc, *nonlocalized_list, old_var); |
continue; |
} |
@@ -514,7 +554,7 @@ remap_decls (tree decls, VEC(tree,gc) **nonlocalized_list, copy_body_data *id) |
/* If we didn't remap this variable, we can't mess with its |
TREE_CHAIN. If we remapped this variable to the return slot, it's |
already declared somewhere else, so don't declare it here. */ |
- |
+ |
if (new_var == id->retvar) |
; |
else if (!new_var) |
@@ -522,7 +562,7 @@ remap_decls (tree decls, VEC(tree,gc) **nonlocalized_list, copy_body_data *id) |
if ((!optimize || debug_info_level > DINFO_LEVEL_TERSE) |
&& !DECL_IGNORED_P (old_var) |
&& nonlocalized_list) |
- VEC_safe_push (tree, gc, *nonlocalized_list, origin_var); |
+ VEC_safe_push (tree, gc, *nonlocalized_list, old_var); |
} |
else |
{ |
@@ -543,7 +583,6 @@ remap_block (tree *block, copy_body_data *id) |
{ |
tree old_block; |
tree new_block; |
- tree fn; |
/* Make the new block. */ |
old_block = *block; |
@@ -560,8 +599,6 @@ remap_block (tree *block, copy_body_data *id) |
&BLOCK_NONLOCALIZED_VARS (new_block), |
id); |
- fn = id->dst_fn; |
- |
if (id->transform_lang_insert_block) |
id->transform_lang_insert_block (new_block); |
@@ -598,16 +635,23 @@ copy_statement_list (tree *tp) |
new_tree = alloc_stmt_list (); |
ni = tsi_start (new_tree); |
oi = tsi_start (*tp); |
+ TREE_TYPE (new_tree) = TREE_TYPE (*tp); |
*tp = new_tree; |
for (; !tsi_end_p (oi); tsi_next (&oi)) |
- tsi_link_after (&ni, tsi_stmt (oi), TSI_NEW_STMT); |
+ { |
+ tree stmt = tsi_stmt (oi); |
+ if (TREE_CODE (stmt) == STATEMENT_LIST) |
+ copy_statement_list (&stmt); |
+ tsi_link_after (&ni, stmt, TSI_CONTINUE_LINKING); |
+ } |
} |
static void |
copy_bind_expr (tree *tp, int *walk_subtrees, copy_body_data *id) |
{ |
tree block = BIND_EXPR_BLOCK (*tp); |
+ tree t; |
/* Copy (and replace) the statement. */ |
copy_tree_r (tp, walk_subtrees, NULL); |
if (block) |
@@ -617,9 +661,21 @@ copy_bind_expr (tree *tp, int *walk_subtrees, copy_body_data *id) |
} |
if (BIND_EXPR_VARS (*tp)) |
- /* This will remap a lot of the same decls again, but this should be |
- harmless. */ |
- BIND_EXPR_VARS (*tp) = remap_decls (BIND_EXPR_VARS (*tp), NULL, id); |
+ { |
+ /* This will remap a lot of the same decls again, but this should be |
+ harmless. */ |
+ BIND_EXPR_VARS (*tp) = remap_decls (BIND_EXPR_VARS (*tp), NULL, id); |
+ |
+ /* Also copy value-expressions. */ |
+ for (t = BIND_EXPR_VARS (*tp); t; t = TREE_CHAIN (t)) |
+ if (TREE_CODE (t) == VAR_DECL |
+ && DECL_HAS_VALUE_EXPR_P (t)) |
+ { |
+ tree tem = DECL_VALUE_EXPR (t); |
+ walk_tree (&tem, copy_tree_body_r, id, NULL); |
+ SET_DECL_VALUE_EXPR (t, tem); |
+ } |
+ } |
} |
@@ -705,6 +761,13 @@ remap_gimple_op_r (tree *tp, int *walk_subtrees, void *data) |
gcc_assert (new_decl); |
/* Replace this variable with the copy. */ |
STRIP_TYPE_NOPS (new_decl); |
+ /* ??? The C++ frontend uses void * pointer zero to initialize |
+ any other type. This confuses the middle-end type verification. |
+ As cloned bodies do not go through gimplification again the fixup |
+ there doesn't trigger. */ |
+ if (TREE_CODE (new_decl) == INTEGER_CST |
+ && !useless_type_conversion_p (TREE_TYPE (*tp), TREE_TYPE (new_decl))) |
+ new_decl = fold_convert (TREE_TYPE (*tp), new_decl); |
*tp = new_decl; |
*walk_subtrees = 0; |
} |
@@ -769,7 +832,8 @@ remap_gimple_op_r (tree *tp, int *walk_subtrees, void *data) |
{ |
if (TREE_CODE (new_tree) == ADDR_EXPR) |
{ |
- *tp = fold_indirect_ref_1 (type, new_tree); |
+ *tp = fold_indirect_ref_1 (EXPR_LOCATION (new_tree), |
+ type, new_tree); |
/* ??? We should either assert here or build |
a VIEW_CONVERT_EXPR instead of blindly leaking |
incompatible types to our IL. */ |
@@ -796,7 +860,8 @@ remap_gimple_op_r (tree *tp, int *walk_subtrees, void *data) |
vars. If not referenced from types only. */ |
if (gimple_in_ssa_p (cfun) |
&& TREE_CODE (*tp) == VAR_DECL |
- && id->remapping_type_depth == 0) |
+ && id->remapping_type_depth == 0 |
+ && !processing_debug_stmt) |
add_referenced_var (*tp); |
/* We should never have TREE_BLOCK set on non-statements. */ |
@@ -911,7 +976,8 @@ copy_tree_body_r (tree *tp, int *walk_subtrees, void *data) |
} |
else if (TREE_CODE (*tp) == STATEMENT_LIST) |
copy_statement_list (tp); |
- else if (TREE_CODE (*tp) == SAVE_EXPR) |
+ else if (TREE_CODE (*tp) == SAVE_EXPR |
+ || TREE_CODE (*tp) == TARGET_EXPR) |
remap_save_expr (tp, id->decl_map, walk_subtrees); |
else if (TREE_CODE (*tp) == LABEL_DECL |
&& (! DECL_CONTEXT (*tp) |
@@ -967,7 +1033,7 @@ copy_tree_body_r (tree *tp, int *walk_subtrees, void *data) |
STRIP_TYPE_NOPS (value); |
if (TREE_CONSTANT (value) || TREE_READONLY (value)) |
{ |
- *tp = build_empty_stmt (); |
+ *tp = build_empty_stmt (EXPR_LOCATION (*tp)); |
return copy_tree_body_r (tp, walk_subtrees, data); |
} |
} |
@@ -991,14 +1057,18 @@ copy_tree_body_r (tree *tp, int *walk_subtrees, void *data) |
but we absolutely rely on that. As fold_indirect_ref |
does other useful transformations, try that first, though. */ |
tree type = TREE_TYPE (TREE_TYPE (*n)); |
- new_tree = unshare_expr (*n); |
+ if (id->do_not_unshare) |
+ new_tree = *n; |
+ else |
+ new_tree = unshare_expr (*n); |
old = *tp; |
*tp = gimple_fold_indirect_ref (new_tree); |
if (! *tp) |
{ |
if (TREE_CODE (new_tree) == ADDR_EXPR) |
{ |
- *tp = fold_indirect_ref_1 (type, new_tree); |
+ *tp = fold_indirect_ref_1 (EXPR_LOCATION (new_tree), |
+ type, new_tree); |
/* ??? We should either assert here or build |
a VIEW_CONVERT_EXPR instead of blindly leaking |
incompatible types to our IL. */ |
@@ -1022,18 +1092,19 @@ copy_tree_body_r (tree *tp, int *walk_subtrees, void *data) |
copy_tree_r (tp, walk_subtrees, NULL); |
/* Global variables we haven't seen yet needs to go into referenced |
- vars. If not referenced from types only. */ |
+ vars. If not referenced from types or debug stmts only. */ |
if (gimple_in_ssa_p (cfun) |
&& TREE_CODE (*tp) == VAR_DECL |
- && id->remapping_type_depth == 0) |
+ && id->remapping_type_depth == 0 |
+ && !processing_debug_stmt) |
add_referenced_var (*tp); |
- |
+ |
/* If EXPR has block defined, map it to newly constructed block. |
When inlining we want EXPRs without block appear in the block |
- of function call. */ |
+ of function call if we are not remapping a type. */ |
if (EXPR_P (*tp)) |
{ |
- new_block = id->block; |
+ new_block = id->remapping_type_depth == 0 ? id->block : NULL; |
if (TREE_BLOCK (*tp)) |
{ |
tree *n; |
@@ -1045,12 +1116,6 @@ copy_tree_body_r (tree *tp, int *walk_subtrees, void *data) |
TREE_BLOCK (*tp) = new_block; |
} |
- if (TREE_CODE (*tp) == RESX_EXPR && id->eh_region_offset) |
- TREE_OPERAND (*tp, 0) = |
- build_int_cst (NULL_TREE, |
- id->eh_region_offset |
- + TREE_INT_CST_LOW (TREE_OPERAND (*tp, 0))); |
- |
if (TREE_CODE (*tp) != OMP_CLAUSE) |
TREE_TYPE (*tp) = remap_type (TREE_TYPE (*tp), id); |
@@ -1090,6 +1155,35 @@ copy_tree_body_r (tree *tp, int *walk_subtrees, void *data) |
return NULL_TREE; |
} |
+/* Helper for remap_gimple_stmt. Given an EH region number for the |
+ source function, map that to the duplicate EH region number in |
+ the destination function. */ |
+ |
+static int |
+remap_eh_region_nr (int old_nr, copy_body_data *id) |
+{ |
+ eh_region old_r, new_r; |
+ void **slot; |
+ |
+ old_r = get_eh_region_from_number_fn (id->src_cfun, old_nr); |
+ slot = pointer_map_contains (id->eh_map, old_r); |
+ new_r = (eh_region) *slot; |
+ |
+ return new_r->index; |
+} |
+ |
+/* Similar, but operate on INTEGER_CSTs. */ |
+ |
+static tree |
+remap_eh_region_tree_nr (tree old_t_nr, copy_body_data *id) |
+{ |
+ int old_nr, new_nr; |
+ |
+ old_nr = tree_low_cst (old_t_nr, 0); |
+ new_nr = remap_eh_region_nr (old_nr, id); |
+ |
+ return build_int_cst (NULL, new_nr); |
+} |
/* Helper for copy_bb. Remap statement STMT using the inlining |
information in ID. Return the new statement copy. */ |
@@ -1156,7 +1250,7 @@ remap_gimple_stmt (gimple stmt, copy_body_data *id) |
case GIMPLE_TRY: |
s1 = remap_gimple_seq (gimple_try_eval (stmt), id); |
s2 = remap_gimple_seq (gimple_try_cleanup (stmt), id); |
- copy = gimple_build_try (s1, s2, gimple_try_kind (stmt)); |
+ copy = gimple_build_try (s1, s2, gimple_try_kind (stmt)); |
break; |
case GIMPLE_WITH_CLEANUP_EXPR: |
@@ -1271,8 +1365,67 @@ remap_gimple_stmt (gimple stmt, copy_body_data *id) |
} |
} |
+ if (gimple_debug_bind_p (stmt)) |
+ { |
+ copy = gimple_build_debug_bind (gimple_debug_bind_get_var (stmt), |
+ gimple_debug_bind_get_value (stmt), |
+ stmt); |
+ VEC_safe_push (gimple, heap, id->debug_stmts, copy); |
+ return copy; |
+ } |
+ |
/* Create a new deep copy of the statement. */ |
copy = gimple_copy (stmt); |
+ |
+ /* Remap the region numbers for __builtin_eh_{pointer,filter}, |
+ RESX and EH_DISPATCH. */ |
+ if (id->eh_map) |
+ switch (gimple_code (copy)) |
+ { |
+ case GIMPLE_CALL: |
+ { |
+ tree r, fndecl = gimple_call_fndecl (copy); |
+ if (fndecl && DECL_BUILT_IN_CLASS (fndecl) == BUILT_IN_NORMAL) |
+ switch (DECL_FUNCTION_CODE (fndecl)) |
+ { |
+ case BUILT_IN_EH_COPY_VALUES: |
+ r = gimple_call_arg (copy, 1); |
+ r = remap_eh_region_tree_nr (r, id); |
+ gimple_call_set_arg (copy, 1, r); |
+ /* FALLTHRU */ |
+ |
+ case BUILT_IN_EH_POINTER: |
+ case BUILT_IN_EH_FILTER: |
+ r = gimple_call_arg (copy, 0); |
+ r = remap_eh_region_tree_nr (r, id); |
+ gimple_call_set_arg (copy, 0, r); |
+ break; |
+ |
+ default: |
+ break; |
+ } |
+ } |
+ break; |
+ |
+ case GIMPLE_RESX: |
+ { |
+ int r = gimple_resx_region (copy); |
+ r = remap_eh_region_nr (r, id); |
+ gimple_resx_set_region (copy, r); |
+ } |
+ break; |
+ |
+ case GIMPLE_EH_DISPATCH: |
+ { |
+ int r = gimple_eh_dispatch_region (copy); |
+ r = remap_eh_region_nr (r, id); |
+ gimple_eh_dispatch_set_region (copy, r); |
+ } |
+ break; |
+ |
+ default: |
+ break; |
+ } |
} |
/* If STMT has a block defined, map it to the newly constructed |
@@ -1289,20 +1442,25 @@ remap_gimple_stmt (gimple stmt, copy_body_data *id) |
gimple_set_block (copy, new_block); |
+ if (gimple_debug_bind_p (copy)) |
+ return copy; |
+ |
/* Remap all the operands in COPY. */ |
memset (&wi, 0, sizeof (wi)); |
wi.info = id; |
if (skip_first) |
walk_tree (gimple_op_ptr (copy, 1), remap_gimple_op_r, &wi, NULL); |
else |
- walk_gimple_op (copy, remap_gimple_op_r, &wi); |
+ walk_gimple_op (copy, remap_gimple_op_r, &wi); |
- /* We have to handle EH region remapping of GIMPLE_RESX specially because |
- the region number is not an operand. */ |
- if (gimple_code (stmt) == GIMPLE_RESX && id->eh_region_offset) |
+ /* Clear the copied virtual operands. We are not remapping them here |
+ but are going to recreate them from scratch. */ |
+ if (gimple_has_mem_ops (copy)) |
{ |
- gimple_resx_set_region (copy, gimple_resx_region (stmt) + id->eh_region_offset); |
+ gimple_set_vdef (copy, NULL_TREE); |
+ gimple_set_vuse (copy, NULL_TREE); |
} |
+ |
return copy; |
} |
@@ -1317,6 +1475,7 @@ copy_bb (copy_body_data *id, basic_block bb, int frequency_scale, |
gimple_stmt_iterator gsi, copy_gsi, seq_gsi; |
basic_block copy_basic_block; |
tree decl; |
+ gcov_type freq; |
/* create_basic_block() will append every new block to |
basic_block_info automatically. */ |
@@ -1326,11 +1485,12 @@ copy_bb (copy_body_data *id, basic_block bb, int frequency_scale, |
/* We are going to rebuild frequencies from scratch. These values |
have just small importance to drive canonicalize_loop_headers. */ |
- copy_basic_block->frequency = ((gcov_type)bb->frequency |
- * frequency_scale / REG_BR_PROB_BASE); |
+ freq = ((gcov_type)bb->frequency * frequency_scale / REG_BR_PROB_BASE); |
- if (copy_basic_block->frequency > BB_FREQ_MAX) |
- copy_basic_block->frequency = BB_FREQ_MAX; |
+ /* We recompute frequencies after inlining, so this is quite safe. */ |
+ if (freq > BB_FREQ_MAX) |
+ freq = BB_FREQ_MAX; |
+ copy_basic_block->frequency = freq; |
copy_gsi = gsi_start_bb (copy_basic_block); |
@@ -1378,6 +1538,8 @@ copy_bb (copy_body_data *id, basic_block bb, int frequency_scale, |
need to process all of them. */ |
do |
{ |
+ tree fn; |
+ |
stmt = gsi_stmt (copy_gsi); |
if (is_gimple_call (stmt) |
&& gimple_call_va_arg_pack_p (stmt) |
@@ -1424,7 +1586,6 @@ copy_bb (copy_body_data *id, basic_block bb, int frequency_scale, |
gimple_call_set_lhs (new_call, gimple_call_lhs (stmt)); |
gsi_replace (©_gsi, new_call, false); |
- gimple_set_bb (stmt, NULL); |
stmt = new_call; |
} |
else if (is_gimple_call (stmt) |
@@ -1466,88 +1627,110 @@ copy_bb (copy_body_data *id, basic_block bb, int frequency_scale, |
callgraph edges and update or duplicate them. */ |
if (is_gimple_call (stmt)) |
{ |
- struct cgraph_node *node; |
struct cgraph_edge *edge; |
int flags; |
switch (id->transform_call_graph_edges) |
{ |
- case CB_CGE_DUPLICATE: |
- edge = cgraph_edge (id->src_node, orig_stmt); |
- if (edge) |
- cgraph_clone_edge (edge, id->dst_node, stmt, |
- REG_BR_PROB_BASE, 1, |
- edge->frequency, true); |
- break; |
- |
- case CB_CGE_MOVE_CLONES: |
- for (node = id->dst_node->next_clone; |
- node; |
- node = node->next_clone) |
- { |
- edge = cgraph_edge (node, orig_stmt); |
- if (edge) |
- cgraph_set_call_stmt (edge, stmt); |
- } |
- /* FALLTHRU */ |
- |
- case CB_CGE_MOVE: |
- edge = cgraph_edge (id->dst_node, orig_stmt); |
- if (edge) |
- cgraph_set_call_stmt (edge, stmt); |
- break; |
+ case CB_CGE_DUPLICATE: |
+ edge = cgraph_edge (id->src_node, orig_stmt); |
+ if (edge) |
+ { |
+ int edge_freq = edge->frequency; |
+ edge = cgraph_clone_edge (edge, id->dst_node, stmt, |
+ gimple_uid (stmt), |
+ REG_BR_PROB_BASE, CGRAPH_FREQ_BASE, |
+ edge->frequency, true); |
+ /* We could also just rescale the frequency, but |
+ doing so would introduce roundoff errors and make |
+ verifier unhappy. */ |
+ edge->frequency |
+ = compute_call_stmt_bb_frequency (id->dst_node->decl, |
+ copy_basic_block); |
+ if (dump_file |
+ && profile_status_for_function (cfun) != PROFILE_ABSENT |
+ && (edge_freq > edge->frequency + 10 |
+ || edge_freq < edge->frequency - 10)) |
+ { |
+ fprintf (dump_file, "Edge frequency estimated by " |
+ "cgraph %i diverge from inliner's estimate %i\n", |
+ edge_freq, |
+ edge->frequency); |
+ fprintf (dump_file, |
+ "Orig bb: %i, orig bb freq %i, new bb freq %i\n", |
+ bb->index, |
+ bb->frequency, |
+ copy_basic_block->frequency); |
+ } |
+ stmt = cgraph_redirect_edge_call_stmt_to_callee (edge); |
+ } |
+ break; |
+ |
+ case CB_CGE_MOVE_CLONES: |
+ cgraph_set_call_stmt_including_clones (id->dst_node, |
+ orig_stmt, stmt); |
+ edge = cgraph_edge (id->dst_node, stmt); |
+ break; |
+ |
+ case CB_CGE_MOVE: |
+ edge = cgraph_edge (id->dst_node, orig_stmt); |
+ if (edge) |
+ cgraph_set_call_stmt (edge, stmt); |
+ break; |
+ |
+ default: |
+ gcc_unreachable (); |
+ } |
- default: |
- gcc_unreachable (); |
+ /* Constant propagation on argument done during inlining |
+ may create new direct call. Produce an edge for it. */ |
+ if ((!edge |
+ || (edge->indirect_call |
+ && id->transform_call_graph_edges == CB_CGE_MOVE_CLONES)) |
+ && is_gimple_call (stmt) |
+ && (fn = gimple_call_fndecl (stmt)) != NULL) |
+ { |
+ struct cgraph_node *dest = cgraph_node (fn); |
+ |
+ /* We have missing edge in the callgraph. This can happen |
+ when previous inlining turned an indirect call into a |
+ direct call by constant propagating arguments or we are |
+ producing dead clone (for further clonning). In all |
+ other cases we hit a bug (incorrect node sharing is the |
+ most common reason for missing edges). */ |
+ gcc_assert (dest->needed || !dest->analyzed |
+ || !id->src_node->analyzed); |
+ if (id->transform_call_graph_edges == CB_CGE_MOVE_CLONES) |
+ cgraph_create_edge_including_clones |
+ (id->dst_node, dest, orig_stmt, stmt, bb->count, |
+ compute_call_stmt_bb_frequency (id->dst_node->decl, |
+ copy_basic_block), |
+ bb->loop_depth, CIF_ORIGINALLY_INDIRECT_CALL); |
+ else |
+ cgraph_create_edge (id->dst_node, dest, stmt, |
+ bb->count, |
+ compute_call_stmt_bb_frequency |
+ (id->dst_node->decl, copy_basic_block), |
+ bb->loop_depth)->inline_failed |
+ = CIF_ORIGINALLY_INDIRECT_CALL; |
+ if (dump_file) |
+ { |
+ fprintf (dump_file, "Created new direct edge to %s", |
+ cgraph_node_name (dest)); |
+ } |
} |
flags = gimple_call_flags (stmt); |
- |
if (flags & ECF_MAY_BE_ALLOCA) |
cfun->calls_alloca = true; |
if (flags & ECF_RETURNS_TWICE) |
cfun->calls_setjmp = true; |
} |
- /* If you think we can abort here, you are wrong. |
- There is no region 0 in gimple. */ |
- gcc_assert (lookup_stmt_eh_region_fn (id->src_cfun, orig_stmt) != 0); |
- |
- if (stmt_could_throw_p (stmt) |
- /* When we are cloning for inlining, we are supposed to |
- construct a clone that calls precisely the same functions |
- as original. However IPA optimizers might've proved |
- earlier some function calls as non-trapping that might |
- render some basic blocks dead that might become |
- unreachable. |
- |
- We can't update SSA with unreachable blocks in CFG and thus |
- we prevent the scenario by preserving even the "dead" eh |
- edges until the point they are later removed by |
- fixup_cfg pass. */ |
- || (id->transform_call_graph_edges == CB_CGE_MOVE_CLONES |
- && lookup_stmt_eh_region_fn (id->src_cfun, orig_stmt) > 0)) |
- { |
- int region = lookup_stmt_eh_region_fn (id->src_cfun, orig_stmt); |
- |
- /* Add an entry for the copied tree in the EH hashtable. |
- When cloning or versioning, use the hashtable in |
- cfun, and just copy the EH number. When inlining, use the |
- hashtable in the caller, and adjust the region number. */ |
- if (region > 0) |
- add_stmt_to_eh_region (stmt, region + id->eh_region_offset); |
- |
- /* If this tree doesn't have a region associated with it, |
- and there is a "current region," |
- then associate this tree with the current region |
- and add edges associated with this region. */ |
- if (lookup_stmt_eh_region_fn (id->src_cfun, orig_stmt) <= 0 |
- && id->eh_region > 0 |
- && stmt_could_throw_p (stmt)) |
- add_stmt_to_eh_region (stmt, id->eh_region); |
- } |
+ maybe_duplicate_eh_stmt_fn (cfun, stmt, id->src_cfun, orig_stmt, |
+ id->eh_map, id->eh_lp_nr); |
- if (gimple_in_ssa_p (cfun)) |
+ if (gimple_in_ssa_p (cfun) && !is_gimple_debug (stmt)) |
{ |
ssa_op_iter i; |
tree def; |
@@ -1600,8 +1783,6 @@ update_ssa_across_abnormal_edges (basic_block bb, basic_block ret_bb, |
gimple phi; |
gimple_stmt_iterator si; |
- gcc_assert (e->flags & EDGE_ABNORMAL); |
- |
if (!nonlocal_goto) |
gcc_assert (e->flags & EDGE_EH); |
@@ -1617,7 +1798,8 @@ update_ssa_across_abnormal_edges (basic_block bb, basic_block ret_bb, |
/* There shouldn't be any PHI nodes in the ENTRY_BLOCK. */ |
gcc_assert (!e->dest->aux); |
- gcc_assert (SSA_NAME_OCCURS_IN_ABNORMAL_PHI (PHI_RESULT (phi))); |
+ gcc_assert ((e->flags & EDGE_EH) |
+ || SSA_NAME_OCCURS_IN_ABNORMAL_PHI (PHI_RESULT (phi))); |
if (!is_gimple_reg (PHI_RESULT (phi))) |
{ |
@@ -1639,9 +1821,10 @@ update_ssa_across_abnormal_edges (basic_block bb, basic_block ret_bb, |
/* Copy edges from BB into its copy constructed earlier, scale profile |
accordingly. Edges will be taken care of later. Assume aux |
- pointers to point to the copies of each BB. */ |
+ pointers to point to the copies of each BB. Return true if any |
+ debug stmts are left after a statement that must end the basic block. */ |
-static void |
+static bool |
copy_edges_for_bb (basic_block bb, gcov_type count_scale, basic_block ret_bb) |
{ |
basic_block new_bb = (basic_block) bb->aux; |
@@ -1649,6 +1832,7 @@ copy_edges_for_bb (basic_block bb, gcov_type count_scale, basic_block ret_bb) |
edge old_edge; |
gimple_stmt_iterator si; |
int flags; |
+ bool need_debug_cleanup = false; |
/* Use the indices from the original blocks to create edges for the |
new ones. */ |
@@ -1669,7 +1853,7 @@ copy_edges_for_bb (basic_block bb, gcov_type count_scale, basic_block ret_bb) |
} |
if (bb->index == ENTRY_BLOCK || bb->index == EXIT_BLOCK) |
- return; |
+ return false; |
for (si = gsi_start_bb (new_bb); !gsi_end_p (si);) |
{ |
@@ -1677,9 +1861,12 @@ copy_edges_for_bb (basic_block bb, gcov_type count_scale, basic_block ret_bb) |
bool can_throw, nonlocal_goto; |
copy_stmt = gsi_stmt (si); |
- update_stmt (copy_stmt); |
- if (gimple_in_ssa_p (cfun)) |
- mark_symbols_for_renaming (copy_stmt); |
+ if (!is_gimple_debug (copy_stmt)) |
+ { |
+ update_stmt (copy_stmt); |
+ if (gimple_in_ssa_p (cfun)) |
+ mark_symbols_for_renaming (copy_stmt); |
+ } |
/* Do this before the possible split_block. */ |
gsi_next (&si); |
@@ -1701,6 +1888,13 @@ copy_edges_for_bb (basic_block bb, gcov_type count_scale, basic_block ret_bb) |
if (can_throw || nonlocal_goto) |
{ |
if (!gsi_end_p (si)) |
+ { |
+ while (!gsi_end_p (si) && is_gimple_debug (gsi_stmt (si))) |
+ gsi_next (&si); |
+ if (gsi_end_p (si)) |
+ need_debug_cleanup = true; |
+ } |
+ if (!gsi_end_p (si)) |
/* Note that bb's predecessor edges aren't necessarily |
right at this point; split_block doesn't care. */ |
{ |
@@ -1712,7 +1906,9 @@ copy_edges_for_bb (basic_block bb, gcov_type count_scale, basic_block ret_bb) |
} |
} |
- if (can_throw) |
+ if (gimple_code (copy_stmt) == GIMPLE_EH_DISPATCH) |
+ make_eh_dispatch_edges (copy_stmt); |
+ else if (can_throw) |
make_eh_edges (copy_stmt); |
if (nonlocal_goto) |
@@ -1723,6 +1919,7 @@ copy_edges_for_bb (basic_block bb, gcov_type count_scale, basic_block ret_bb) |
update_ssa_across_abnormal_edges (gimple_bb (copy_stmt), ret_bb, |
can_throw, nonlocal_goto); |
} |
+ return need_debug_cleanup; |
} |
/* Copy the PHIs. All blocks and edges are copied, some blocks |
@@ -1773,7 +1970,8 @@ copy_phis_for_bb (basic_block bb, copy_body_data *id) |
new_arg = force_gimple_operand (new_arg, &stmts, true, NULL); |
gsi_insert_seq_on_edge_immediate (new_edge, stmts); |
} |
- add_phi_arg (new_phi, new_arg, new_edge); |
+ add_phi_arg (new_phi, new_arg, new_edge, |
+ gimple_phi_arg_location_from_edge (phi, old_edge)); |
} |
} |
} |
@@ -1792,24 +1990,16 @@ remap_decl_1 (tree decl, void *data) |
NEW_FNDECL to be build. CALLEE_FNDECL is the original */ |
static void |
-initialize_cfun (tree new_fndecl, tree callee_fndecl, gcov_type count, |
- int frequency) |
+initialize_cfun (tree new_fndecl, tree callee_fndecl, gcov_type count) |
{ |
struct function *src_cfun = DECL_STRUCT_FUNCTION (callee_fndecl); |
- gcov_type count_scale, frequency_scale; |
+ gcov_type count_scale; |
if (ENTRY_BLOCK_PTR_FOR_FUNCTION (src_cfun)->count) |
count_scale = (REG_BR_PROB_BASE * count |
/ ENTRY_BLOCK_PTR_FOR_FUNCTION (src_cfun)->count); |
else |
- count_scale = 1; |
- |
- if (ENTRY_BLOCK_PTR_FOR_FUNCTION (src_cfun)->frequency) |
- frequency_scale = (REG_BR_PROB_BASE * frequency |
- / |
- ENTRY_BLOCK_PTR_FOR_FUNCTION (src_cfun)->frequency); |
- else |
- frequency_scale = count_scale; |
+ count_scale = REG_BR_PROB_BASE; |
/* Register specific tree functions. */ |
gimple_register_cfg_hooks (); |
@@ -1829,9 +2019,6 @@ initialize_cfun (tree new_fndecl, tree callee_fndecl, gcov_type count, |
cfun->function_end_locus = src_cfun->function_end_locus; |
cfun->curr_properties = src_cfun->curr_properties; |
cfun->last_verified = src_cfun->last_verified; |
- if (src_cfun->ipa_transforms_to_apply) |
- cfun->ipa_transforms_to_apply = VEC_copy (ipa_opt_pass, heap, |
- src_cfun->ipa_transforms_to_apply); |
cfun->va_list_gpr_size = src_cfun->va_list_gpr_size; |
cfun->va_list_fpr_size = src_cfun->va_list_fpr_size; |
cfun->function_frequency = src_cfun->function_frequency; |
@@ -1845,18 +2032,17 @@ initialize_cfun (tree new_fndecl, tree callee_fndecl, gcov_type count, |
init_empty_tree_cfg (); |
+ profile_status_for_function (cfun) = profile_status_for_function (src_cfun); |
ENTRY_BLOCK_PTR->count = |
(ENTRY_BLOCK_PTR_FOR_FUNCTION (src_cfun)->count * count_scale / |
REG_BR_PROB_BASE); |
- ENTRY_BLOCK_PTR->frequency = |
- (ENTRY_BLOCK_PTR_FOR_FUNCTION (src_cfun)->frequency * |
- frequency_scale / REG_BR_PROB_BASE); |
+ ENTRY_BLOCK_PTR->frequency |
+ = ENTRY_BLOCK_PTR_FOR_FUNCTION (src_cfun)->frequency; |
EXIT_BLOCK_PTR->count = |
(EXIT_BLOCK_PTR_FOR_FUNCTION (src_cfun)->count * count_scale / |
REG_BR_PROB_BASE); |
EXIT_BLOCK_PTR->frequency = |
- (EXIT_BLOCK_PTR_FOR_FUNCTION (src_cfun)->frequency * |
- frequency_scale / REG_BR_PROB_BASE); |
+ EXIT_BLOCK_PTR_FOR_FUNCTION (src_cfun)->frequency; |
if (src_cfun->eh) |
init_eh_for_function (); |
@@ -1869,11 +2055,68 @@ initialize_cfun (tree new_fndecl, tree callee_fndecl, gcov_type count, |
pop_cfun (); |
} |
+/* Helper function for copy_cfg_body. Move debug stmts from the end |
+ of NEW_BB to the beginning of successor basic blocks when needed. If the |
+ successor has multiple predecessors, reset them, otherwise keep |
+ their value. */ |
+ |
+static void |
+maybe_move_debug_stmts_to_successors (copy_body_data *id, basic_block new_bb) |
+{ |
+ edge e; |
+ edge_iterator ei; |
+ gimple_stmt_iterator si = gsi_last_nondebug_bb (new_bb); |
+ |
+ if (gsi_end_p (si) |
+ || gsi_one_before_end_p (si) |
+ || !(stmt_can_throw_internal (gsi_stmt (si)) |
+ || stmt_can_make_abnormal_goto (gsi_stmt (si)))) |
+ return; |
+ |
+ FOR_EACH_EDGE (e, ei, new_bb->succs) |
+ { |
+ gimple_stmt_iterator ssi = gsi_last_bb (new_bb); |
+ gimple_stmt_iterator dsi = gsi_after_labels (e->dest); |
+ while (is_gimple_debug (gsi_stmt (ssi))) |
+ { |
+ gimple stmt = gsi_stmt (ssi), new_stmt; |
+ tree var; |
+ tree value; |
+ |
+ /* For the last edge move the debug stmts instead of copying |
+ them. */ |
+ if (ei_one_before_end_p (ei)) |
+ { |
+ si = ssi; |
+ gsi_prev (&ssi); |
+ if (!single_pred_p (e->dest)) |
+ gimple_debug_bind_reset_value (stmt); |
+ gsi_remove (&si, false); |
+ gsi_insert_before (&dsi, stmt, GSI_SAME_STMT); |
+ continue; |
+ } |
+ |
+ var = gimple_debug_bind_get_var (stmt); |
+ if (single_pred_p (e->dest)) |
+ { |
+ value = gimple_debug_bind_get_value (stmt); |
+ value = unshare_expr (value); |
+ } |
+ else |
+ value = NULL_TREE; |
+ new_stmt = gimple_build_debug_bind (var, value, stmt); |
+ gsi_insert_before (&dsi, new_stmt, GSI_SAME_STMT); |
+ VEC_safe_push (gimple, heap, id->debug_stmts, new_stmt); |
+ gsi_prev (&ssi); |
+ } |
+ } |
+} |
+ |
/* Make a copy of the body of FN so that it can be inserted inline in |
another function. Walks FN via CFG, returns new fndecl. */ |
static tree |
-copy_cfg_body (copy_body_data * id, gcov_type count, int frequency, |
+copy_cfg_body (copy_body_data * id, gcov_type count, int frequency_scale, |
basic_block entry_block_map, basic_block exit_block_map) |
{ |
tree callee_fndecl = id->src_fn; |
@@ -1882,21 +2125,15 @@ copy_cfg_body (copy_body_data * id, gcov_type count, int frequency, |
struct function *cfun_to_copy; |
basic_block bb; |
tree new_fndecl = NULL; |
- gcov_type count_scale, frequency_scale; |
+ bool need_debug_cleanup = false; |
+ gcov_type count_scale; |
int last; |
if (ENTRY_BLOCK_PTR_FOR_FUNCTION (src_cfun)->count) |
count_scale = (REG_BR_PROB_BASE * count |
/ ENTRY_BLOCK_PTR_FOR_FUNCTION (src_cfun)->count); |
else |
- count_scale = 1; |
- |
- if (ENTRY_BLOCK_PTR_FOR_FUNCTION (src_cfun)->frequency) |
- frequency_scale = (REG_BR_PROB_BASE * frequency |
- / |
- ENTRY_BLOCK_PTR_FOR_FUNCTION (src_cfun)->frequency); |
- else |
- frequency_scale = count_scale; |
+ count_scale = REG_BR_PROB_BASE; |
/* Register specific tree functions. */ |
gimple_register_cfg_hooks (); |
@@ -1914,11 +2151,8 @@ copy_cfg_body (copy_body_data * id, gcov_type count, int frequency, |
/* Duplicate any exception-handling regions. */ |
if (cfun->eh) |
- { |
- id->eh_region_offset |
- = duplicate_eh_regions (cfun_to_copy, remap_decl_1, id, |
- 0, id->eh_region); |
- } |
+ id->eh_map = duplicate_eh_regions (cfun_to_copy, NULL, id->eh_lp_nr, |
+ remap_decl_1, id); |
/* Use aux pointers to map the original blocks to copy. */ |
FOR_EACH_BB_FN (bb, cfun_to_copy) |
@@ -1932,7 +2166,7 @@ copy_cfg_body (copy_body_data * id, gcov_type count, int frequency, |
/* Now that we've duplicated the blocks, duplicate their edges. */ |
FOR_ALL_BB_FN (bb, cfun_to_copy) |
- copy_edges_for_bb (bb, count_scale, exit_block_map); |
+ need_debug_cleanup |= copy_edges_for_bb (bb, count_scale, exit_block_map); |
if (gimple_in_ssa_p (cfun)) |
FOR_ALL_BB_FN (bb, cfun_to_copy) |
@@ -1940,6 +2174,10 @@ copy_cfg_body (copy_body_data * id, gcov_type count, int frequency, |
FOR_ALL_BB_FN (bb, cfun_to_copy) |
{ |
+ if (need_debug_cleanup |
+ && bb->index != ENTRY_BLOCK |
+ && bb->index != EXIT_BLOCK) |
+ maybe_move_debug_stmts_to_successors (id, (basic_block) bb->aux); |
((basic_block)bb->aux)->aux = NULL; |
bb->aux = NULL; |
} |
@@ -1947,15 +2185,124 @@ copy_cfg_body (copy_body_data * id, gcov_type count, int frequency, |
/* Zero out AUX fields of newly created block during EH edge |
insertion. */ |
for (; last < last_basic_block; last++) |
- BASIC_BLOCK (last)->aux = NULL; |
+ { |
+ if (need_debug_cleanup) |
+ maybe_move_debug_stmts_to_successors (id, BASIC_BLOCK (last)); |
+ BASIC_BLOCK (last)->aux = NULL; |
+ } |
entry_block_map->aux = NULL; |
exit_block_map->aux = NULL; |
+ if (id->eh_map) |
+ { |
+ pointer_map_destroy (id->eh_map); |
+ id->eh_map = NULL; |
+ } |
+ |
return new_fndecl; |
} |
+/* Copy the debug STMT using ID. We deal with these statements in a |
+ special way: if any variable in their VALUE expression wasn't |
+ remapped yet, we won't remap it, because that would get decl uids |
+ out of sync, causing codegen differences between -g and -g0. If |
+ this arises, we drop the VALUE expression altogether. */ |
+ |
+static void |
+copy_debug_stmt (gimple stmt, copy_body_data *id) |
+{ |
+ tree t, *n; |
+ struct walk_stmt_info wi; |
+ |
+ t = id->block; |
+ if (gimple_block (stmt)) |
+ { |
+ tree *n; |
+ n = (tree *) pointer_map_contains (id->decl_map, gimple_block (stmt)); |
+ if (n) |
+ t = *n; |
+ } |
+ gimple_set_block (stmt, t); |
+ |
+ /* Remap all the operands in COPY. */ |
+ memset (&wi, 0, sizeof (wi)); |
+ wi.info = id; |
+ |
+ processing_debug_stmt = 1; |
+ |
+ t = gimple_debug_bind_get_var (stmt); |
+ |
+ if (TREE_CODE (t) == PARM_DECL && id->debug_map |
+ && (n = (tree *) pointer_map_contains (id->debug_map, t))) |
+ { |
+ gcc_assert (TREE_CODE (*n) == VAR_DECL); |
+ t = *n; |
+ } |
+ else if (TREE_CODE (t) == VAR_DECL |
+ && !TREE_STATIC (t) |
+ && gimple_in_ssa_p (cfun) |
+ && !pointer_map_contains (id->decl_map, t) |
+ && !var_ann (t)) |
+ /* T is a non-localized variable. */; |
+ else |
+ walk_tree (&t, remap_gimple_op_r, &wi, NULL); |
+ |
+ gimple_debug_bind_set_var (stmt, t); |
+ |
+ if (gimple_debug_bind_has_value_p (stmt)) |
+ walk_tree (gimple_debug_bind_get_value_ptr (stmt), |
+ remap_gimple_op_r, &wi, NULL); |
+ |
+ /* Punt if any decl couldn't be remapped. */ |
+ if (processing_debug_stmt < 0) |
+ gimple_debug_bind_reset_value (stmt); |
+ |
+ processing_debug_stmt = 0; |
+ |
+ update_stmt (stmt); |
+ if (gimple_in_ssa_p (cfun)) |
+ mark_symbols_for_renaming (stmt); |
+} |
+ |
+/* Process deferred debug stmts. In order to give values better odds |
+ of being successfully remapped, we delay the processing of debug |
+ stmts until all other stmts that might require remapping are |
+ processed. */ |
+ |
+static void |
+copy_debug_stmts (copy_body_data *id) |
+{ |
+ size_t i; |
+ gimple stmt; |
+ |
+ if (!id->debug_stmts) |
+ return; |
+ |
+ for (i = 0; VEC_iterate (gimple, id->debug_stmts, i, stmt); i++) |
+ copy_debug_stmt (stmt, id); |
+ |
+ VEC_free (gimple, heap, id->debug_stmts); |
+} |
+ |
+/* Make a copy of the body of SRC_FN so that it can be inserted inline in |
+ another function. */ |
+ |
static tree |
-copy_body (copy_body_data *id, gcov_type count, int frequency, |
+copy_tree_body (copy_body_data *id) |
+{ |
+ tree fndecl = id->src_fn; |
+ tree body = DECL_SAVED_TREE (fndecl); |
+ |
+ walk_tree (&body, copy_tree_body_r, id, NULL); |
+ |
+ return body; |
+} |
+ |
+/* Make a copy of the body of FN so that it can be inserted inline in |
+ another function. */ |
+ |
+static tree |
+copy_body (copy_body_data *id, gcov_type count, int frequency_scale, |
basic_block entry_block_map, basic_block exit_block_map) |
{ |
tree fndecl = id->src_fn; |
@@ -1963,7 +2310,8 @@ copy_body (copy_body_data *id, gcov_type count, int frequency, |
/* If this body has a CFG, walk CFG and copy. */ |
gcc_assert (ENTRY_BLOCK_PTR_FOR_FUNCTION (DECL_STRUCT_FUNCTION (fndecl))); |
- body = copy_cfg_body (id, count, frequency, entry_block_map, exit_block_map); |
+ body = copy_cfg_body (id, count, frequency_scale, entry_block_map, exit_block_map); |
+ copy_debug_stmts (id); |
return body; |
} |
@@ -1984,8 +2332,51 @@ self_inlining_addr_expr (tree value, tree fn) |
return var && auto_var_in_fn_p (var, fn); |
} |
+/* Append to BB a debug annotation that binds VAR to VALUE, inheriting |
+ lexical block and line number information from base_stmt, if given, |
+ or from the last stmt of the block otherwise. */ |
+ |
+static gimple |
+insert_init_debug_bind (copy_body_data *id, |
+ basic_block bb, tree var, tree value, |
+ gimple base_stmt) |
+{ |
+ gimple note; |
+ gimple_stmt_iterator gsi; |
+ tree tracked_var; |
+ |
+ if (!gimple_in_ssa_p (id->src_cfun)) |
+ return NULL; |
+ |
+ if (!MAY_HAVE_DEBUG_STMTS) |
+ return NULL; |
+ |
+ tracked_var = target_for_debug_bind (var); |
+ if (!tracked_var) |
+ return NULL; |
+ |
+ if (bb) |
+ { |
+ gsi = gsi_last_bb (bb); |
+ if (!base_stmt && !gsi_end_p (gsi)) |
+ base_stmt = gsi_stmt (gsi); |
+ } |
+ |
+ note = gimple_build_debug_bind (tracked_var, value, base_stmt); |
+ |
+ if (bb) |
+ { |
+ if (!gsi_end_p (gsi)) |
+ gsi_insert_after (&gsi, note, GSI_SAME_STMT); |
+ else |
+ gsi_insert_before (&gsi, note, GSI_SAME_STMT); |
+ } |
+ |
+ return note; |
+} |
+ |
static void |
-insert_init_stmt (basic_block bb, gimple init_stmt) |
+insert_init_stmt (copy_body_data *id, basic_block bb, gimple init_stmt) |
{ |
/* If VAR represents a zero-sized variable, it's possible that the |
assignment statement may result in no gimple statements. */ |
@@ -1997,7 +2388,8 @@ insert_init_stmt (basic_block bb, gimple init_stmt) |
from a rhs with a conversion. Handle that here by forcing the |
rhs into a temporary. gimple_regimplify_operands is not |
prepared to do this for us. */ |
- if (!is_gimple_reg (gimple_assign_lhs (init_stmt)) |
+ if (!is_gimple_debug (init_stmt) |
+ && !is_gimple_reg (gimple_assign_lhs (init_stmt)) |
&& is_gimple_reg_type (TREE_TYPE (gimple_assign_lhs (init_stmt))) |
&& gimple_assign_rhs_class (init_stmt) == GIMPLE_UNARY_RHS) |
{ |
@@ -2012,6 +2404,18 @@ insert_init_stmt (basic_block bb, gimple init_stmt) |
gsi_insert_after (&si, init_stmt, GSI_NEW_STMT); |
gimple_regimplify_operands (init_stmt, &si); |
mark_symbols_for_renaming (init_stmt); |
+ |
+ if (!is_gimple_debug (init_stmt) && MAY_HAVE_DEBUG_STMTS) |
+ { |
+ tree var, def = gimple_assign_lhs (init_stmt); |
+ |
+ if (TREE_CODE (def) == SSA_NAME) |
+ var = SSA_NAME_VAR (def); |
+ else |
+ var = def; |
+ |
+ insert_init_debug_bind (id, bb, var, def, init_stmt); |
+ } |
} |
} |
@@ -2042,9 +2446,29 @@ setup_one_parameter (copy_body_data *id, tree p, tree value, tree fn, |
rhs = fold_build1 (VIEW_CONVERT_EXPR, TREE_TYPE (p), value); |
} |
+ /* Make an equivalent VAR_DECL. Note that we must NOT remap the type |
+ here since the type of this decl must be visible to the calling |
+ function. */ |
+ var = copy_decl_to_var (p, id); |
+ |
+ /* We're actually using the newly-created var. */ |
+ if (gimple_in_ssa_p (cfun) && TREE_CODE (var) == VAR_DECL) |
+ { |
+ get_var_ann (var); |
+ add_referenced_var (var); |
+ } |
+ |
+ /* Declare this new variable. */ |
+ TREE_CHAIN (var) = *vars; |
+ *vars = var; |
+ |
+ /* Make gimplifier happy about this variable. */ |
+ DECL_SEEN_IN_BIND_EXPR_P (var) = 1; |
+ |
/* If the parameter is never assigned to, has no SSA_NAMEs created, |
- we may not need to create a new variable here at all. Instead, we may |
- be able to just use the argument value. */ |
+ we would not need to create a new variable here at all, if it |
+ weren't for debug info. Still, we can just use the argument |
+ value. */ |
if (TREE_READONLY (p) |
&& !TREE_ADDRESSABLE (p) |
&& value && !TREE_SIDE_EFFECTS (value) |
@@ -2065,32 +2489,16 @@ setup_one_parameter (copy_body_data *id, tree p, tree value, tree fn, |
&& ! self_inlining_addr_expr (value, fn)) |
{ |
insert_decl_map (id, p, value); |
- return NULL; |
+ insert_debug_decl_map (id, p, var); |
+ return insert_init_debug_bind (id, bb, var, value, NULL); |
} |
} |
- /* Make an equivalent VAR_DECL. Note that we must NOT remap the type |
- here since the type of this decl must be visible to the calling |
- function. */ |
- var = copy_decl_to_var (p, id); |
- if (gimple_in_ssa_p (cfun) && TREE_CODE (var) == VAR_DECL) |
- { |
- get_var_ann (var); |
- add_referenced_var (var); |
- } |
- |
/* Register the VAR_DECL as the equivalent for the PARM_DECL; |
that way, when the PARM_DECL is encountered, it will be |
automatically replaced by the VAR_DECL. */ |
insert_decl_map (id, p, var); |
- /* Declare this new variable. */ |
- TREE_CHAIN (var) = *vars; |
- *vars = var; |
- |
- /* Make gimplifier happy about this variable. */ |
- DECL_SEEN_IN_BIND_EXPR_P (var) = 1; |
- |
/* Even if P was TREE_READONLY, the new VAR should not be. |
In the original code, we would have constructed a |
temporary, and then the function body would have never |
@@ -2112,15 +2520,7 @@ setup_one_parameter (copy_body_data *id, tree p, tree value, tree fn, |
Do replacement at -O0 for const arguments replaced by constant. |
This is important for builtin_constant_p and other construct requiring |
- constant argument to be visible in inlined function body. |
- |
- FIXME: This usually kills the last connection in between inlined |
- function parameter and the actual value in debug info. Can we do |
- better here? If we just inserted the statement, copy propagation |
- would kill it anyway as it always did in older versions of GCC. |
- |
- We might want to introduce a notion that single SSA_NAME might |
- represent multiple variables for purposes of debugging. */ |
+ constant argument to be visible in inlined function body. */ |
if (gimple_in_ssa_p (cfun) && rhs && def && is_gimple_reg (p) |
&& (optimize |
|| (TREE_READONLY (p) |
@@ -2130,7 +2530,7 @@ setup_one_parameter (copy_body_data *id, tree p, tree value, tree fn, |
&& !SSA_NAME_OCCURS_IN_ABNORMAL_PHI (def)) |
{ |
insert_decl_map (id, def, rhs); |
- return NULL; |
+ return insert_init_debug_bind (id, bb, var, rhs, NULL); |
} |
/* If the value of argument is never used, don't care about initializing |
@@ -2138,7 +2538,7 @@ setup_one_parameter (copy_body_data *id, tree p, tree value, tree fn, |
if (optimize && gimple_in_ssa_p (cfun) && !def && is_gimple_reg (p)) |
{ |
gcc_assert (!value || !TREE_SIDE_EFFECTS (value)); |
- return NULL; |
+ return insert_init_debug_bind (id, bb, var, rhs, NULL); |
} |
/* Initialize this VAR_DECL from the equivalent argument. Convert |
@@ -2148,7 +2548,7 @@ setup_one_parameter (copy_body_data *id, tree p, tree value, tree fn, |
if (rhs == error_mark_node) |
{ |
insert_decl_map (id, p, var); |
- return NULL; |
+ return insert_init_debug_bind (id, bb, var, rhs, NULL); |
} |
STRIP_USELESS_TYPE_CONVERSION (rhs); |
@@ -2166,7 +2566,7 @@ setup_one_parameter (copy_body_data *id, tree p, tree value, tree fn, |
init_stmt = gimple_build_assign (var, rhs); |
if (bb && init_stmt) |
- insert_init_stmt (bb, init_stmt); |
+ insert_init_stmt (id, bb, init_stmt); |
} |
return init_stmt; |
} |
@@ -2220,28 +2620,30 @@ initialize_inlined_parameters (copy_body_data *id, gimple stmt, |
is set only for CALL_EXPR_RETURN_SLOT_OPT. MODIFY_DEST, if non-null, |
was the LHS of the MODIFY_EXPR to which this call is the RHS. |
- The return value is a (possibly null) value that is the result of the |
- function as seen by the callee. *USE_P is a (possibly null) value that |
- holds the result as seen by the caller. */ |
+ The return value is a (possibly null) value that holds the result |
+ as seen by the caller. */ |
static tree |
-declare_return_variable (copy_body_data *id, tree return_slot, tree modify_dest, |
- tree *use_p) |
+declare_return_variable (copy_body_data *id, tree return_slot, tree modify_dest) |
{ |
tree callee = id->src_fn; |
tree caller = id->dst_fn; |
tree result = DECL_RESULT (callee); |
tree callee_type = TREE_TYPE (result); |
- tree caller_type = TREE_TYPE (TREE_TYPE (callee)); |
+ tree caller_type; |
tree var, use; |
+ /* Handle type-mismatches in the function declaration return type |
+ vs. the call expression. */ |
+ if (modify_dest) |
+ caller_type = TREE_TYPE (modify_dest); |
+ else |
+ caller_type = TREE_TYPE (TREE_TYPE (callee)); |
+ |
/* We don't need to do anything for functions that don't return |
anything. */ |
if (!result || VOID_TYPE_P (callee_type)) |
- { |
- *use_p = NULL_TREE; |
- return NULL_TREE; |
- } |
+ return NULL_TREE; |
/* If there was a return slot, then the return value is the |
dereferenced address of that object. */ |
@@ -2256,7 +2658,7 @@ declare_return_variable (copy_body_data *id, tree return_slot, tree modify_dest, |
STRIP_USELESS_TYPE_CONVERSION (return_slot_addr); |
/* We are going to construct *&return_slot and we can't do that |
- for variables believed to be not addressable. |
+ for variables believed to be not addressable. |
FIXME: This check possibly can match, because values returned |
via return slot optimization are not believed to have address |
@@ -2374,11 +2776,14 @@ declare_return_variable (copy_body_data *id, tree return_slot, tree modify_dest, |
use = var; |
if (!useless_type_conversion_p (caller_type, TREE_TYPE (var))) |
use = fold_convert (caller_type, var); |
- |
+ |
STRIP_USELESS_TYPE_CONVERSION (use); |
if (DECL_BY_REFERENCE (result)) |
- var = build_fold_addr_expr (var); |
+ { |
+ TREE_ADDRESSABLE (var) = 1; |
+ var = build_fold_addr_expr (var); |
+ } |
done: |
/* Register the VAR_DECL as the equivalent for the RESULT_DECL; that |
@@ -2389,63 +2794,82 @@ declare_return_variable (copy_body_data *id, tree return_slot, tree modify_dest, |
/* Remember this so we can ignore it in remap_decls. */ |
id->retvar = var; |
- *use_p = use; |
- return var; |
+ return use; |
} |
-/* Returns nonzero if a function can be inlined as a tree. */ |
+/* Callback through walk_tree. Determine if a DECL_INITIAL makes reference |
+ to a local label. */ |
-bool |
-tree_inlinable_function_p (tree fn) |
+static tree |
+has_label_address_in_static_1 (tree *nodep, int *walk_subtrees, void *fnp) |
{ |
- return inlinable_function_p (fn); |
-} |
+ tree node = *nodep; |
+ tree fn = (tree) fnp; |
-static const char *inline_forbidden_reason; |
+ if (TREE_CODE (node) == LABEL_DECL && DECL_CONTEXT (node) == fn) |
+ return node; |
-/* A callback for walk_gimple_seq to handle tree operands. Returns |
- NULL_TREE if a function can be inlined, otherwise sets the reason |
- why not and returns a tree representing the offending operand. */ |
+ if (TYPE_P (node)) |
+ *walk_subtrees = 0; |
-static tree |
-inline_forbidden_p_op (tree *nodep, int *walk_subtrees ATTRIBUTE_UNUSED, |
- void *fnp ATTRIBUTE_UNUSED) |
+ return NULL_TREE; |
+} |
+ |
+/* Determine if the function can be copied. If so return NULL. If |
+ not return a string describng the reason for failure. */ |
+ |
+static const char * |
+copy_forbidden (struct function *fun, tree fndecl) |
{ |
- tree node = *nodep; |
- tree t; |
+ const char *reason = fun->cannot_be_copied_reason; |
+ tree step; |
- if (TREE_CODE (node) == RECORD_TYPE || TREE_CODE (node) == UNION_TYPE) |
+ /* Only examine the function once. */ |
+ if (fun->cannot_be_copied_set) |
+ return reason; |
+ |
+ /* We cannot copy a function that receives a non-local goto |
+ because we cannot remap the destination label used in the |
+ function that is performing the non-local goto. */ |
+ /* ??? Actually, this should be possible, if we work at it. |
+ No doubt there's just a handful of places that simply |
+ assume it doesn't happen and don't substitute properly. */ |
+ if (fun->has_nonlocal_label) |
{ |
- /* We cannot inline a function of the form |
- |
- void F (int i) { struct S { int ar[i]; } s; } |
+ reason = G_("function %q+F can never be copied " |
+ "because it receives a non-local goto"); |
+ goto fail; |
+ } |
- Attempting to do so produces a catch-22. |
- If walk_tree examines the TYPE_FIELDS chain of RECORD_TYPE/ |
- UNION_TYPE nodes, then it goes into infinite recursion on a |
- structure containing a pointer to its own type. If it doesn't, |
- then the type node for S doesn't get adjusted properly when |
- F is inlined. |
+ for (step = fun->local_decls; step; step = TREE_CHAIN (step)) |
+ { |
+ tree decl = TREE_VALUE (step); |
- ??? This is likely no longer true, but it's too late in the 4.0 |
- cycle to try to find out. This should be checked for 4.1. */ |
- for (t = TYPE_FIELDS (node); t; t = TREE_CHAIN (t)) |
- if (variably_modified_type_p (TREE_TYPE (t), NULL)) |
- { |
- inline_forbidden_reason |
- = G_("function %q+F can never be inlined " |
- "because it uses variable sized variables"); |
- return node; |
- } |
+ if (TREE_CODE (decl) == VAR_DECL |
+ && TREE_STATIC (decl) |
+ && !DECL_EXTERNAL (decl) |
+ && DECL_INITIAL (decl) |
+ && walk_tree_without_duplicates (&DECL_INITIAL (decl), |
+ has_label_address_in_static_1, |
+ fndecl)) |
+ { |
+ reason = G_("function %q+F can never be copied because it saves " |
+ "address of local label in a static variable"); |
+ goto fail; |
+ } |
} |
- return NULL_TREE; |
+ fail: |
+ fun->cannot_be_copied_reason = reason; |
+ fun->cannot_be_copied_set = true; |
+ return reason; |
} |
-/* A callback for walk_gimple_seq to handle statements. Returns |
- non-NULL iff a function can not be inlined. Also sets the reason |
- why. */ |
+static const char *inline_forbidden_reason; |
+ |
+/* A callback for walk_gimple_seq to handle statements. Returns non-null |
+ iff a function can not be inlined. Also sets the reason why. */ |
static tree |
inline_forbidden_p_stmt (gimple_stmt_iterator *gsi, bool *handled_ops_p, |
@@ -2554,21 +2978,6 @@ inline_forbidden_p_stmt (gimple_stmt_iterator *gsi, bool *handled_ops_p, |
} |
break; |
- case GIMPLE_LABEL: |
- t = gimple_label_label (stmt); |
- if (DECL_NONLOCAL (t)) |
- { |
- /* We cannot inline a function that receives a non-local goto |
- because we cannot remap the destination label used in the |
- function that is performing the non-local goto. */ |
- inline_forbidden_reason |
- = G_("function %q+F can never be inlined " |
- "because it receives a non-local goto"); |
- *handled_ops_p = true; |
- return t; |
- } |
- break; |
- |
default: |
break; |
} |
@@ -2577,42 +2986,25 @@ inline_forbidden_p_stmt (gimple_stmt_iterator *gsi, bool *handled_ops_p, |
return NULL_TREE; |
} |
- |
-static tree |
-inline_forbidden_p_2 (tree *nodep, int *walk_subtrees, |
- void *fnp) |
-{ |
- tree node = *nodep; |
- tree fn = (tree) fnp; |
- |
- if (TREE_CODE (node) == LABEL_DECL && DECL_CONTEXT (node) == fn) |
- { |
- inline_forbidden_reason |
- = G_("function %q+F can never be inlined " |
- "because it saves address of local label in a static variable"); |
- return node; |
- } |
- |
- if (TYPE_P (node)) |
- *walk_subtrees = 0; |
- |
- return NULL_TREE; |
-} |
- |
/* Return true if FNDECL is a function that cannot be inlined into |
another one. */ |
static bool |
inline_forbidden_p (tree fndecl) |
{ |
- location_t saved_loc = input_location; |
struct function *fun = DECL_STRUCT_FUNCTION (fndecl); |
- tree step; |
struct walk_stmt_info wi; |
struct pointer_set_t *visited_nodes; |
basic_block bb; |
bool forbidden_p = false; |
+ /* First check for shared reasons not to copy the code. */ |
+ inline_forbidden_reason = copy_forbidden (fun, fndecl); |
+ if (inline_forbidden_reason != NULL) |
+ return true; |
+ |
+ /* Next, walk the statements of the function looking for |
+ constraucts we can't handle, or are non-optimal for inlining. */ |
visited_nodes = pointer_set_create (); |
memset (&wi, 0, sizeof (wi)); |
wi.info = (void *) fndecl; |
@@ -2622,41 +3014,21 @@ inline_forbidden_p (tree fndecl) |
{ |
gimple ret; |
gimple_seq seq = bb_seq (bb); |
- ret = walk_gimple_seq (seq, inline_forbidden_p_stmt, |
- inline_forbidden_p_op, &wi); |
+ ret = walk_gimple_seq (seq, inline_forbidden_p_stmt, NULL, &wi); |
forbidden_p = (ret != NULL); |
if (forbidden_p) |
- goto egress; |
- } |
- |
- for (step = fun->local_decls; step; step = TREE_CHAIN (step)) |
- { |
- tree decl = TREE_VALUE (step); |
- if (TREE_CODE (decl) == VAR_DECL |
- && TREE_STATIC (decl) |
- && !DECL_EXTERNAL (decl) |
- && DECL_INITIAL (decl)) |
- { |
- tree ret; |
- ret = walk_tree_without_duplicates (&DECL_INITIAL (decl), |
- inline_forbidden_p_2, fndecl); |
- forbidden_p = (ret != NULL); |
- if (forbidden_p) |
- goto egress; |
- } |
+ break; |
} |
-egress: |
pointer_set_destroy (visited_nodes); |
- input_location = saved_loc; |
return forbidden_p; |
} |
/* Returns nonzero if FN is a function that does not have any |
fundamental inline blocking properties. */ |
-static bool |
-inlinable_function_p (tree fn) |
+bool |
+tree_inlinable_function_p (tree fn) |
{ |
bool inlinable = true; |
bool do_warning; |
@@ -2729,6 +3101,8 @@ estimate_move_cost (tree type) |
{ |
HOST_WIDE_INT size; |
+ gcc_assert (!VOID_TYPE_P (type)); |
+ |
size = int_size_in_bytes (type); |
if (size < 0 || size > MOVE_MAX_PIECES * MOVE_RATIO (!optimize_size)) |
@@ -2741,7 +3115,8 @@ estimate_move_cost (tree type) |
/* Returns cost of operation CODE, according to WEIGHTS */ |
static int |
-estimate_operator_cost (enum tree_code code, eni_weights *weights) |
+estimate_operator_cost (enum tree_code code, eni_weights *weights, |
+ tree op1 ATTRIBUTE_UNUSED, tree op2) |
{ |
switch (code) |
{ |
@@ -2763,6 +3138,7 @@ estimate_operator_cost (enum tree_code code, eni_weights *weights) |
case MINUS_EXPR: |
case MULT_EXPR: |
+ case ADDR_SPACE_CONVERT_EXPR: |
case FIXED_CONVERT_EXPR: |
case FIX_TRUNC_EXPR: |
@@ -2851,7 +3227,9 @@ estimate_operator_cost (enum tree_code code, eni_weights *weights) |
case FLOOR_MOD_EXPR: |
case ROUND_MOD_EXPR: |
case RDIV_EXPR: |
- return weights->div_mod_cost; |
+ if (TREE_CODE (op2) != INTEGER_CST) |
+ return weights->div_mod_cost; |
+ return 1; |
default: |
/* We expect a copy assignment with no operator. */ |
@@ -2888,6 +3266,7 @@ estimate_num_insns (gimple stmt, eni_weights *weights) |
unsigned cost, i; |
enum gimple_code code = gimple_code (stmt); |
tree lhs; |
+ tree rhs; |
switch (code) |
{ |
@@ -2911,25 +3290,39 @@ estimate_num_insns (gimple stmt, eni_weights *weights) |
of moving something into "a", which we compute using the function |
estimate_move_cost. */ |
lhs = gimple_assign_lhs (stmt); |
+ rhs = gimple_assign_rhs1 (stmt); |
+ |
if (is_gimple_reg (lhs)) |
cost = 0; |
else |
cost = estimate_move_cost (TREE_TYPE (lhs)); |
- cost += estimate_operator_cost (gimple_assign_rhs_code (stmt), weights); |
+ if (!is_gimple_reg (rhs) && !is_gimple_min_invariant (rhs)) |
+ cost += estimate_move_cost (TREE_TYPE (rhs)); |
+ |
+ cost += estimate_operator_cost (gimple_assign_rhs_code (stmt), weights, |
+ gimple_assign_rhs1 (stmt), |
+ get_gimple_rhs_class (gimple_assign_rhs_code (stmt)) |
+ == GIMPLE_BINARY_RHS |
+ ? gimple_assign_rhs2 (stmt) : NULL); |
break; |
case GIMPLE_COND: |
- cost = 1 + estimate_operator_cost (gimple_cond_code (stmt), weights); |
+ cost = 1 + estimate_operator_cost (gimple_cond_code (stmt), weights, |
+ gimple_op (stmt, 0), |
+ gimple_op (stmt, 1)); |
break; |
case GIMPLE_SWITCH: |
/* Take into account cost of the switch + guess 2 conditional jumps for |
- each case label. |
+ each case label. |
TODO: once the switch expansion logic is sufficiently separated, we can |
do better job on estimating cost of the switch. */ |
- cost = gimple_switch_num_labels (stmt) * 2; |
+ if (weights->time_based) |
+ cost = floor_log2 (gimple_switch_num_labels (stmt)) * 2; |
+ else |
+ cost = gimple_switch_num_labels (stmt) * 2; |
break; |
case GIMPLE_CALL: |
@@ -2945,21 +3338,26 @@ estimate_num_insns (gimple stmt, eni_weights *weights) |
cost = weights->target_builtin_call_cost; |
else |
cost = weights->call_cost; |
- |
+ |
if (decl && DECL_BUILT_IN_CLASS (decl) == BUILT_IN_NORMAL) |
switch (DECL_FUNCTION_CODE (decl)) |
{ |
case BUILT_IN_CONSTANT_P: |
return 0; |
case BUILT_IN_EXPECT: |
- cost = 0; |
- break; |
+ return 0; |
/* Prefetch instruction is not expensive. */ |
case BUILT_IN_PREFETCH: |
cost = weights->target_builtin_call_cost; |
break; |
+ /* Exception state returns or moves registers around. */ |
+ case BUILT_IN_EH_FILTER: |
+ case BUILT_IN_EH_POINTER: |
+ case BUILT_IN_EH_COPY_VALUES: |
+ return 0; |
+ |
default: |
break; |
} |
@@ -2967,6 +3365,8 @@ estimate_num_insns (gimple stmt, eni_weights *weights) |
if (decl) |
funtype = TREE_TYPE (decl); |
+ if (!VOID_TYPE_P (TREE_TYPE (funtype))) |
+ cost += estimate_move_cost (TREE_TYPE (funtype)); |
/* Our cost must be kept in sync with |
cgraph_estimate_size_after_inlining that does use function |
declaration to figure out the arguments. */ |
@@ -2974,20 +3374,24 @@ estimate_num_insns (gimple stmt, eni_weights *weights) |
{ |
tree arg; |
for (arg = DECL_ARGUMENTS (decl); arg; arg = TREE_CHAIN (arg)) |
- cost += estimate_move_cost (TREE_TYPE (arg)); |
+ if (!VOID_TYPE_P (TREE_TYPE (arg))) |
+ cost += estimate_move_cost (TREE_TYPE (arg)); |
} |
else if (funtype && prototype_p (funtype)) |
{ |
tree t; |
- for (t = TYPE_ARG_TYPES (funtype); t; t = TREE_CHAIN (t)) |
- cost += estimate_move_cost (TREE_VALUE (t)); |
+ for (t = TYPE_ARG_TYPES (funtype); t && t != void_list_node; |
+ t = TREE_CHAIN (t)) |
+ if (!VOID_TYPE_P (TREE_VALUE (t))) |
+ cost += estimate_move_cost (TREE_VALUE (t)); |
} |
else |
{ |
for (i = 0; i < gimple_call_num_args (stmt); i++) |
{ |
tree arg = gimple_call_arg (stmt, i); |
- cost += estimate_move_cost (TREE_TYPE (arg)); |
+ if (!VOID_TYPE_P (TREE_TYPE (arg))) |
+ cost += estimate_move_cost (TREE_TYPE (arg)); |
} |
} |
@@ -2999,13 +3403,23 @@ estimate_num_insns (gimple stmt, eni_weights *weights) |
case GIMPLE_NOP: |
case GIMPLE_PHI: |
case GIMPLE_RETURN: |
- case GIMPLE_CHANGE_DYNAMIC_TYPE: |
case GIMPLE_PREDICT: |
+ case GIMPLE_DEBUG: |
return 0; |
case GIMPLE_ASM: |
+ return asm_str_count (gimple_asm_string (stmt)); |
+ |
case GIMPLE_RESX: |
- return 1; |
+ /* This is either going to be an external function call with one |
+ argument, or two register copy statements plus a goto. */ |
+ return 2; |
+ |
+ case GIMPLE_EH_DISPATCH: |
+ /* ??? This is going to turn into a switch statement. Ideally |
+ we'd have a look at the eh region and estimate the number of |
+ edges involved. */ |
+ return 10; |
case GIMPLE_BIND: |
return estimate_num_insns_seq (gimple_bind_body (stmt), weights); |
@@ -3083,15 +3497,11 @@ estimate_num_insns_fn (tree fndecl, eni_weights *weights) |
void |
init_inline_once (void) |
{ |
- eni_inlining_weights.call_cost = PARAM_VALUE (PARAM_INLINE_CALL_COST); |
- eni_inlining_weights.target_builtin_call_cost = 1; |
- eni_inlining_weights.div_mod_cost = 10; |
- eni_inlining_weights.omp_cost = 40; |
- |
eni_size_weights.call_cost = 1; |
eni_size_weights.target_builtin_call_cost = 1; |
eni_size_weights.div_mod_cost = 1; |
eni_size_weights.omp_cost = 40; |
+ eni_size_weights.time_based = false; |
/* Estimating time for call is difficult, since we have no idea what the |
called function does. In the current uses of eni_time_weights, |
@@ -3101,6 +3511,7 @@ init_inline_once (void) |
eni_time_weights.target_builtin_call_cost = 10; |
eni_time_weights.div_mod_cost = 10; |
eni_time_weights.omp_cost = 40; |
+ eni_time_weights.time_based = true; |
} |
/* Estimate the number of instructions in a gimple_seq. */ |
@@ -3146,14 +3557,14 @@ get_indirect_callee_fndecl (struct cgraph_node *node, gimple stmt) |
static bool |
expand_call_inline (basic_block bb, gimple stmt, copy_body_data *id) |
{ |
- tree retvar, use_retvar; |
+ tree use_retvar; |
tree fn; |
- struct pointer_map_t *st; |
+ struct pointer_map_t *st, *dst; |
tree return_slot; |
tree modify_dest; |
location_t saved_location; |
struct cgraph_edge *cg_edge; |
- const char *reason; |
+ cgraph_inline_failed_t reason; |
basic_block return_block; |
edge e; |
gimple_stmt_iterator gsi, stmt_gsi; |
@@ -3204,28 +3615,12 @@ expand_call_inline (basic_block bb, gimple stmt, copy_body_data *id) |
cg_edge = cgraph_edge (id->dst_node, stmt); |
- /* Constant propagation on argument done during previous inlining |
- may create new direct call. Produce an edge for it. */ |
- if (!cg_edge) |
- { |
- struct cgraph_node *dest = cgraph_node (fn); |
- |
- /* We have missing edge in the callgraph. This can happen in one case |
- where previous inlining turned indirect call into direct call by |
- constant propagating arguments. In all other cases we hit a bug |
- (incorrect node sharing is most common reason for missing edges. */ |
- gcc_assert (dest->needed); |
- cgraph_create_edge (id->dst_node, dest, stmt, |
- bb->count, CGRAPH_FREQ_BASE, |
- bb->loop_depth)->inline_failed |
- = N_("originally indirect function call not considered for inlining"); |
- if (dump_file) |
- { |
- fprintf (dump_file, "Created new direct edge to %s", |
- cgraph_node_name (dest)); |
- } |
- goto egress; |
- } |
+ /* Don't inline functions with different EH personalities. */ |
+ if (DECL_FUNCTION_PERSONALITY (cg_edge->caller->decl) |
+ && DECL_FUNCTION_PERSONALITY (cg_edge->callee->decl) |
+ && (DECL_FUNCTION_PERSONALITY (cg_edge->caller->decl) |
+ != DECL_FUNCTION_PERSONALITY (cg_edge->callee->decl))) |
+ goto egress; |
/* Don't try to inline functions that are not well-suited to |
inlining. */ |
@@ -3241,18 +3636,19 @@ expand_call_inline (basic_block bb, gimple stmt, copy_body_data *id) |
/* Avoid warnings during early inline pass. */ |
&& cgraph_global_info_ready) |
{ |
- sorry ("inlining failed in call to %q+F: %s", fn, reason); |
+ sorry ("inlining failed in call to %q+F: %s", fn, |
+ cgraph_inline_failed_string (reason)); |
sorry ("called from here"); |
} |
else if (warn_inline && DECL_DECLARED_INLINE_P (fn) |
&& !DECL_IN_SYSTEM_HEADER (fn) |
- && strlen (reason) |
+ && reason != CIF_UNSPECIFIED |
&& !lookup_attribute ("noinline", DECL_ATTRIBUTES (fn)) |
/* Avoid warnings during early inline pass. */ |
&& cgraph_global_info_ready) |
{ |
warning (OPT_Winline, "inlining failed in call to %q+F: %s", |
- fn, reason); |
+ fn, cgraph_inline_failed_string (reason)); |
warning (OPT_Winline, "called from here"); |
} |
goto egress; |
@@ -3265,7 +3661,12 @@ expand_call_inline (basic_block bb, gimple stmt, copy_body_data *id) |
#endif |
/* We will be inlining this callee. */ |
- id->eh_region = lookup_stmt_eh_region (stmt); |
+ id->eh_lp_nr = lookup_stmt_eh_lp (stmt); |
+ |
+ /* Update the callers EH personality. */ |
+ if (DECL_FUNCTION_PERSONALITY (cg_edge->callee->decl)) |
+ DECL_FUNCTION_PERSONALITY (cg_edge->caller->decl) |
+ = DECL_FUNCTION_PERSONALITY (cg_edge->callee->decl); |
/* Split the block holding the GIMPLE_CALL. */ |
e = split_block (bb, stmt); |
@@ -3310,6 +3711,8 @@ expand_call_inline (basic_block bb, gimple stmt, copy_body_data *id) |
map. */ |
st = id->decl_map; |
id->decl_map = pointer_map_create (); |
+ dst = id->debug_map; |
+ id->debug_map = NULL; |
/* Record the function we are about to inline. */ |
id->src_fn = fn; |
@@ -3373,14 +3776,7 @@ expand_call_inline (basic_block bb, gimple stmt, copy_body_data *id) |
} |
/* Declare the return variable for the function. */ |
- retvar = declare_return_variable (id, return_slot, modify_dest, &use_retvar); |
- |
- if (DECL_IS_OPERATOR_NEW (fn)) |
- { |
- gcc_assert (TREE_CODE (retvar) == VAR_DECL |
- && POINTER_TYPE_P (TREE_TYPE (retvar))); |
- DECL_NO_TBAA_P (retvar) = 1; |
- } |
+ use_retvar = declare_return_variable (id, return_slot, modify_dest); |
/* Add local vars in this inlined callee to caller. */ |
t_step = id->src_cfun->local_decls; |
@@ -3398,17 +3794,43 @@ expand_call_inline (basic_block bb, gimple stmt, copy_body_data *id) |
cfun->local_decls); |
} |
+ if (dump_file && (dump_flags & TDF_DETAILS)) |
+ { |
+ fprintf (dump_file, "Inlining "); |
+ print_generic_expr (dump_file, id->src_fn, 0); |
+ fprintf (dump_file, " to "); |
+ print_generic_expr (dump_file, id->dst_fn, 0); |
+ fprintf (dump_file, " with frequency %i\n", cg_edge->frequency); |
+ } |
+ |
/* This is it. Duplicate the callee body. Assume callee is |
pre-gimplified. Note that we must not alter the caller |
function in any way before this point, as this CALL_EXPR may be |
a self-referential call; if we're calling ourselves, we need to |
duplicate our body before altering anything. */ |
- copy_body (id, bb->count, bb->frequency, bb, return_block); |
+ copy_body (id, bb->count, |
+ cg_edge->frequency * REG_BR_PROB_BASE / CGRAPH_FREQ_BASE, |
+ bb, return_block); |
+ |
+ /* Reset the escaped and callused solutions. */ |
+ if (cfun->gimple_df) |
+ { |
+ pt_solution_reset (&cfun->gimple_df->escaped); |
+ pt_solution_reset (&cfun->gimple_df->callused); |
+ } |
/* Clean up. */ |
+ if (id->debug_map) |
+ { |
+ pointer_map_destroy (id->debug_map); |
+ id->debug_map = dst; |
+ } |
pointer_map_destroy (id->decl_map); |
id->decl_map = st; |
+ /* Unlink the calls virtual operands before replacing it. */ |
+ unlink_stmt_vdef (stmt); |
+ |
/* If the inlined function returns a result that we care about, |
substitute the GIMPLE_CALL with an assignment of the return |
variable to the LHS of the call. That is, if STMT was |
@@ -3419,10 +3841,7 @@ expand_call_inline (basic_block bb, gimple stmt, copy_body_data *id) |
stmt = gimple_build_assign (gimple_call_lhs (stmt), use_retvar); |
gsi_replace (&stmt_gsi, stmt, false); |
if (gimple_in_ssa_p (cfun)) |
- { |
- update_stmt (stmt); |
- mark_symbols_for_renaming (stmt); |
- } |
+ mark_symbols_for_renaming (stmt); |
maybe_clean_or_replace_eh_stmt (old_stmt, stmt); |
} |
else |
@@ -3442,7 +3861,6 @@ expand_call_inline (basic_block bb, gimple stmt, copy_body_data *id) |
undefined via a move. */ |
stmt = gimple_build_assign (gimple_call_lhs (stmt), def); |
gsi_replace (&stmt_gsi, stmt, true); |
- update_stmt (stmt); |
} |
else |
{ |
@@ -3527,16 +3945,60 @@ fold_marked_statements (int first, struct pointer_set_t *statements) |
if (pointer_set_contains (statements, gsi_stmt (gsi))) |
{ |
gimple old_stmt = gsi_stmt (gsi); |
+ tree old_decl = is_gimple_call (old_stmt) ? gimple_call_fndecl (old_stmt) : 0; |
- if (fold_stmt (&gsi)) |
+ if (old_decl && DECL_BUILT_IN (old_decl)) |
+ { |
+ /* Folding builtins can create multiple instructions, |
+ we need to look at all of them. */ |
+ gimple_stmt_iterator i2 = gsi; |
+ gsi_prev (&i2); |
+ if (fold_stmt (&gsi)) |
+ { |
+ gimple new_stmt; |
+ if (gsi_end_p (i2)) |
+ i2 = gsi_start_bb (BASIC_BLOCK (first)); |
+ else |
+ gsi_next (&i2); |
+ while (1) |
+ { |
+ new_stmt = gsi_stmt (i2); |
+ update_stmt (new_stmt); |
+ cgraph_update_edges_for_call_stmt (old_stmt, old_decl, |
+ new_stmt); |
+ |
+ if (new_stmt == gsi_stmt (gsi)) |
+ { |
+ /* It is okay to check only for the very last |
+ of these statements. If it is a throwing |
+ statement nothing will change. If it isn't |
+ this can remove EH edges. If that weren't |
+ correct then because some intermediate stmts |
+ throw, but not the last one. That would mean |
+ we'd have to split the block, which we can't |
+ here and we'd loose anyway. And as builtins |
+ probably never throw, this all |
+ is mood anyway. */ |
+ if (maybe_clean_or_replace_eh_stmt (old_stmt, |
+ new_stmt)) |
+ gimple_purge_dead_eh_edges (BASIC_BLOCK (first)); |
+ break; |
+ } |
+ gsi_next (&i2); |
+ } |
+ } |
+ } |
+ else if (fold_stmt (&gsi)) |
{ |
/* Re-read the statement from GSI as fold_stmt() may |
have changed it. */ |
gimple new_stmt = gsi_stmt (gsi); |
update_stmt (new_stmt); |
- if (is_gimple_call (old_stmt)) |
- cgraph_update_edges_for_call_stmt (old_stmt, new_stmt); |
+ if (is_gimple_call (old_stmt) |
+ || is_gimple_call (new_stmt)) |
+ cgraph_update_edges_for_call_stmt (old_stmt, old_decl, |
+ new_stmt); |
if (maybe_clean_or_replace_eh_stmt (old_stmt, new_stmt)) |
gimple_purge_dead_eh_edges (BASIC_BLOCK (first)); |
@@ -3566,7 +4028,6 @@ unsigned int |
optimize_inline_calls (tree fn) |
{ |
copy_body_data id; |
- tree prev_fn; |
basic_block bb; |
int last = n_basic_blocks; |
struct gimplify_ctx gctx; |
@@ -3583,12 +4044,8 @@ optimize_inline_calls (tree fn) |
id.src_node = id.dst_node = cgraph_node (fn); |
id.dst_fn = fn; |
/* Or any functions that aren't finished yet. */ |
- prev_fn = NULL_TREE; |
if (current_function_decl) |
- { |
- id.dst_fn = current_function_decl; |
- prev_fn = current_function_decl; |
- } |
+ id.dst_fn = current_function_decl; |
id.copy_decl = copy_decl_maybe_to_var; |
id.transform_call_graph_edges = CB_CGE_DUPLICATE; |
@@ -3628,21 +4085,23 @@ optimize_inline_calls (tree fn) |
gcc_assert (e->inline_failed); |
} |
#endif |
- |
+ |
/* Fold the statements before compacting/renumbering the basic blocks. */ |
fold_marked_statements (last, id.statements_to_fold); |
pointer_set_destroy (id.statements_to_fold); |
- |
+ |
+ gcc_assert (!id.debug_stmts); |
+ |
/* Renumber the (code) basic_blocks consecutively. */ |
compact_blocks (); |
/* Renumber the lexical scoping (non-code) blocks consecutively. */ |
number_blocks (fn); |
- /* We are not going to maintain the cgraph edges up to date. |
- Kill it so it won't confuse us. */ |
- cgraph_node_remove_callees (id.dst_node); |
- |
fold_cond_expr_cond (); |
+ delete_unreachable_blocks_update_callgraph (&id); |
+#ifdef ENABLE_CHECKING |
+ verify_cgraph_node (id.dst_node); |
+#endif |
/* It would be nice to check SSA/CFG/statement consistency here, but it is |
not possible yet - the IPA passes might make various functions to not |
@@ -3836,7 +4295,8 @@ unsave_r (tree *tp, int *walk_subtrees, void *data) |
gcc_unreachable (); |
else if (TREE_CODE (*tp) == BIND_EXPR) |
copy_bind_expr (tp, walk_subtrees, id); |
- else if (TREE_CODE (*tp) == SAVE_EXPR) |
+ else if (TREE_CODE (*tp) == SAVE_EXPR |
+ || TREE_CODE (*tp) == TARGET_EXPR) |
remap_save_expr (tp, st, walk_subtrees); |
else |
{ |
@@ -3867,6 +4327,7 @@ unsave_expr_now (tree expr) |
id.src_fn = current_function_decl; |
id.dst_fn = current_function_decl; |
id.decl_map = pointer_map_create (); |
+ id.debug_map = NULL; |
id.copy_decl = copy_decl_no_change; |
id.transform_call_graph_edges = CB_CGE_DUPLICATE; |
@@ -3882,6 +4343,8 @@ unsave_expr_now (tree expr) |
/* Clean up. */ |
pointer_map_destroy (id.decl_map); |
+ if (id.debug_map) |
+ pointer_map_destroy (id.debug_map); |
return expr; |
} |
@@ -4013,6 +4476,7 @@ copy_gimple_seq_and_replace_locals (gimple_seq seq) |
id.src_fn = current_function_decl; |
id.dst_fn = current_function_decl; |
id.decl_map = pointer_map_create (); |
+ id.debug_map = NULL; |
id.copy_decl = copy_decl_no_change; |
id.transform_call_graph_edges = CB_CGE_DUPLICATE; |
@@ -4037,6 +4501,8 @@ copy_gimple_seq_and_replace_locals (gimple_seq seq) |
/* Clean up. */ |
pointer_map_destroy (id.decl_map); |
+ if (id.debug_map) |
+ pointer_map_destroy (id.debug_map); |
return copy; |
} |
@@ -4091,14 +4557,14 @@ copy_decl_for_dup_finish (copy_body_data *id, tree decl, tree copy) |
DECL_IGNORED_P (copy) = DECL_IGNORED_P (decl); |
/* Set the DECL_ABSTRACT_ORIGIN so the debugging routines know what |
- declaration inspired this copy. */ |
+ declaration inspired this copy. */ |
DECL_ABSTRACT_ORIGIN (copy) = DECL_ORIGIN (decl); |
/* The new variable/label has no RTL, yet. */ |
if (CODE_CONTAINS_STRUCT (TREE_CODE (copy), TS_DECL_WRTL) |
&& !TREE_STATIC (copy) && !DECL_EXTERNAL (copy)) |
SET_DECL_RTL (copy, NULL_RTX); |
- |
+ |
/* These args would always appear unused, if not for this. */ |
TREE_USED (copy) = 1; |
@@ -4132,12 +4598,12 @@ copy_decl_to_var (tree decl, copy_body_data *id) |
type = TREE_TYPE (decl); |
- copy = build_decl (VAR_DECL, DECL_NAME (decl), type); |
+ copy = build_decl (DECL_SOURCE_LOCATION (id->dst_fn), |
+ VAR_DECL, DECL_NAME (decl), type); |
TREE_ADDRESSABLE (copy) = TREE_ADDRESSABLE (decl); |
TREE_READONLY (copy) = TREE_READONLY (decl); |
TREE_THIS_VOLATILE (copy) = TREE_THIS_VOLATILE (decl); |
DECL_GIMPLE_REG_P (copy) = DECL_GIMPLE_REG_P (decl); |
- DECL_NO_TBAA_P (copy) = DECL_NO_TBAA_P (decl); |
return copy_decl_for_dup_finish (id, decl, copy); |
} |
@@ -4157,14 +4623,14 @@ copy_result_decl_to_var (tree decl, copy_body_data *id) |
if (DECL_BY_REFERENCE (decl)) |
type = TREE_TYPE (type); |
- copy = build_decl (VAR_DECL, DECL_NAME (decl), type); |
+ copy = build_decl (DECL_SOURCE_LOCATION (id->dst_fn), |
+ VAR_DECL, DECL_NAME (decl), type); |
TREE_READONLY (copy) = TREE_READONLY (decl); |
TREE_THIS_VOLATILE (copy) = TREE_THIS_VOLATILE (decl); |
if (!DECL_BY_REFERENCE (decl)) |
{ |
TREE_ADDRESSABLE (copy) = TREE_ADDRESSABLE (decl); |
DECL_GIMPLE_REG_P (copy) = DECL_GIMPLE_REG_P (decl); |
- DECL_NO_TBAA_P (copy) = DECL_NO_TBAA_P (decl); |
} |
return copy_decl_for_dup_finish (id, decl, copy); |
@@ -4255,29 +4721,131 @@ copy_static_chain (tree static_chain, copy_body_data * id) |
/* Return true if the function is allowed to be versioned. |
This is a guard for the versioning functionality. */ |
+ |
bool |
tree_versionable_function_p (tree fndecl) |
{ |
- if (fndecl == NULL_TREE) |
- return false; |
- /* ??? There are cases where a function is |
- uninlinable but can be versioned. */ |
- if (!tree_inlinable_function_p (fndecl)) |
- return false; |
- |
- return true; |
+ return (!lookup_attribute ("noclone", DECL_ATTRIBUTES (fndecl)) |
+ && copy_forbidden (DECL_STRUCT_FUNCTION (fndecl), fndecl) == NULL); |
+} |
+ |
+/* Delete all unreachable basic blocks and update callgraph. |
+ Doing so is somewhat nontrivial because we need to update all clones and |
+ remove inline function that become unreachable. */ |
+ |
+static bool |
+delete_unreachable_blocks_update_callgraph (copy_body_data *id) |
+{ |
+ bool changed = false; |
+ basic_block b, next_bb; |
+ |
+ find_unreachable_blocks (); |
+ |
+ /* Delete all unreachable basic blocks. */ |
+ |
+ for (b = ENTRY_BLOCK_PTR->next_bb; b != EXIT_BLOCK_PTR; b = next_bb) |
+ { |
+ next_bb = b->next_bb; |
+ |
+ if (!(b->flags & BB_REACHABLE)) |
+ { |
+ gimple_stmt_iterator bsi; |
+ |
+ for (bsi = gsi_start_bb (b); !gsi_end_p (bsi); gsi_next (&bsi)) |
+ if (gimple_code (gsi_stmt (bsi)) == GIMPLE_CALL) |
+ { |
+ struct cgraph_edge *e; |
+ struct cgraph_node *node; |
+ |
+ if ((e = cgraph_edge (id->dst_node, gsi_stmt (bsi))) != NULL) |
+ { |
+ if (!e->inline_failed) |
+ cgraph_remove_node_and_inline_clones (e->callee); |
+ else |
+ cgraph_remove_edge (e); |
+ } |
+ if (id->transform_call_graph_edges == CB_CGE_MOVE_CLONES |
+ && id->dst_node->clones) |
+ for (node = id->dst_node->clones; node != id->dst_node;) |
+ { |
+ if ((e = cgraph_edge (node, gsi_stmt (bsi))) != NULL) |
+ { |
+ if (!e->inline_failed) |
+ cgraph_remove_node_and_inline_clones (e->callee); |
+ else |
+ cgraph_remove_edge (e); |
+ } |
+ |
+ if (node->clones) |
+ node = node->clones; |
+ else if (node->next_sibling_clone) |
+ node = node->next_sibling_clone; |
+ else |
+ { |
+ while (node != id->dst_node && !node->next_sibling_clone) |
+ node = node->clone_of; |
+ if (node != id->dst_node) |
+ node = node->next_sibling_clone; |
+ } |
+ } |
+ } |
+ delete_basic_block (b); |
+ changed = true; |
+ } |
+ } |
+ |
+ if (changed) |
+ tidy_fallthru_edges (); |
+ return changed; |
+} |
+ |
+/* Update clone info after duplication. */ |
+ |
+static void |
+update_clone_info (copy_body_data * id) |
+{ |
+ struct cgraph_node *node; |
+ if (!id->dst_node->clones) |
+ return; |
+ for (node = id->dst_node->clones; node != id->dst_node;) |
+ { |
+ /* First update replace maps to match the new body. */ |
+ if (node->clone.tree_map) |
+ { |
+ unsigned int i; |
+ for (i = 0; i < VEC_length (ipa_replace_map_p, node->clone.tree_map); i++) |
+ { |
+ struct ipa_replace_map *replace_info; |
+ replace_info = VEC_index (ipa_replace_map_p, node->clone.tree_map, i); |
+ walk_tree (&replace_info->old_tree, copy_tree_body_r, id, NULL); |
+ walk_tree (&replace_info->new_tree, copy_tree_body_r, id, NULL); |
+ } |
+ } |
+ if (node->clones) |
+ node = node->clones; |
+ else if (node->next_sibling_clone) |
+ node = node->next_sibling_clone; |
+ else |
+ { |
+ while (node != id->dst_node && !node->next_sibling_clone) |
+ node = node->clone_of; |
+ if (node != id->dst_node) |
+ node = node->next_sibling_clone; |
+ } |
+ } |
} |
/* Create a copy of a function's tree. |
OLD_DECL and NEW_DECL are FUNCTION_DECL tree nodes |
of the original function and the new copied function |
- respectively. In case we want to replace a DECL |
- tree with another tree while duplicating the function's |
- body, TREE_MAP represents the mapping between these |
+ respectively. In case we want to replace a DECL |
+ tree with another tree while duplicating the function's |
+ body, TREE_MAP represents the mapping between these |
trees. If UPDATE_CLONES is set, the call_stmt fields |
of edges of clones of the function will be updated. */ |
void |
-tree_function_versioning (tree old_decl, tree new_decl, varray_type tree_map, |
+tree_function_versioning (tree old_decl, tree new_decl, |
+ VEC(ipa_replace_map_p,gc)* tree_map, |
bool update_clones, bitmap args_to_skip) |
{ |
struct cgraph_node *old_version_node; |
@@ -4286,7 +4854,7 @@ tree_function_versioning (tree old_decl, tree new_decl, varray_type tree_map, |
tree p; |
unsigned i; |
struct ipa_replace_map *replace_info; |
- basic_block old_entry_block; |
+ basic_block old_entry_block, bb; |
VEC (gimple, heap) *init_stmts = VEC_alloc (gimple, heap, 10); |
tree t_step; |
@@ -4308,26 +4876,35 @@ tree_function_versioning (tree old_decl, tree new_decl, varray_type tree_map, |
DECL_ARTIFICIAL (new_decl) = 1; |
DECL_ABSTRACT_ORIGIN (new_decl) = DECL_ORIGIN (old_decl); |
+ DECL_FUNCTION_PERSONALITY (new_decl) = DECL_FUNCTION_PERSONALITY (old_decl); |
/* Prepare the data structures for the tree copy. */ |
memset (&id, 0, sizeof (id)); |
/* Generate a new name for the new version. */ |
- if (!update_clones) |
- { |
- DECL_NAME (new_decl) = create_tmp_var_name (NULL); |
- SET_DECL_ASSEMBLER_NAME (new_decl, DECL_NAME (new_decl)); |
- SET_DECL_RTL (new_decl, NULL_RTX); |
- id.statements_to_fold = pointer_set_create (); |
- } |
- |
+ id.statements_to_fold = pointer_set_create (); |
+ |
id.decl_map = pointer_map_create (); |
+ id.debug_map = NULL; |
id.src_fn = old_decl; |
id.dst_fn = new_decl; |
id.src_node = old_version_node; |
id.dst_node = new_version_node; |
id.src_cfun = DECL_STRUCT_FUNCTION (old_decl); |
- |
+ if (id.src_node->ipa_transforms_to_apply) |
+ { |
+ VEC(ipa_opt_pass,heap) * old_transforms_to_apply = id.dst_node->ipa_transforms_to_apply; |
+ unsigned int i; |
+ |
+ id.dst_node->ipa_transforms_to_apply = VEC_copy (ipa_opt_pass, heap, |
+ id.src_node->ipa_transforms_to_apply); |
+ for (i = 0; i < VEC_length (ipa_opt_pass, old_transforms_to_apply); i++) |
+ VEC_safe_push (ipa_opt_pass, heap, id.dst_node->ipa_transforms_to_apply, |
+ VEC_index (ipa_opt_pass, |
+ old_transforms_to_apply, |
+ i)); |
+ } |
+ |
id.copy_decl = copy_decl_no_change; |
id.transform_call_graph_edges |
= update_clones ? CB_CGE_MOVE_CLONES : CB_CGE_MOVE; |
@@ -4339,24 +4916,22 @@ tree_function_versioning (tree old_decl, tree new_decl, varray_type tree_map, |
old_entry_block = ENTRY_BLOCK_PTR_FOR_FUNCTION |
(DECL_STRUCT_FUNCTION (old_decl)); |
initialize_cfun (new_decl, old_decl, |
- old_entry_block->count, |
- old_entry_block->frequency); |
+ old_entry_block->count); |
push_cfun (DECL_STRUCT_FUNCTION (new_decl)); |
- |
+ |
/* Copy the function's static chain. */ |
p = DECL_STRUCT_FUNCTION (old_decl)->static_chain_decl; |
if (p) |
DECL_STRUCT_FUNCTION (new_decl)->static_chain_decl = |
copy_static_chain (DECL_STRUCT_FUNCTION (old_decl)->static_chain_decl, |
&id); |
- |
+ |
/* If there's a tree_map, prepare for substitution. */ |
if (tree_map) |
- for (i = 0; i < VARRAY_ACTIVE_SIZE (tree_map); i++) |
+ for (i = 0; i < VEC_length (ipa_replace_map_p, tree_map); i++) |
{ |
gimple init; |
- replace_info |
- = (struct ipa_replace_map *) VARRAY_GENERIC_PTR (tree_map, i); |
+ replace_info = VEC_index (ipa_replace_map_p, tree_map, i); |
if (replace_info->replace_p) |
{ |
tree op = replace_info->new_tree; |
@@ -4365,7 +4940,7 @@ tree_function_versioning (tree old_decl, tree new_decl, varray_type tree_map, |
if (TREE_CODE (op) == VIEW_CONVERT_EXPR) |
op = TREE_OPERAND (op, 0); |
- |
+ |
if (TREE_CODE (op) == ADDR_EXPR) |
{ |
op = TREE_OPERAND (op, 0); |
@@ -4388,13 +4963,14 @@ tree_function_versioning (tree old_decl, tree new_decl, varray_type tree_map, |
DECL_ARGUMENTS (new_decl) = |
copy_arguments_for_versioning (DECL_ARGUMENTS (old_decl), &id, |
args_to_skip, &vars); |
- |
+ |
DECL_INITIAL (new_decl) = remap_blocks (DECL_INITIAL (id.src_fn), &id); |
- |
+ |
/* Renumber the lexical scoping (non-code) blocks consecutively. */ |
number_blocks (id.dst_fn); |
- |
+ |
declare_inline_vars (DECL_INITIAL (new_decl), vars); |
+ |
if (DECL_STRUCT_FUNCTION (old_decl)->local_decls != NULL_TREE) |
/* Add local vars. */ |
for (t_step = DECL_STRUCT_FUNCTION (old_decl)->local_decls; |
@@ -4408,51 +4984,55 @@ tree_function_versioning (tree old_decl, tree new_decl, varray_type tree_map, |
tree_cons (NULL_TREE, remap_decl (var, &id), |
cfun->local_decls); |
} |
- |
+ |
/* Copy the Function's body. */ |
- copy_body (&id, old_entry_block->count, old_entry_block->frequency, ENTRY_BLOCK_PTR, EXIT_BLOCK_PTR); |
- |
+ copy_body (&id, old_entry_block->count, REG_BR_PROB_BASE, |
+ ENTRY_BLOCK_PTR, EXIT_BLOCK_PTR); |
+ |
if (DECL_RESULT (old_decl) != NULL_TREE) |
{ |
tree *res_decl = &DECL_RESULT (old_decl); |
DECL_RESULT (new_decl) = remap_decl (*res_decl, &id); |
lang_hooks.dup_lang_specific_decl (DECL_RESULT (new_decl)); |
} |
- |
+ |
/* Renumber the lexical scoping (non-code) blocks consecutively. */ |
number_blocks (new_decl); |
- if (VEC_length (gimple, init_stmts)) |
+ /* We want to create the BB unconditionally, so that the addition of |
+ debug stmts doesn't affect BB count, which may in the end cause |
+ codegen differences. */ |
+ bb = split_edge (single_succ_edge (ENTRY_BLOCK_PTR)); |
+ while (VEC_length (gimple, init_stmts)) |
+ insert_init_stmt (&id, bb, VEC_pop (gimple, init_stmts)); |
+ update_clone_info (&id); |
+ |
+ /* Remap the nonlocal_goto_save_area, if any. */ |
+ if (cfun->nonlocal_goto_save_area) |
{ |
- basic_block bb = split_edge (single_succ_edge (ENTRY_BLOCK_PTR)); |
- while (VEC_length (gimple, init_stmts)) |
- insert_init_stmt (bb, VEC_pop (gimple, init_stmts)); |
+ struct walk_stmt_info wi; |
+ |
+ memset (&wi, 0, sizeof (wi)); |
+ wi.info = &id; |
+ walk_tree (&cfun->nonlocal_goto_save_area, remap_gimple_op_r, &wi, NULL); |
} |
/* Clean up. */ |
pointer_map_destroy (id.decl_map); |
- if (!update_clones) |
- { |
- fold_marked_statements (0, id.statements_to_fold); |
- pointer_set_destroy (id.statements_to_fold); |
- fold_cond_expr_cond (); |
- } |
- if (gimple_in_ssa_p (cfun)) |
- { |
- free_dominance_info (CDI_DOMINATORS); |
- free_dominance_info (CDI_POST_DOMINATORS); |
- if (!update_clones) |
- delete_unreachable_blocks (); |
- update_ssa (TODO_update_ssa); |
- if (!update_clones) |
- { |
- fold_cond_expr_cond (); |
- if (need_ssa_update_p ()) |
- update_ssa (TODO_update_ssa); |
- } |
- } |
+ if (id.debug_map) |
+ pointer_map_destroy (id.debug_map); |
+ free_dominance_info (CDI_DOMINATORS); |
+ free_dominance_info (CDI_POST_DOMINATORS); |
+ |
+ fold_marked_statements (0, id.statements_to_fold); |
+ pointer_set_destroy (id.statements_to_fold); |
+ fold_cond_expr_cond (); |
+ delete_unreachable_blocks_update_callgraph (&id); |
+ update_ssa (TODO_update_ssa); |
free_dominance_info (CDI_DOMINATORS); |
free_dominance_info (CDI_POST_DOMINATORS); |
+ |
+ gcc_assert (!id.debug_stmts); |
VEC_free (gimple, heap, init_stmts); |
pop_cfun (); |
current_function_decl = old_current_function_decl; |
@@ -4461,6 +5041,60 @@ tree_function_versioning (tree old_decl, tree new_decl, varray_type tree_map, |
return; |
} |
+/* EXP is CALL_EXPR present in a GENERIC expression tree. Try to integrate |
+ the callee and return the inlined body on success. */ |
+ |
+tree |
+maybe_inline_call_in_expr (tree exp) |
+{ |
+ tree fn = get_callee_fndecl (exp); |
+ |
+ /* We can only try to inline "const" functions. */ |
+ if (fn && TREE_READONLY (fn) && DECL_SAVED_TREE (fn)) |
+ { |
+ struct pointer_map_t *decl_map = pointer_map_create (); |
+ call_expr_arg_iterator iter; |
+ copy_body_data id; |
+ tree param, arg, t; |
+ |
+ /* Remap the parameters. */ |
+ for (param = DECL_ARGUMENTS (fn), arg = first_call_expr_arg (exp, &iter); |
+ param; |
+ param = TREE_CHAIN (param), arg = next_call_expr_arg (&iter)) |
+ *pointer_map_insert (decl_map, param) = arg; |
+ |
+ memset (&id, 0, sizeof (id)); |
+ id.src_fn = fn; |
+ id.dst_fn = current_function_decl; |
+ id.src_cfun = DECL_STRUCT_FUNCTION (fn); |
+ id.decl_map = decl_map; |
+ |
+ id.copy_decl = copy_decl_no_change; |
+ id.transform_call_graph_edges = CB_CGE_DUPLICATE; |
+ id.transform_new_cfg = false; |
+ id.transform_return_to_modify = true; |
+ id.transform_lang_insert_block = false; |
+ |
+ /* Make sure not to unshare trees behind the front-end's back |
+ since front-end specific mechanisms may rely on sharing. */ |
+ id.regimplify = false; |
+ id.do_not_unshare = true; |
+ |
+ /* We're not inside any EH region. */ |
+ id.eh_lp_nr = 0; |
+ |
+ t = copy_tree_body (&id); |
+ pointer_map_destroy (decl_map); |
+ |
+ /* We can only return something suitable for use in a GENERIC |
+ expression tree. */ |
+ if (TREE_CODE (t) == MODIFY_EXPR) |
+ return TREE_OPERAND (t, 1); |
+ } |
+ |
+ return NULL_TREE; |
+} |
+ |
/* Duplicate a type, fields and all. */ |
tree |
@@ -4473,11 +5107,14 @@ build_duplicate_type (tree type) |
id.dst_fn = current_function_decl; |
id.src_cfun = cfun; |
id.decl_map = pointer_map_create (); |
+ id.debug_map = NULL; |
id.copy_decl = copy_decl_no_change; |
type = remap_type_1 (type, &id); |
pointer_map_destroy (id.decl_map); |
+ if (id.debug_map) |
+ pointer_map_destroy (id.debug_map); |
TYPE_CANONICAL (type) = type; |
@@ -4485,9 +5122,10 @@ build_duplicate_type (tree type) |
} |
/* Return whether it is safe to inline a function because it used different |
- target specific options or different optimization options. */ |
+ target specific options or call site actual types mismatch parameter types. |
+ E is the call edge to be checked. */ |
bool |
-tree_can_inline_p (tree caller, tree callee) |
+tree_can_inline_p (struct cgraph_edge *e) |
{ |
#if 0 |
/* This causes a regression in SPEC in that it prevents a cold function from |
@@ -4516,7 +5154,48 @@ tree_can_inline_p (tree caller, tree callee) |
return false; |
} |
#endif |
+ tree caller, callee, lhs; |
+ |
+ caller = e->caller->decl; |
+ callee = e->callee->decl; |
+ |
+ /* We cannot inline a function that uses a different EH personality |
+ than the caller. */ |
+ if (DECL_FUNCTION_PERSONALITY (caller) |
+ && DECL_FUNCTION_PERSONALITY (callee) |
+ && (DECL_FUNCTION_PERSONALITY (caller) |
+ != DECL_FUNCTION_PERSONALITY (callee))) |
+ { |
+ e->inline_failed = CIF_UNSPECIFIED; |
+ gimple_call_set_cannot_inline (e->call_stmt, true); |
+ return false; |
+ } |
/* Allow the backend to decide if inlining is ok. */ |
- return targetm.target_option.can_inline_p (caller, callee); |
+ if (!targetm.target_option.can_inline_p (caller, callee)) |
+ { |
+ e->inline_failed = CIF_TARGET_OPTION_MISMATCH; |
+ gimple_call_set_cannot_inline (e->call_stmt, true); |
+ e->call_stmt_cannot_inline_p = true; |
+ return false; |
+ } |
+ |
+ /* Do not inline calls where we cannot triviall work around mismatches |
+ in argument or return types. */ |
+ if (e->call_stmt |
+ && ((DECL_RESULT (callee) |
+ && !DECL_BY_REFERENCE (DECL_RESULT (callee)) |
+ && (lhs = gimple_call_lhs (e->call_stmt)) != NULL_TREE |
+ && !useless_type_conversion_p (TREE_TYPE (DECL_RESULT (callee)), |
+ TREE_TYPE (lhs)) |
+ && !fold_convertible_p (TREE_TYPE (DECL_RESULT (callee)), lhs)) |
+ || !gimple_check_call_args (e->call_stmt))) |
+ { |
+ e->inline_failed = CIF_MISMATCHED_ARGUMENTS; |
+ gimple_call_set_cannot_inline (e->call_stmt, true); |
+ e->call_stmt_cannot_inline_p = true; |
+ return false; |
+ } |
+ |
+ return true; |
} |