From 44662f681e8be6b3b7eafcec4afa0ecb8cc587cd Mon Sep 17 00:00:00 2001 From: Eric Botcazou Date: Fri, 28 Sep 2018 21:20:53 +0000 Subject: [PATCH] calls.c (expand_call): Try to do a tail call for thunks at -O0 too. * calls.c (expand_call): Try to do a tail call for thunks at -O0 too. * cgraph.h (struct cgraph_thunk_info): Add indirect_offset. (cgraph_node::create_thunk): Add indirect_offset parameter. (thunk_adjust): Likewise. * cgraph.c (cgraph_node::create_thunk): Add indirect_offset parameter and initialize the corresponding field with it. (cgraph_node::dump): Dump indirect_offset field. * cgraphclones.c (duplicate_thunk_for_node): Deal with indirect_offset. * cgraphunit.c (cgraph_node::analyze): Be prepared for external thunks. (thunk_adjust): Add indirect_offset parameter and deal with it. (cgraph_node::expand_thunk): Deal with the indirect_offset field and pass it to thunk_adjust. Do not call the target hook if it's non-zero or if the thunk is external or local. Fix formatting. Do not chain the RESULT_DECL to BLOCK_VARS. Pass the static chain to the target, if any, in the GIMPLE representation. * ipa-icf.c (sem_function::equals_wpa): Deal with indirect_offset. * lto-cgraph.c (lto_output_node): Write indirect_offset field. (input_node): Read indirect_offset field. * tree-inline.c (expand_call_inline): Pass indirect_offset field in the call to thunk_adjust. * tree-nested.c (struct nesting_info): Add thunk_p field. (create_nesting_tree): Set it. (convert_all_function_calls): Copy static chain from targets to thunks. (finalize_nesting_tree_1): Return early for thunks. (unnest_nesting_tree_1): Do not finalize thunks. (gimplify_all_functions): Do not gimplify thunks. cp/ * method.c (use_thunk): Adjust call to cgraph_node::create_thunk. ada/ * gcc-interface/decl.c (is_cplusplus_method): Do not require C++ convention on Interfaces. * gcc-interface/trans.c (Subprogram_Body_to_gnu): Try to create a bona-fide thunk and hand it over to the middle-end. (get_controlling_type): New function. (use_alias_for_thunk_p): Likewise. (thunk_labelno): New static variable. (make_covariant_thunk): New function. (maybe_make_gnu_thunk): Likewise. * gcc-interface/utils.c (finish_subprog_decl): Set DECL_CONTEXT of the result DECL here instead of... (end_subprog_body): ...here. Co-Authored-By: Pierre-Marie de Rodat From-SVN: r264701 --- gcc/ChangeLog | 30 +++++ gcc/ada/ChangeLog | 16 +++ gcc/ada/gcc-interface/decl.c | 6 +- gcc/ada/gcc-interface/trans.c | 242 ++++++++++++++++++++++++++++++++++ gcc/ada/gcc-interface/utils.c | 4 +- gcc/calls.c | 3 +- gcc/cgraph.c | 7 +- gcc/cgraph.h | 8 +- gcc/cgraphclones.c | 7 +- gcc/cgraphunit.c | 118 +++++++++++++---- gcc/cp/ChangeLog | 5 + gcc/cp/method.c | 2 +- gcc/ipa-icf.c | 2 + gcc/lto-cgraph.c | 5 +- gcc/tree-inline.c | 2 +- gcc/tree-nested.c | 33 ++++- 16 files changed, 441 insertions(+), 49 deletions(-) diff --git a/gcc/ChangeLog b/gcc/ChangeLog index 24afef245fc..96baa7d48e2 100644 --- a/gcc/ChangeLog +++ b/gcc/ChangeLog @@ -1,3 +1,33 @@ +2018-09-28 Eric Botcazou + Pierre-Marie de Rodat + + * calls.c (expand_call): Try to do a tail call for thunks at -O0 too. + * cgraph.h (struct cgraph_thunk_info): Add indirect_offset. + (cgraph_node::create_thunk): Add indirect_offset parameter. + (thunk_adjust): Likewise. + * cgraph.c (cgraph_node::create_thunk): Add indirect_offset parameter + and initialize the corresponding field with it. + (cgraph_node::dump): Dump indirect_offset field. + * cgraphclones.c (duplicate_thunk_for_node): Deal with indirect_offset. + * cgraphunit.c (cgraph_node::analyze): Be prepared for external thunks. + (thunk_adjust): Add indirect_offset parameter and deal with it. + (cgraph_node::expand_thunk): Deal with the indirect_offset field and + pass it to thunk_adjust. Do not call the target hook if it's non-zero + or if the thunk is external or local. Fix formatting. Do not chain + the RESULT_DECL to BLOCK_VARS. Pass the static chain to the target, + if any, in the GIMPLE representation. + * ipa-icf.c (sem_function::equals_wpa): Deal with indirect_offset. + * lto-cgraph.c (lto_output_node): Write indirect_offset field. + (input_node): Read indirect_offset field. + * tree-inline.c (expand_call_inline): Pass indirect_offset field in the + call to thunk_adjust. + * tree-nested.c (struct nesting_info): Add thunk_p field. + (create_nesting_tree): Set it. + (convert_all_function_calls): Copy static chain from targets to thunks. + (finalize_nesting_tree_1): Return early for thunks. + (unnest_nesting_tree_1): Do not finalize thunks. + (gimplify_all_functions): Do not gimplify thunks. + 2018-09-28 David Malcolm * opt-suggestions.c (option_proposer::build_option_suggestions): diff --git a/gcc/ada/ChangeLog b/gcc/ada/ChangeLog index ce20db3d0d0..c18c4c8852e 100644 --- a/gcc/ada/ChangeLog +++ b/gcc/ada/ChangeLog @@ -1,3 +1,19 @@ +2018-09-28 Eric Botcazou + Pierre-Marie de Rodat + + * gcc-interface/decl.c (is_cplusplus_method): Do not require C++ + convention on Interfaces. + * gcc-interface/trans.c (Subprogram_Body_to_gnu): Try to create a + bona-fide thunk and hand it over to the middle-end. + (get_controlling_type): New function. + (use_alias_for_thunk_p): Likewise. + (thunk_labelno): New static variable. + (make_covariant_thunk): New function. + (maybe_make_gnu_thunk): Likewise. + * gcc-interface/utils.c (finish_subprog_decl): Set DECL_CONTEXT of the + result DECL here instead of... + (end_subprog_body): ...here. + 2018-09-27 Martin Sebor * gcc-interface/utils.c (make_packable_type): Introduce a temporary diff --git a/gcc/ada/gcc-interface/decl.c b/gcc/ada/gcc-interface/decl.c index 8dd62f73d5e..4a1845a33b6 100644 --- a/gcc/ada/gcc-interface/decl.c +++ b/gcc/ada/gcc-interface/decl.c @@ -4851,15 +4851,15 @@ is_cplusplus_method (Entity_Id gnat_entity) if (Convention (gnat_entity) != Convention_CPP) return false; - /* And that the type of the first parameter (indirectly) has it too. */ + /* And that the type of the first parameter (indirectly) has it too, but + we make an exception for Interfaces because they need not be imported. */ Entity_Id gnat_first = First_Formal (gnat_entity); if (No (gnat_first)) return false; - Entity_Id gnat_type = Etype (gnat_first); if (Is_Access_Type (gnat_type)) gnat_type = Directly_Designated_Type (gnat_type); - if (Convention (gnat_type) != Convention_CPP) + if (Convention (gnat_type) != Convention_CPP && !Is_Interface (gnat_type)) return false; /* This is the main case: a C++ virtual method imported as a primitive diff --git a/gcc/ada/gcc-interface/trans.c b/gcc/ada/gcc-interface/trans.c index 12f6a87437f..53513e538f1 100644 --- a/gcc/ada/gcc-interface/trans.c +++ b/gcc/ada/gcc-interface/trans.c @@ -247,6 +247,7 @@ static bool set_end_locus_from_node (tree, Node_Id); static int lvalue_required_p (Node_Id, tree, bool, bool); static tree build_raise_check (int, enum exception_info_kind); static tree create_init_temporary (const char *, tree, tree *, Node_Id); +static bool maybe_make_gnu_thunk (Entity_Id gnat_thunk, tree gnu_thunk); /* Hooks for debug info back-ends, only supported and used in a restricted set of configurations. */ @@ -3791,6 +3792,11 @@ Subprogram_Body_to_gnu (Node_Id gnat_node) if (Was_Expression_Function (gnat_node)) DECL_DISREGARD_INLINE_LIMITS (gnu_subprog_decl) = 1; + /* Try to create a bona-fide thunk and hand it over to the middle-end. */ + if (Is_Thunk (gnat_subprog_id) + && maybe_make_gnu_thunk (gnat_subprog_id, gnu_subprog_decl)) + return; + /* Initialize the information structure for the function. */ allocate_struct_function (gnu_subprog_decl, false); gnu_subprog_language = ggc_cleared_alloc (); @@ -10333,6 +10339,242 @@ get_elaboration_procedure (void) return gnu_elab_proc_stack->last (); } +/* Return the controlling type of a dispatching subprogram. */ + +static Entity_Id +get_controlling_type (Entity_Id subprog) +{ + /* This is modelled on Expand_Interface_Thunk. */ + Entity_Id controlling_type = Etype (First_Formal (subprog)); + if (Is_Access_Type (controlling_type)) + controlling_type = Directly_Designated_Type (controlling_type); + controlling_type = Underlying_Type (controlling_type); + if (Is_Concurrent_Type (controlling_type)) + controlling_type = Corresponding_Record_Type (controlling_type); + controlling_type = Base_Type (controlling_type); + return controlling_type; +} + +/* Return whether we should use an alias for the TARGET of a thunk + in order to make the call generated in the thunk local. */ + +static bool +use_alias_for_thunk_p (tree target) +{ + /* We cannot generate a local call in this case. */ + if (DECL_EXTERNAL (target)) + return false; + + /* The call is already local in this case. */ + if (TREE_CODE (DECL_CONTEXT (target)) == FUNCTION_DECL) + return false; + + return TARGET_USE_LOCAL_THUNK_ALIAS_P (target); +} + +static GTY(()) unsigned long thunk_labelno = 0; + +/* Create an alias for TARGET to be used as the target of a thunk. */ + +static tree +make_alias_for_thunk (tree target) +{ + char buf[64]; + targetm.asm_out.generate_internal_label (buf, "LTHUNK", thunk_labelno++); + + tree alias = build_decl (DECL_SOURCE_LOCATION (target), TREE_CODE (target), + get_identifier (buf), TREE_TYPE (target)); + + DECL_LANG_SPECIFIC (alias) = DECL_LANG_SPECIFIC (target); + DECL_CONTEXT (alias) = DECL_CONTEXT (target); + TREE_READONLY (alias) = TREE_READONLY (target); + TREE_THIS_VOLATILE (alias) = TREE_THIS_VOLATILE (target); + DECL_ARTIFICIAL (alias) = 1; + DECL_INITIAL (alias) = error_mark_node; + DECL_ARGUMENTS (alias) = copy_list (DECL_ARGUMENTS (target)); + TREE_ADDRESSABLE (alias) = 1; + SET_DECL_ASSEMBLER_NAME (alias, DECL_NAME (alias)); + + cgraph_node *n = cgraph_node::create_same_body_alias (alias, target); + gcc_assert (n); + + return alias; +} + +/* Create the covariant part of the {GNAT,GNU}_THUNK. */ + +static tree +make_covariant_thunk (Entity_Id gnat_thunk, tree gnu_thunk) +{ + tree gnu_name = create_concat_name (gnat_thunk, "CV"); + tree gnu_cv_thunk + = build_decl (DECL_SOURCE_LOCATION (gnu_thunk), TREE_CODE (gnu_thunk), + gnu_name, TREE_TYPE (gnu_thunk)); + + DECL_ARGUMENTS (gnu_cv_thunk) = copy_list (DECL_ARGUMENTS (gnu_thunk)); + DECL_RESULT (gnu_cv_thunk) = copy_node (DECL_RESULT (gnu_thunk)); + DECL_CONTEXT (DECL_RESULT (gnu_cv_thunk)) = gnu_cv_thunk; + + DECL_LANG_SPECIFIC (gnu_cv_thunk) = DECL_LANG_SPECIFIC (gnu_thunk); + DECL_CONTEXT (gnu_cv_thunk) = DECL_CONTEXT (gnu_thunk); + TREE_READONLY (gnu_cv_thunk) = TREE_READONLY (gnu_thunk); + TREE_THIS_VOLATILE (gnu_cv_thunk) = TREE_THIS_VOLATILE (gnu_thunk); + TREE_PUBLIC (gnu_cv_thunk) = TREE_PUBLIC (gnu_thunk); + DECL_ARTIFICIAL (gnu_cv_thunk) = 1; + + return gnu_cv_thunk; +} + +/* Try to create a GNU thunk for {GNAT,GNU}_THUNK and return true on success. + + GNU thunks are more efficient than GNAT thunks because they don't call into + the runtime to retrieve the offset used in the displacement operation, but + they are tailored to C++ and thus too limited to support the full range of + thunks generated in Ada. Here's the complete list of limitations: + + 1. Multi-controlling thunks, i.e thunks with more than one controlling + parameter, are simply not supported. + + 2. Covariant thunks, i.e. thunks for which the result is also controlling, + are split into a pair of (this, covariant-only) thunks. + + 3. Variable-offset thunks, i.e. thunks for which the offset depends on the + object and not only on its type, are supported as 2nd class citizens. + + 4. External thunks, i.e. thunks for which the target is not declared in + the same unit as the thunk, are supported as 2nd class citizens. + + 5. Local thunks, i.e. thunks generated for a local type, are supported as + 2nd class citizens. */ + +static bool +maybe_make_gnu_thunk (Entity_Id gnat_thunk, tree gnu_thunk) +{ + const Entity_Id gnat_target = Thunk_Entity (gnat_thunk); + + /* Check that the first formal of the target is the only controlling one. */ + Entity_Id gnat_formal = First_Formal (gnat_target); + if (!Is_Controlling_Formal (gnat_formal)) + return false; + for (gnat_formal = Next_Formal (gnat_formal); + Present (gnat_formal); + gnat_formal = Next_Formal (gnat_formal)) + if (Is_Controlling_Formal (gnat_formal)) + return false; + + /* Look for the types that control the target and the thunk. */ + const Entity_Id gnat_controlling_type = get_controlling_type (gnat_target); + const Entity_Id gnat_interface_type = get_controlling_type (gnat_thunk); + + /* Now compute whether the former covers the latter. */ + const Entity_Id gnat_interface_tag + = Is_Interface (gnat_interface_type) + ? Find_Interface_Tag (gnat_controlling_type, gnat_interface_type) + : Empty; + tree gnu_interface_tag + = Present (gnat_interface_tag) + ? gnat_to_gnu_field_decl (gnat_interface_tag) + : NULL_TREE; + tree gnu_interface_offset + = gnu_interface_tag ? byte_position (gnu_interface_tag) : NULL_TREE; + + /* There are three ways to retrieve the offset between the interface view + and the base object. Either the controlling type covers the interface + type and the offset of the corresponding tag is fixed, in which case it + can be statically encoded in the thunk (see FIXED_OFFSET below). Or the + controlling type doesn't cover the interface type but is of fixed size, + in which case the offset is stored in the dispatch table, two pointers + above the dispatch table address (see VIRTUAL_VALUE below). Otherwise, + the offset is variable and is stored right after the tag in every object + (see INDIRECT_OFFSET below). See also a-tags.ads for more details. */ + HOST_WIDE_INT fixed_offset, virtual_value, indirect_offset; + tree virtual_offset; + + if (gnu_interface_offset && TREE_CODE (gnu_interface_offset) == INTEGER_CST) + { + fixed_offset = - tree_to_shwi (gnu_interface_offset); + virtual_value = 0; + virtual_offset = NULL_TREE; + indirect_offset = 0; + } + else if (!gnu_interface_offset + && !Is_Variable_Size_Record (gnat_controlling_type)) + { + fixed_offset = 0; + virtual_value = - 2 * (HOST_WIDE_INT) (POINTER_SIZE / BITS_PER_UNIT); + virtual_offset = build_int_cst (integer_type_node, virtual_value); + indirect_offset = 0; + } + else + { + /* Covariant thunks with variable offset are not supported. */ + if (Has_Controlling_Result (gnat_target)) + return false; + + fixed_offset = 0; + virtual_value = 0; + virtual_offset = NULL_TREE; + indirect_offset = (HOST_WIDE_INT) (POINTER_SIZE / BITS_PER_UNIT); + } + + tree gnu_target = gnat_to_gnu_entity (gnat_target, NULL_TREE, false); + + /* Thunk and target must have the same nesting level, if any. */ + gcc_assert (DECL_CONTEXT (gnu_thunk) == DECL_CONTEXT (gnu_target)); + + /* If the target returns by invisible reference and is external, apply the + same transformation as Subprogram_Body_to_gnu here. */ + if (TREE_ADDRESSABLE (TREE_TYPE (gnu_target)) + && DECL_EXTERNAL (gnu_target) + && !POINTER_TYPE_P (TREE_TYPE (DECL_RESULT (gnu_target)))) + { + TREE_TYPE (DECL_RESULT (gnu_target)) + = build_reference_type (TREE_TYPE (DECL_RESULT (gnu_target))); + relayout_decl (DECL_RESULT (gnu_target)); + } + + /* The thunk expander requires the return types of thunk and target to be + compatible, which is not fully the case with the CICO mechanism. */ + if (TYPE_CI_CO_LIST (TREE_TYPE (gnu_thunk))) + { + tree gnu_target_type = TREE_TYPE (gnu_target); + gcc_assert (TYPE_CI_CO_LIST (gnu_target_type)); + TYPE_CANONICAL (TREE_TYPE (TREE_TYPE (gnu_thunk))) + = TYPE_CANONICAL (TREE_TYPE (gnu_target_type)); + } + + cgraph_node *target_node = cgraph_node::get_create (gnu_target); + + /* If the return type of the target is a controlling type, then we need + both an usual this thunk and a covariant thunk in this order: + + this thunk --> covariant thunk --> target + + For covariant thunks, we can only handle a fixed offset. */ + if (Has_Controlling_Result (gnat_target)) + { + gcc_assert (fixed_offset < 0); + tree gnu_cv_thunk = make_covariant_thunk (gnat_thunk, gnu_thunk); + target_node->create_thunk (gnu_cv_thunk, gnu_target, false, + - fixed_offset, 0, 0, + NULL_TREE, gnu_target); + + gnu_target = gnu_cv_thunk; + } + + /* We may also need to create an alias for the target in order to make + the call local, depending on the linkage of the target. */ + tree gnu_alias = use_alias_for_thunk_p (gnu_target) + ? make_alias_for_thunk (gnu_target) + : gnu_target; + + target_node->create_thunk (gnu_thunk, gnu_target, true, + fixed_offset, virtual_value, indirect_offset, + virtual_offset, gnu_alias); + + return true; +} + /* Initialize the table that maps GNAT codes to GCC codes for simple binary and unary operations. */ diff --git a/gcc/ada/gcc-interface/utils.c b/gcc/ada/gcc-interface/utils.c index 9530165a0c1..1ce2f725927 100644 --- a/gcc/ada/gcc-interface/utils.c +++ b/gcc/ada/gcc-interface/utils.c @@ -3294,6 +3294,7 @@ finish_subprog_decl (tree decl, tree asm_name, tree type) DECL_ARTIFICIAL (result_decl) = 1; DECL_IGNORED_P (result_decl) = 1; + DECL_CONTEXT (result_decl) = decl; DECL_BY_REFERENCE (result_decl) = TREE_ADDRESSABLE (type); DECL_RESULT (decl) = result_decl; @@ -3369,9 +3370,6 @@ end_subprog_body (tree body) DECL_INITIAL (fndecl) = current_binding_level->block; gnat_poplevel (); - /* Mark the RESULT_DECL as being in this subprogram. */ - DECL_CONTEXT (DECL_RESULT (fndecl)) = fndecl; - /* The body should be a BIND_EXPR whose BLOCK is the top-level one. */ if (TREE_CODE (body) == BIND_EXPR) { diff --git a/gcc/calls.c b/gcc/calls.c index e9660b62424..80af5c3b3b3 100644 --- a/gcc/calls.c +++ b/gcc/calls.c @@ -3610,9 +3610,8 @@ expand_call (tree exp, rtx target, int ignore) pushed these optimizations into -O2. Don't try if we're already expanding a call, as that means we're an argument. Don't try if there's cleanups, as we know there's code to follow the call. */ - if (currently_expanding_call++ != 0 - || !flag_optimize_sibling_calls + || (!flag_optimize_sibling_calls && !CALL_FROM_THUNK_P (exp)) || args_size.var || dbg_cnt (tail_call) == false) try_tail_call = 0; diff --git a/gcc/cgraph.c b/gcc/cgraph.c index 148f29ea749..8a03f3d6828 100644 --- a/gcc/cgraph.c +++ b/gcc/cgraph.c @@ -617,6 +617,7 @@ cgraph_node * cgraph_node::create_thunk (tree alias, tree, bool this_adjusting, HOST_WIDE_INT fixed_offset, HOST_WIDE_INT virtual_value, + HOST_WIDE_INT indirect_offset, tree virtual_offset, tree real_alias) { @@ -635,6 +636,7 @@ cgraph_node::create_thunk (tree alias, tree, bool this_adjusting, node->thunk.fixed_offset = fixed_offset; node->thunk.virtual_value = virtual_value; + node->thunk.indirect_offset = indirect_offset; node->thunk.alias = real_alias; node->thunk.this_adjusting = this_adjusting; node->thunk.virtual_offset_p = virtual_offset != NULL; @@ -2099,10 +2101,11 @@ cgraph_node::dump (FILE *f) fprintf (f, " of %s (asm: %s)", lang_hooks.decl_printable_name (thunk.alias, 2), IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (thunk.alias))); - fprintf (f, " fixed offset %i virtual value %i has " - "virtual offset %i)\n", + fprintf (f, " fixed offset %i virtual value %i indirect_offset %i " + "has virtual offset %i\n", (int)thunk.fixed_offset, (int)thunk.virtual_value, + (int)thunk.indirect_offset, (int)thunk.virtual_offset_p); } if (alias && thunk.alias diff --git a/gcc/cgraph.h b/gcc/cgraph.h index 2b00f0165fa..71c54537b93 100644 --- a/gcc/cgraph.h +++ b/gcc/cgraph.h @@ -666,6 +666,10 @@ struct GTY(()) cgraph_thunk_info { VIRTUAL_OFFSET_P is true. */ HOST_WIDE_INT virtual_value; + /* Offset from "this" to get the offset to adjust "this". Zero means: this + offset is to be ignored. */ + HOST_WIDE_INT indirect_offset; + /* Thunk target, i.e. the method that this thunk wraps. Depending on the TARGET_USE_LOCAL_THUNK_ALIAS_P macro, this may have to be a new alias. */ tree alias; @@ -1033,6 +1037,7 @@ public: cgraph_node * create_thunk (tree alias, tree, bool this_adjusting, HOST_WIDE_INT fixed_offset, HOST_WIDE_INT virtual_value, + HOST_WIDE_INT indirect_offset, tree virtual_offset, tree real_alias); @@ -2373,7 +2378,8 @@ void cgraphunit_c_finalize (void); IN_SSA is true if the gimple is in SSA. */ basic_block init_lowered_empty_function (tree, bool, profile_count); -tree thunk_adjust (gimple_stmt_iterator *, tree, bool, HOST_WIDE_INT, tree); +tree thunk_adjust (gimple_stmt_iterator *, tree, bool, HOST_WIDE_INT, tree, + HOST_WIDE_INT); /* In cgraphclones.c */ tree clone_function_name_1 (const char *, const char *); diff --git a/gcc/cgraphclones.c b/gcc/cgraphclones.c index 0c0a94b04a3..2af45bd4fec 100644 --- a/gcc/cgraphclones.c +++ b/gcc/cgraphclones.c @@ -274,10 +274,11 @@ duplicate_thunk_for_node (cgraph_node *thunk, cgraph_node *node) cgraph_edge *cs; for (cs = node->callers; cs; cs = cs->next_caller) if (cs->caller->thunk.thunk_p - && cs->caller->thunk.this_adjusting == thunk->thunk.this_adjusting && cs->caller->thunk.fixed_offset == thunk->thunk.fixed_offset - && cs->caller->thunk.virtual_offset_p == thunk->thunk.virtual_offset_p - && cs->caller->thunk.virtual_value == thunk->thunk.virtual_value) + && cs->caller->thunk.virtual_value == thunk->thunk.virtual_value + && cs->caller->thunk.indirect_offset == thunk->thunk.indirect_offset + && cs->caller->thunk.this_adjusting == thunk->thunk.this_adjusting + && cs->caller->thunk.virtual_offset_p == thunk->thunk.virtual_offset_p) return cs->caller; tree new_decl; diff --git a/gcc/cgraphunit.c b/gcc/cgraphunit.c index ec490d75bd1..c0baaeaefa4 100644 --- a/gcc/cgraphunit.c +++ b/gcc/cgraphunit.c @@ -623,20 +623,18 @@ cgraph_node::analyze (void) callees->can_throw_external = !TREE_NOTHROW (t->decl); /* Target code in expand_thunk may need the thunk's target to be analyzed, so recurse here. */ - if (!t->analyzed) + if (!t->analyzed && t->definition) t->analyze (); if (t->alias) { t = t->get_alias_target (); - if (!t->analyzed) + if (!t->analyzed && t->definition) t->analyze (); } - if (!expand_thunk (false, false)) - { - thunk.alias = NULL; - return; - } + bool ret = expand_thunk (false, false); thunk.alias = NULL; + if (!ret) + return; } if (alias) resolve_alias (cgraph_node::get (alias_target), transparent_alias); @@ -1609,15 +1607,16 @@ init_lowered_empty_function (tree decl, bool in_ssa, profile_count count) return bb; } -/* Adjust PTR by the constant FIXED_OFFSET, and by the vtable - offset indicated by VIRTUAL_OFFSET, if that is - non-null. THIS_ADJUSTING is nonzero for a this adjusting thunk and - zero for a result adjusting thunk. */ +/* Adjust PTR by the constant FIXED_OFFSET, by the vtable offset indicated by + VIRTUAL_OFFSET, and by the indirect offset indicated by INDIRECT_OFFSET, if + it is non-null. THIS_ADJUSTING is nonzero for a this adjusting thunk and zero + for a result adjusting thunk. */ tree thunk_adjust (gimple_stmt_iterator * bsi, tree ptr, bool this_adjusting, - HOST_WIDE_INT fixed_offset, tree virtual_offset) + HOST_WIDE_INT fixed_offset, tree virtual_offset, + HOST_WIDE_INT indirect_offset) { gassign *stmt; tree ret; @@ -1632,6 +1631,16 @@ thunk_adjust (gimple_stmt_iterator * bsi, gsi_insert_after (bsi, stmt, GSI_NEW_STMT); } + if (!vtable_entry_type && (virtual_offset || indirect_offset != 0)) + { + tree vfunc_type = make_node (FUNCTION_TYPE); + TREE_TYPE (vfunc_type) = integer_type_node; + TYPE_ARG_TYPES (vfunc_type) = NULL_TREE; + layout_type (vfunc_type); + + vtable_entry_type = build_pointer_type (vfunc_type); + } + /* If there's a virtual offset, look up that value in the vtable and adjust the pointer again. */ if (virtual_offset) @@ -1640,16 +1649,6 @@ thunk_adjust (gimple_stmt_iterator * bsi, tree vtabletmp2; tree vtabletmp3; - if (!vtable_entry_type) - { - tree vfunc_type = make_node (FUNCTION_TYPE); - TREE_TYPE (vfunc_type) = integer_type_node; - TYPE_ARG_TYPES (vfunc_type) = NULL_TREE; - layout_type (vfunc_type); - - vtable_entry_type = build_pointer_type (vfunc_type); - } - vtabletmp = create_tmp_reg (build_pointer_type (build_pointer_type (vtable_entry_type)), "vptr"); @@ -1687,6 +1686,41 @@ thunk_adjust (gimple_stmt_iterator * bsi, GSI_CONTINUE_LINKING); } + /* Likewise for an offset that is stored in the object that contains the + vtable. */ + if (indirect_offset != 0) + { + tree offset_ptr, offset_tree; + + /* Get the address of the offset. */ + offset_ptr + = create_tmp_reg (build_pointer_type + (build_pointer_type (vtable_entry_type)), + "offset_ptr"); + stmt = gimple_build_assign (offset_ptr, + build1 (NOP_EXPR, TREE_TYPE (offset_ptr), + ptr)); + gsi_insert_after (bsi, stmt, GSI_NEW_STMT); + + stmt = gimple_build_assign + (offset_ptr, + fold_build_pointer_plus_hwi_loc (input_location, offset_ptr, + indirect_offset)); + gsi_insert_after (bsi, stmt, GSI_NEW_STMT); + + /* Get the offset itself. */ + offset_tree = create_tmp_reg (TREE_TYPE (TREE_TYPE (offset_ptr)), + "offset"); + stmt = gimple_build_assign (offset_tree, + build_simple_mem_ref (offset_ptr)); + gsi_insert_after (bsi, stmt, GSI_NEW_STMT); + + /* Adjust the `this' pointer. */ + ptr = fold_build_pointer_plus_loc (input_location, ptr, offset_tree); + ptr = force_gimple_operand_gsi (bsi, ptr, true, NULL_TREE, false, + GSI_CONTINUE_LINKING); + } + if (!this_adjusting && fixed_offset != 0) /* Adjust the pointer by the constant. */ @@ -1725,6 +1759,7 @@ cgraph_node::expand_thunk (bool output_asm_thunks, bool force_gimple_thunk) bool this_adjusting = thunk.this_adjusting; HOST_WIDE_INT fixed_offset = thunk.fixed_offset; HOST_WIDE_INT virtual_value = thunk.virtual_value; + HOST_WIDE_INT indirect_offset = thunk.indirect_offset; tree virtual_offset = NULL; tree alias = callees->callee->decl; tree thunk_fndecl = decl; @@ -1735,7 +1770,11 @@ cgraph_node::expand_thunk (bool output_asm_thunks, bool force_gimple_thunk) if (thunk.add_pointer_bounds_args) return false; - if (!force_gimple_thunk && this_adjusting + if (!force_gimple_thunk + && this_adjusting + && indirect_offset == 0 + && !DECL_EXTERNAL (alias) + && !DECL_STATIC_CHAIN (alias) && targetm.asm_out.can_output_mi_thunk (thunk_fndecl, fixed_offset, virtual_value, alias)) { @@ -1838,8 +1877,8 @@ cgraph_node::expand_thunk (bool output_asm_thunks, bool force_gimple_thunk) resdecl = build_decl (input_location, RESULT_DECL, 0, restype); DECL_ARTIFICIAL (resdecl) = 1; DECL_IGNORED_P (resdecl) = 1; + DECL_CONTEXT (resdecl) = thunk_fndecl; DECL_RESULT (thunk_fndecl) = resdecl; - DECL_CONTEXT (DECL_RESULT (thunk_fndecl)) = thunk_fndecl; } else resdecl = DECL_RESULT (thunk_fndecl); @@ -1876,8 +1915,11 @@ cgraph_node::expand_thunk (bool output_asm_thunks, bool force_gimple_thunk) restmp = resdecl; if (VAR_P (restmp)) - add_local_decl (cfun, restmp); - BLOCK_VARS (DECL_INITIAL (current_function_decl)) = restmp; + { + add_local_decl (cfun, restmp); + BLOCK_VARS (DECL_INITIAL (current_function_decl)) + = restmp; + } } else restmp = create_tmp_var (restype, "retval"); @@ -1894,7 +1936,7 @@ cgraph_node::expand_thunk (bool output_asm_thunks, bool force_gimple_thunk) if (this_adjusting) { vargs.quick_push (thunk_adjust (&bsi, a, 1, fixed_offset, - virtual_offset)); + virtual_offset, indirect_offset)); arg = DECL_CHAIN (a); i = 1; } @@ -1919,6 +1961,25 @@ cgraph_node::expand_thunk (bool output_asm_thunks, bool force_gimple_thunk) call = gimple_build_call_vec (build_fold_addr_expr_loc (0, alias), vargs); callees->call_stmt = call; gimple_call_set_from_thunk (call, true); + if (DECL_STATIC_CHAIN (alias)) + { + tree p = DECL_STRUCT_FUNCTION (alias)->static_chain_decl; + tree type = TREE_TYPE (p); + tree decl = build_decl (DECL_SOURCE_LOCATION (thunk_fndecl), + PARM_DECL, create_tmp_var_name ("CHAIN"), + type); + DECL_ARTIFICIAL (decl) = 1; + DECL_IGNORED_P (decl) = 1; + TREE_USED (decl) = 1; + DECL_CONTEXT (decl) = thunk_fndecl; + DECL_ARG_TYPE (decl) = type; + TREE_READONLY (decl) = 1; + + struct function *sf = DECL_STRUCT_FUNCTION (thunk_fndecl); + sf->static_chain_decl = decl; + + gimple_call_set_chain (call, decl); + } /* Return slot optimization is always possible and in fact requred to return values with DECL_BY_REFERENCE. */ @@ -1979,7 +2040,8 @@ cgraph_node::expand_thunk (bool output_asm_thunks, bool force_gimple_thunk) } restmp = thunk_adjust (&bsi, restmp, /*this_adjusting=*/0, - fixed_offset, virtual_offset); + fixed_offset, virtual_offset, + indirect_offset); if (true_label) { gimple *stmt; diff --git a/gcc/cp/ChangeLog b/gcc/cp/ChangeLog index f5d602de026..e10ada2d7b6 100644 --- a/gcc/cp/ChangeLog +++ b/gcc/cp/ChangeLog @@ -1,3 +1,8 @@ +2018-09-28 Eric Botcazou + Pierre-Marie de Rodat + + * method.c (use_thunk): Adjust call to cgraph_node::create_thunk. + 2018-09-28 Richard Biener * error.c (cp_print_error_function): Simplify by eliding diff --git a/gcc/cp/method.c b/gcc/cp/method.c index 5379b4d3d09..2de2d105224 100644 --- a/gcc/cp/method.c +++ b/gcc/cp/method.c @@ -375,7 +375,7 @@ use_thunk (tree thunk_fndecl, bool emit_p) gcc_checking_assert (funcn); thunk_node = funcn->create_thunk (thunk_fndecl, function, this_adjusting, fixed_offset, virtual_value, - virtual_offset, alias); + 0, virtual_offset, alias); if (DECL_ONE_ONLY (function)) thunk_node->add_to_same_comdat_group (funcn); diff --git a/gcc/ipa-icf.c b/gcc/ipa-icf.c index 8a6a7a3f32f..3c54f8d4b6d 100644 --- a/gcc/ipa-icf.c +++ b/gcc/ipa-icf.c @@ -593,6 +593,8 @@ sem_function::equals_wpa (sem_item *item, return return_false_with_msg ("thunk fixed_offset mismatch"); if (cnode->thunk.virtual_value != cnode2->thunk.virtual_value) return return_false_with_msg ("thunk virtual_value mismatch"); + if (cnode->thunk.indirect_offset != cnode2->thunk.indirect_offset) + return return_false_with_msg ("thunk indirect_offset mismatch"); if (cnode->thunk.this_adjusting != cnode2->thunk.this_adjusting) return return_false_with_msg ("thunk this_adjusting mismatch"); if (cnode->thunk.virtual_offset_p != cnode2->thunk.virtual_offset_p) diff --git a/gcc/lto-cgraph.c b/gcc/lto-cgraph.c index 6d9eea13f22..99998cc3c75 100644 --- a/gcc/lto-cgraph.c +++ b/gcc/lto-cgraph.c @@ -556,6 +556,7 @@ lto_output_node (struct lto_simple_output_block *ob, struct cgraph_node *node, + (node->thunk.add_pointer_bounds_args != 0) * 8); streamer_write_uhwi_stream (ob->main_stream, node->thunk.fixed_offset); streamer_write_uhwi_stream (ob->main_stream, node->thunk.virtual_value); + streamer_write_uhwi_stream (ob->main_stream, node->thunk.indirect_offset); } streamer_write_hwi_stream (ob->main_stream, node->profile_id); if (DECL_STATIC_CONSTRUCTOR (node->decl)) @@ -1271,10 +1272,12 @@ input_node (struct lto_file_decl_data *file_data, int type = streamer_read_uhwi (ib); HOST_WIDE_INT fixed_offset = streamer_read_uhwi (ib); HOST_WIDE_INT virtual_value = streamer_read_uhwi (ib); + HOST_WIDE_INT indirect_offset = streamer_read_uhwi (ib); node->thunk.fixed_offset = fixed_offset; - node->thunk.this_adjusting = (type & 2); node->thunk.virtual_value = virtual_value; + node->thunk.indirect_offset = indirect_offset; + node->thunk.this_adjusting = (type & 2); node->thunk.virtual_offset_p = (type & 4); node->thunk.add_pointer_bounds_args = (type & 8); } diff --git a/gcc/tree-inline.c b/gcc/tree-inline.c index 42c33d23b9b..2eac7b7cc4a 100644 --- a/gcc/tree-inline.c +++ b/gcc/tree-inline.c @@ -4473,7 +4473,7 @@ expand_call_inline (basic_block bb, gimple *stmt, copy_body_data *id) GSI_NEW_STMT); gcc_assert (id->src_node->thunk.this_adjusting); op = thunk_adjust (&iter, op, 1, id->src_node->thunk.fixed_offset, - virtual_offset); + virtual_offset, id->src_node->thunk.indirect_offset); gimple_call_set_arg (stmt, 0, op); gimple_call_set_fndecl (stmt, edge->callee->decl); diff --git a/gcc/tree-nested.c b/gcc/tree-nested.c index 4c8eda94f14..4579b4c5839 100644 --- a/gcc/tree-nested.c +++ b/gcc/tree-nested.c @@ -104,6 +104,7 @@ struct nesting_info tree chain_decl; tree nl_goto_field; + bool thunk_p; bool any_parm_remapped; bool any_tramp_created; bool any_descr_created; @@ -834,6 +835,7 @@ create_nesting_tree (struct cgraph_node *cgn) info->mem_refs = new hash_set; info->suppress_expansion = BITMAP_ALLOC (&nesting_info_bitmap_obstack); info->context = cgn->decl; + info->thunk_p = cgn->thunk.thunk_p; for (cgn = cgn->nested; cgn ; cgn = cgn->next_nested) { @@ -2786,6 +2788,8 @@ convert_all_function_calls (struct nesting_info *root) within the debugger. */ FOR_EACH_NEST_INFO (n, root) { + if (n->thunk_p) + continue; tree decl = n->context; if (!optimize) { @@ -2806,6 +2810,14 @@ convert_all_function_calls (struct nesting_info *root) chain_count += DECL_STATIC_CHAIN (decl); } + FOR_EACH_NEST_INFO (n, root) + if (n->thunk_p) + { + tree decl = n->context; + tree alias = cgraph_node::get (decl)->thunk.alias; + DECL_STATIC_CHAIN (decl) = DECL_STATIC_CHAIN (alias); + } + /* Walk the functions and perform transformations. Note that these transformations can induce new uses of the static chain, which in turn require re-examining all users of the decl. */ @@ -2825,12 +2837,22 @@ convert_all_function_calls (struct nesting_info *root) FOR_EACH_NEST_INFO (n, root) { + if (n->thunk_p) + continue; tree decl = n->context; walk_function (convert_tramp_reference_stmt, convert_tramp_reference_op, n); walk_function (convert_gimple_call, NULL, n); chain_count += DECL_STATIC_CHAIN (decl); } + + FOR_EACH_NEST_INFO (n, root) + if (n->thunk_p) + { + tree decl = n->context; + tree alias = cgraph_node::get (decl)->thunk.alias; + DECL_STATIC_CHAIN (decl) = DECL_STATIC_CHAIN (alias); + } } while (chain_count != old_chain_count); @@ -3055,12 +3077,13 @@ build_init_call_stmt (struct nesting_info *info, tree decl, tree field, static void finalize_nesting_tree_1 (struct nesting_info *root) { - gimple_seq stmt_list; + gimple_seq stmt_list = NULL; gimple *stmt; tree context = root->context; struct function *sf; - stmt_list = NULL; + if (root->thunk_p) + return; /* If we created a non-local frame type or decl, we need to lay them out at this time. */ @@ -3340,7 +3363,8 @@ unnest_nesting_tree_1 (struct nesting_info *root) if (node->origin) { node->unnest (); - cgraph_node::finalize_function (root->context, true); + if (!root->thunk_p) + cgraph_node::finalize_function (root->context, true); } } @@ -3380,7 +3404,8 @@ gimplify_all_functions (struct cgraph_node *root) if (!gimple_body (root->decl)) gimplify_function_tree (root->decl); for (iter = root->nested; iter; iter = iter->next_nested) - gimplify_all_functions (iter); + if (!iter->thunk.thunk_p) + gimplify_all_functions (iter); } /* Main entry point for this pass. Process FNDECL and all of its nested -- 2.30.2