675 lines
19 KiB
C++
675 lines
19 KiB
C++
/* Support for thunks in symbol table.
|
|
Copyright (C) 2003-2022 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;
|
|
struct GTY (()) unprocessed_thunk
|
|
{
|
|
cgraph_node *node;
|
|
thunk_info *info;
|
|
};
|
|
/* To be PCH safe we store thunks into a vector before end of compilation
|
|
unit. */
|
|
static GTY (()) vec<unprocessed_thunk, va_gc> *thunks;
|
|
|
|
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 ();
|
|
}
|
|
|
|
/* Add unprocessed thunk. */
|
|
void
|
|
thunk_info::register_early (cgraph_node *node)
|
|
{
|
|
unprocessed_thunk entry = {node, new (ggc_alloc <thunk_info> ()) thunk_info};
|
|
*entry.info = *this;
|
|
vec_safe_push (thunks, entry);
|
|
}
|
|
|
|
/* Attach recorded thunks to cgraph_nodes.
|
|
All this is done only to avoid need to stream summaries to PCH. */
|
|
void
|
|
thunk_info::process_early_thunks ()
|
|
{
|
|
unprocessed_thunk *e;
|
|
unsigned int i;
|
|
if (!thunks)
|
|
return;
|
|
|
|
FOR_EACH_VEC_ELT (*thunks, i, e)
|
|
{
|
|
*thunk_info::get_create (e->node) = *e->info;
|
|
}
|
|
vec_free (thunks);
|
|
thunks = NULL;
|
|
}
|
|
|
|
/* 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"
|