* function.h (struct function): Add cannot_be_copied_reason, and cannot_be_copied_set. * tree-inline.c (has_label_address_in_static_1): Rename from inline_forbidden_p_2; don't set inline_forbidden_reason here. (cannot_copy_type_1): Rename from inline_forbidden_p_op; likewise don't set inline_forbidden_reason. (copy_forbidden): New function, split out of inline_forbidden_p. (inline_forbidden_p_stmt): Don't check for nonlocal labels here. (inline_forbidden_p): Use copy_forbidden. (tree_versionable_function_p): Likewise. (inlinable_function_p): Merge into tree_inlinable_function_p. (tree_function_versioning): Remap cfun->nonlocal_goto_save_area. * ipa-cp.c (ipcp_versionable_function_p): New function. (ipcp_cloning_candidate_p): Use it. (ipcp_node_modifiable_p): Likewise. --- function.h (revision 148984) +++ function.h (local) @@ -524,11 +524,17 @@ struct GTY(()) function { /* Properties used by the pass manager. */ unsigned int curr_properties; unsigned int last_verified; + /* Interprocedural passes scheduled to have their transform functions applied next time we execute local pass on them. We maintain it per-function in order to allow IPA passes to introduce new functions. */ VEC(ipa_opt_pass,heap) * GTY((skip)) ipa_transforms_to_apply; + /* Non-null if the function does something that would prevent it from + being copied; this applies to both versioning and inlining. Set to + a string describing the reason for failure. */ + const char * GTY((skip)) cannot_be_copied_reason; + /* Collected bit flags. */ /* Number of units of general registers that need saving in stdarg @@ -540,7 +546,6 @@ struct GTY(()) function { function. */ unsigned int va_list_fpr_size : 8; - /* How commonly executed the function is. Initialized during branch probabilities pass. */ ENUM_BITFIELD (function_frequency) function_frequency : 2; @@ -556,6 +561,11 @@ struct GTY(()) function { from nested functions. */ unsigned int has_nonlocal_label : 1; + /* Nonzero if we've set cannot_be_copied_reason. I.e. if + (cannot_be_copied_set && !cannot_be_copied_reason), the function + can in fact be copied. */ + unsigned int cannot_be_copied_set : 1; + /* Nonzero if current function uses stdarg.h or equivalent. */ unsigned int stdarg : 1; --- ipa-cp.c (revision 148984) +++ ipa-cp.c (local) @@ -351,6 +351,45 @@ ipcp_print_all_lattices (FILE * f) } } +/* Return true if ipcp algorithms would allow cloning NODE. */ + +static bool +ipcp_versionable_function_p (struct cgraph_node *node) +{ + tree decl = node->decl; + basic_block bb; + + /* There are a number of generic reasons functions cannot be versioned. */ + if (!tree_versionable_function_p (decl)) + return false; + + /* Removing arguments doesn't work if the function takes varargs. */ + if (DECL_STRUCT_FUNCTION (decl)->stdarg) + return false; + + /* Removing arguments doesn't work if we use __builtin_apply_args. */ + FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (decl)) + { + gimple_stmt_iterator gsi; + for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi)) + { + const_gimple stmt = gsi_stmt (gsi); + tree t; + + if (!is_gimple_call (stmt)) + continue; + t = gimple_call_fndecl (stmt); + if (t == NULL_TREE) + continue; + if (DECL_BUILT_IN_CLASS (t) == BUILT_IN_NORMAL + && DECL_FUNCTION_CODE (t) == BUILT_IN_APPLY_ARGS) + return false; + } + } + + return true; +} + /* Return true if this NODE is viable candidate for cloning. */ static bool ipcp_cloning_candidate_p (struct cgraph_node *node) @@ -374,7 +413,7 @@ ipcp_cloning_candidate_p (struct cgraph_ cgraph_node_name (node)); return false; } - if (!tree_versionable_function_p (node->decl)) + if (!ipcp_versionable_function_p (node)) { if (dump_file) fprintf (dump_file, "Not considering %s for cloning; body is not versionable.\n", @@ -677,7 +716,7 @@ ipcp_node_modifiable_p (struct cgraph_no { /* Once we will be able to do in-place replacement, we can be more lax here. */ - return tree_versionable_function_p (node->decl); + return ipcp_versionable_function_p (node); } /* Print count scale data structures. */ --- tree-inline.c (revision 148984) +++ tree-inline.c (local) @@ -119,7 +119,6 @@ eni_weights eni_time_weights; /* Prototypes. */ static tree declare_return_variable (copy_body_data *, tree, tree, tree *); -static bool inlinable_function_p (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 *); @@ -2436,26 +2435,32 @@ declare_return_variable (copy_body_data return var; } -/* 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; + + if (TYPE_P (node)) + *walk_subtrees = 0; + + return NULL_TREE; +} -/* 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. */ +/* Callback through walk_tree. Determine if we've got an aggregate + type that we can't support; return non-null if so. */ static tree -inline_forbidden_p_op (tree *nodep, int *walk_subtrees ATTRIBUTE_UNUSED, - void *fnp ATTRIBUTE_UNUSED) +cannot_copy_type_1 (tree *nodep, int *walk_subtrees ATTRIBUTE_UNUSED, + void *data ATTRIBUTE_UNUSED) { - tree node = *nodep; - tree t; + tree t, node = *nodep; if (TREE_CODE (node) == RECORD_TYPE || TREE_CODE (node) == UNION_TYPE) { @@ -2474,21 +2479,78 @@ inline_forbidden_p_op (tree *nodep, int 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; - } + return node; } return NULL_TREE; } -/* A callback for walk_gimple_seq to handle statements. Returns - non-NULL iff a function can not be inlined. Also sets the reason - why. */ +/* 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) +{ + const char *reason = fun->cannot_be_copied_reason; + tree step; + + /* 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) + { + reason = G_("function %q+F can never be copied " + "because it receives a non-local goto"); + goto fail; + } + + 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) + && 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; + } + + if (!TREE_STATIC (decl) && !DECL_EXTERNAL (decl) + && variably_modified_type_p (TREE_TYPE (decl), NULL) + && walk_tree_without_duplicates (&TREE_TYPE (decl), + cannot_copy_type_1, NULL)) + { + reason = G_("function %q+F can never be copied " + "because it uses variable sized variables"); + goto fail; + } + } + + fail: + fun->cannot_be_copied_reason = reason; + fun->cannot_be_copied_set = true; + return reason; +} + + +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, @@ -2597,21 +2659,6 @@ inline_forbidden_p_stmt (gimple_stmt_ite } 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; } @@ -2620,28 +2667,6 @@ inline_forbidden_p_stmt (gimple_stmt_ite 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. */ @@ -2649,12 +2674,18 @@ static bool inline_forbidden_p (tree fndecl) { 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; @@ -2664,31 +2695,12 @@ 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); return forbidden_p; } @@ -2696,8 +2708,8 @@ egress: /* 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; @@ -4304,17 +4316,11 @@ copy_static_chain (tree static_chain, co /* 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 copy_forbidden (DECL_STRUCT_FUNCTION (fndecl), fndecl) == NULL; } /* Delete all unreachable basic blocks and update callgraph. @@ -4420,7 +4426,8 @@ delete_unreachable_blocks_update_callgra 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, VEC(ipa_replace_map_p,gc)* 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; @@ -4547,7 +4554,8 @@ tree_function_versioning (tree old_decl, } /* 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, old_entry_block->frequency, + ENTRY_BLOCK_PTR, EXIT_BLOCK_PTR); if (DECL_RESULT (old_decl) != NULL_TREE) { @@ -4566,6 +4574,16 @@ tree_function_versioning (tree old_decl, insert_init_stmt (bb, VEC_pop (gimple, init_stmts)); } + /* Remap the nonlocal_goto_save_area, if any. */ + if (cfun->nonlocal_goto_save_area) + { + 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); free_dominance_info (CDI_DOMINATORS);