diff options
Diffstat (limited to 'gcc/symtab-thunks.cc')
-rw-r--r-- | gcc/symtab-thunks.cc | 639 |
1 files changed, 639 insertions, 0 deletions
diff --git a/gcc/symtab-thunks.cc b/gcc/symtab-thunks.cc new file mode 100644 index 0000000..1a4aaa2 --- /dev/null +++ b/gcc/symtab-thunks.cc @@ -0,0 +1,639 @@ +/* Support for thunks in symbol table. + Copyright (C) 2003-2020 Free Software Foundation, Inc. + Contributed by Jan Hubicka + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 3, or (at your option) any later +version. + +GCC is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "backend.h" +#include "tree.h" +#include "gimple.h" +#include "predict.h" +#include "target.h" +#include "rtl.h" +#include "alloc-pool.h" +#include "cgraph.h" +#include "symbol-summary.h" +#include "symtab-thunks.h" +#include "lto-streamer.h" +#include "fold-const.h" +#include "gimple-iterator.h" +#include "stor-layout.h" +#include "gimplify-me.h" +#include "varasm.h" +#include "output.h" +#include "cfg.h" +#include "cfghooks.h" +#include "gimple-ssa.h" +#include "gimple-fold.h" +#include "cfgloop.h" +#include "tree-into-ssa.h" +#include "tree-cfg.h" +#include "cfgcleanup.h" +#include "tree-pass.h" +#include "data-streamer.h" +#include "langhooks.h" + +/* Used for vtable lookup in thunk adjusting. */ +static GTY (()) tree vtable_entry_type; + +namespace { + +/* Function summary for thunk_infos. */ +class GTY((user)) thunk_infos_t: public function_summary <thunk_info *> +{ +public: + thunk_infos_t (symbol_table *table, bool ggc): + function_summary<thunk_info *> (table, ggc) { } + + /* Hook that is called by summary when a node is duplicated. */ + virtual void duplicate (cgraph_node *node, + cgraph_node *node2, + thunk_info *data, + thunk_info *data2); +}; + +/* Duplication hook. */ +void +thunk_infos_t::duplicate (cgraph_node *, cgraph_node *, + thunk_info *src, thunk_info *dst) +{ + *dst = *src; +} + +} /* anon namespace */ + +/* Return thunk_info possibly creating new one. */ +thunk_info * +thunk_info::get_create (cgraph_node *node) +{ + if (!symtab->m_thunks) + { + symtab->m_thunks + = new (ggc_alloc_no_dtor <thunk_infos_t> ()) + thunk_infos_t (symtab, true); + symtab->m_thunks->disable_insertion_hook (); + } + return symtab->m_thunks->get_create (node); +} + +/* Stream out THIS to OB. */ +void +thunk_info::stream_out (lto_simple_output_block *ob) +{ + streamer_write_uhwi_stream + (ob->main_stream, + 1 + (this_adjusting != 0) * 2 + + (virtual_offset_p != 0) * 4); + streamer_write_uhwi_stream (ob->main_stream, fixed_offset); + streamer_write_uhwi_stream (ob->main_stream, virtual_value); + streamer_write_uhwi_stream (ob->main_stream, indirect_offset); +} + +/* Stream in THIS from IB. */ +void +thunk_info::stream_in (class lto_input_block *ib) +{ + int type = streamer_read_uhwi (ib); + fixed_offset = streamer_read_uhwi (ib); + virtual_value = streamer_read_uhwi (ib); + indirect_offset = streamer_read_uhwi (ib); + + this_adjusting = (type & 2); + virtual_offset_p = (type & 4); +} + +/* Dump THIS to F. */ +void +thunk_info::dump (FILE *f) +{ + if (alias) + fprintf (f, " of %s (asm:%s)", + lang_hooks.decl_printable_name (alias, 2), + IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (alias))); + fprintf (f, " fixed offset %i virtual value %i indirect_offset %i " + "has virtual offset %i\n", + (int)fixed_offset, + (int)virtual_value, + (int)indirect_offset, + (int)virtual_offset_p); +} + +/* Hash THIS. */ +hashval_t +thunk_info::hash () +{ + inchash::hash hstate; + hstate.add_hwi (fixed_offset); + hstate.add_hwi (virtual_value); + hstate.add_flag (this_adjusting); + hstate.add_flag (virtual_offset_p); + return hstate.end (); +} + +/* 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 indirect_offset) +{ + gassign *stmt; + tree ret; + + if (this_adjusting + && fixed_offset != 0) + { + stmt = gimple_build_assign + (ptr, fold_build_pointer_plus_hwi_loc (input_location, + ptr, + fixed_offset)); + 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) + { + tree vtabletmp; + tree vtabletmp2; + tree vtabletmp3; + + vtabletmp = create_tmp_reg + (build_pointer_type + (build_pointer_type (vtable_entry_type)), "vptr"); + + /* The vptr is always at offset zero in the object. */ + stmt = gimple_build_assign (vtabletmp, + build1 (NOP_EXPR, TREE_TYPE (vtabletmp), + ptr)); + gsi_insert_after (bsi, stmt, GSI_NEW_STMT); + + /* Form the vtable address. */ + vtabletmp2 = create_tmp_reg (TREE_TYPE (TREE_TYPE (vtabletmp)), + "vtableaddr"); + stmt = gimple_build_assign (vtabletmp2, + build_simple_mem_ref (vtabletmp)); + gsi_insert_after (bsi, stmt, GSI_NEW_STMT); + + /* Find the entry with the vcall offset. */ + stmt = gimple_build_assign (vtabletmp2, + fold_build_pointer_plus_loc (input_location, + vtabletmp2, + virtual_offset)); + gsi_insert_after (bsi, stmt, GSI_NEW_STMT); + + /* Get the offset itself. */ + vtabletmp3 = create_tmp_reg (TREE_TYPE (TREE_TYPE (vtabletmp2)), + "vcalloffset"); + stmt = gimple_build_assign (vtabletmp3, + build_simple_mem_ref (vtabletmp2)); + gsi_insert_after (bsi, stmt, GSI_NEW_STMT); + + /* Adjust the `this' pointer. */ + ptr = fold_build_pointer_plus_loc (input_location, ptr, vtabletmp3); + ptr = force_gimple_operand_gsi (bsi, ptr, true, NULL_TREE, false, + 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. */ + { + tree ptrtmp; + + if (VAR_P (ptr)) + ptrtmp = ptr; + else + { + ptrtmp = create_tmp_reg (TREE_TYPE (ptr), "ptr"); + stmt = gimple_build_assign (ptrtmp, ptr); + gsi_insert_after (bsi, stmt, GSI_NEW_STMT); + } + ptr = fold_build_pointer_plus_hwi_loc (input_location, + ptrtmp, fixed_offset); + } + + /* Emit the statement and gimplify the adjustment expression. */ + ret = create_tmp_reg (TREE_TYPE (ptr), "adjusted_this"); + stmt = gimple_build_assign (ret, ptr); + gsi_insert_after (bsi, stmt, GSI_NEW_STMT); + + return ret; +} + +/* Expand thunk NODE to gimple if possible. + When FORCE_GIMPLE_THUNK is true, gimple thunk is created and + no assembler is produced. + When OUTPUT_ASM_THUNK is true, also produce assembler for + thunks that are not lowered. */ +bool +expand_thunk (cgraph_node *node, bool output_asm_thunks, + bool force_gimple_thunk) +{ + thunk_info *info = thunk_info::get (node); + bool this_adjusting = info->this_adjusting; + HOST_WIDE_INT fixed_offset = info->fixed_offset; + HOST_WIDE_INT virtual_value = info->virtual_value; + HOST_WIDE_INT indirect_offset = info->indirect_offset; + tree virtual_offset = NULL; + tree alias = node->callees->callee->decl; + tree thunk_fndecl = node->decl; + tree a; + + 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)) + { + tree fn_block; + tree restype = TREE_TYPE (TREE_TYPE (thunk_fndecl)); + + if (!output_asm_thunks) + { + node->analyzed = true; + return false; + } + + if (in_lto_p) + node->get_untransformed_body (); + a = DECL_ARGUMENTS (thunk_fndecl); + + current_function_decl = thunk_fndecl; + + /* Ensure thunks are emitted in their correct sections. */ + resolve_unique_section (thunk_fndecl, 0, + flag_function_sections); + + DECL_RESULT (thunk_fndecl) + = build_decl (DECL_SOURCE_LOCATION (thunk_fndecl), + RESULT_DECL, 0, restype); + DECL_CONTEXT (DECL_RESULT (thunk_fndecl)) = thunk_fndecl; + + /* The back end expects DECL_INITIAL to contain a BLOCK, so we + create one. */ + fn_block = make_node (BLOCK); + BLOCK_VARS (fn_block) = a; + DECL_INITIAL (thunk_fndecl) = fn_block; + BLOCK_SUPERCONTEXT (fn_block) = thunk_fndecl; + allocate_struct_function (thunk_fndecl, false); + init_function_start (thunk_fndecl); + cfun->is_thunk = 1; + insn_locations_init (); + set_curr_insn_location (DECL_SOURCE_LOCATION (thunk_fndecl)); + prologue_location = curr_insn_location (); + + targetm.asm_out.output_mi_thunk (asm_out_file, thunk_fndecl, + fixed_offset, virtual_value, alias); + + insn_locations_finalize (); + init_insn_lengths (); + free_after_compilation (cfun); + TREE_ASM_WRITTEN (thunk_fndecl) = 1; + node->thunk = false; + node->analyzed = false; + } + else if (stdarg_p (TREE_TYPE (thunk_fndecl))) + { + error ("generic thunk code fails for method %qD which uses %<...%>", + thunk_fndecl); + TREE_ASM_WRITTEN (thunk_fndecl) = 1; + node->analyzed = true; + return false; + } + else + { + tree restype; + basic_block bb, then_bb, else_bb, return_bb; + gimple_stmt_iterator bsi; + int nargs = 0; + tree arg; + int i; + tree resdecl; + tree restmp = NULL; + + gcall *call; + greturn *ret; + bool alias_is_noreturn = TREE_THIS_VOLATILE (alias); + + /* We may be called from expand_thunk that releases body except for + DECL_ARGUMENTS. In this case force_gimple_thunk is true. */ + if (in_lto_p && !force_gimple_thunk) + node->get_untransformed_body (); + + /* We need to force DECL_IGNORED_P when the thunk is created + after early debug was run. */ + if (force_gimple_thunk) + DECL_IGNORED_P (thunk_fndecl) = 1; + + a = DECL_ARGUMENTS (thunk_fndecl); + + current_function_decl = thunk_fndecl; + + /* Ensure thunks are emitted in their correct sections. */ + resolve_unique_section (thunk_fndecl, 0, + flag_function_sections); + + bitmap_obstack_initialize (NULL); + + if (info->virtual_offset_p) + virtual_offset = size_int (virtual_value); + + /* Build the return declaration for the function. */ + restype = TREE_TYPE (TREE_TYPE (thunk_fndecl)); + if (DECL_RESULT (thunk_fndecl) == NULL_TREE) + { + 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; + } + else + resdecl = DECL_RESULT (thunk_fndecl); + + profile_count cfg_count = node->count; + if (!cfg_count.initialized_p ()) + cfg_count = profile_count::from_gcov_type + (BB_FREQ_MAX).guessed_local (); + + bb = then_bb = else_bb = return_bb + = init_lowered_empty_function (thunk_fndecl, true, cfg_count); + + bsi = gsi_start_bb (bb); + + /* Build call to the function being thunked. */ + if (!VOID_TYPE_P (restype) + && (!alias_is_noreturn + || TREE_ADDRESSABLE (restype) + || TREE_CODE (TYPE_SIZE_UNIT (restype)) != INTEGER_CST)) + { + if (DECL_BY_REFERENCE (resdecl)) + { + restmp = gimple_fold_indirect_ref (resdecl); + if (!restmp) + restmp = build2 (MEM_REF, + TREE_TYPE (TREE_TYPE (resdecl)), + resdecl, + build_int_cst (TREE_TYPE (resdecl), 0)); + } + else if (!is_gimple_reg_type (restype)) + { + if (aggregate_value_p (resdecl, TREE_TYPE (thunk_fndecl))) + { + restmp = resdecl; + + if (VAR_P (restmp)) + { + add_local_decl (cfun, restmp); + BLOCK_VARS (DECL_INITIAL (current_function_decl)) + = restmp; + } + } + else + restmp = create_tmp_var (restype, "retval"); + } + else + restmp = create_tmp_reg (restype, "retval"); + } + + for (arg = a; arg; arg = DECL_CHAIN (arg)) + nargs++; + auto_vec<tree> vargs (nargs); + i = 0; + arg = a; + if (this_adjusting) + { + vargs.quick_push (thunk_adjust (&bsi, a, 1, fixed_offset, + virtual_offset, indirect_offset)); + arg = DECL_CHAIN (a); + i = 1; + } + + if (nargs) + for (; i < nargs; i++, arg = DECL_CHAIN (arg)) + { + tree tmp = arg; + DECL_NOT_GIMPLE_REG_P (arg) = 0; + if (!is_gimple_val (arg)) + { + tmp = create_tmp_reg (TYPE_MAIN_VARIANT + (TREE_TYPE (arg)), "arg"); + gimple *stmt = gimple_build_assign (tmp, arg); + gsi_insert_after (&bsi, stmt, GSI_NEW_STMT); + } + vargs.quick_push (tmp); + } + call = gimple_build_call_vec (build_fold_addr_expr_loc (0, alias), vargs); + node->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 required to + return values with DECL_BY_REFERENCE. */ + if (aggregate_value_p (resdecl, TREE_TYPE (thunk_fndecl)) + && (!is_gimple_reg_type (TREE_TYPE (resdecl)) + || DECL_BY_REFERENCE (resdecl))) + gimple_call_set_return_slot_opt (call, true); + + if (restmp) + { + gimple_call_set_lhs (call, restmp); + gcc_assert (useless_type_conversion_p (TREE_TYPE (restmp), + TREE_TYPE (TREE_TYPE (alias)))); + } + gsi_insert_after (&bsi, call, GSI_NEW_STMT); + if (!alias_is_noreturn) + { + if (restmp && !this_adjusting + && (fixed_offset || virtual_offset)) + { + tree true_label = NULL_TREE; + + if (TREE_CODE (TREE_TYPE (restmp)) == POINTER_TYPE) + { + gimple *stmt; + edge e; + /* If the return type is a pointer, we need to + protect against NULL. We know there will be an + adjustment, because that's why we're emitting a + thunk. */ + then_bb = create_basic_block (NULL, bb); + then_bb->count = cfg_count - cfg_count.apply_scale (1, 16); + return_bb = create_basic_block (NULL, then_bb); + return_bb->count = cfg_count; + else_bb = create_basic_block (NULL, else_bb); + else_bb->count = cfg_count.apply_scale (1, 16); + add_bb_to_loop (then_bb, bb->loop_father); + add_bb_to_loop (return_bb, bb->loop_father); + add_bb_to_loop (else_bb, bb->loop_father); + remove_edge (single_succ_edge (bb)); + true_label = gimple_block_label (then_bb); + stmt = gimple_build_cond (NE_EXPR, restmp, + build_zero_cst (TREE_TYPE (restmp)), + NULL_TREE, NULL_TREE); + gsi_insert_after (&bsi, stmt, GSI_NEW_STMT); + e = make_edge (bb, then_bb, EDGE_TRUE_VALUE); + e->probability = profile_probability::guessed_always () + .apply_scale (1, 16); + e = make_edge (bb, else_bb, EDGE_FALSE_VALUE); + e->probability = profile_probability::guessed_always () + .apply_scale (1, 16); + make_single_succ_edge (return_bb, + EXIT_BLOCK_PTR_FOR_FN (cfun), 0); + make_single_succ_edge (then_bb, return_bb, EDGE_FALLTHRU); + e = make_edge (else_bb, return_bb, EDGE_FALLTHRU); + e->probability = profile_probability::always (); + bsi = gsi_last_bb (then_bb); + } + + restmp = thunk_adjust (&bsi, restmp, /*this_adjusting=*/0, + fixed_offset, virtual_offset, + indirect_offset); + if (true_label) + { + gimple *stmt; + bsi = gsi_last_bb (else_bb); + stmt = gimple_build_assign (restmp, + build_zero_cst + (TREE_TYPE (restmp))); + gsi_insert_after (&bsi, stmt, GSI_NEW_STMT); + bsi = gsi_last_bb (return_bb); + } + } + else + { + gimple_call_set_tail (call, true); + cfun->tail_call_marked = true; + } + + /* Build return value. */ + if (!DECL_BY_REFERENCE (resdecl)) + ret = gimple_build_return (restmp); + else + ret = gimple_build_return (resdecl); + + gsi_insert_after (&bsi, ret, GSI_NEW_STMT); + } + else + { + gimple_call_set_tail (call, true); + cfun->tail_call_marked = true; + remove_edge (single_succ_edge (bb)); + } + + cfun->gimple_df->in_ssa_p = true; + update_max_bb_count (); + profile_status_for_fn (cfun) + = cfg_count.initialized_p () && cfg_count.ipa_p () + ? PROFILE_READ : PROFILE_GUESSED; + /* FIXME: C++ FE should stop setting TREE_ASM_WRITTEN on thunks. */ + TREE_ASM_WRITTEN (thunk_fndecl) = false; + delete_unreachable_blocks (); + update_ssa (TODO_update_ssa); + checking_verify_flow_info (); + free_dominance_info (CDI_DOMINATORS); + + /* Since we want to emit the thunk, we explicitly mark its name as + referenced. */ + node->thunk = false; + node->lowered = true; + bitmap_obstack_release (NULL); + } + current_function_decl = NULL; + set_cfun (NULL); + return true; +} + +void +symtab_thunks_cc_finalize (void) +{ + vtable_entry_type = NULL; +} + +#include "gt-symtab-thunks.h" |