/* Support for thunks in symbol table.
   Copyright (C) 2003-2023 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.  */
  void duplicate (cgraph_node *node,
		  cgraph_node *node2,
		  thunk_info *data,
		  thunk_info *data2) final override;
};

/* 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 / 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 / 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 () / 16;
		  e = make_edge (bb, else_bb, EDGE_FALSE_VALUE);
		  e->probability = profile_probability::guessed_always () / 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_ctrl_altering (call, true);
	  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;
      cfun->cfg->full_profile = true;
      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"