/* Call site information.

   Copyright (C) 2011-2024 Free Software Foundation, Inc.

   Contributed by Cygnus Support, using pieces from other GDB modules.

   This file is part of GDB.

   This program 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 of the License, or
   (at your option) any later version.

   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.  */

#ifndef GDB_DWARF2_CALL_SITE_H
#define GDB_DWARF2_CALL_SITE_H

#include "dwarf2/types.h"
#include "../frame.h"
#include "gdbsupport/function-view.h"
#include "gdbsupport/unordered_set.h"

struct dwarf2_locexpr_baton;
struct dwarf2_per_cu_data;
struct dwarf2_per_objfile;

/* struct call_site_parameter can be referenced in callees by several ways.  */

enum call_site_parameter_kind
{
  /* * Use field call_site_parameter.u.dwarf_reg.  */
  CALL_SITE_PARAMETER_DWARF_REG,

  /* * Use field call_site_parameter.u.fb_offset.  */
  CALL_SITE_PARAMETER_FB_OFFSET,

  /* * Use field call_site_parameter.u.param_offset.  */
  CALL_SITE_PARAMETER_PARAM_OFFSET
};

struct call_site_target
{
  /* The kind of location held by this call site target.  */
  enum kind
  {
    /* An address.  */
    PHYSADDR,
    /* A name.  */
    PHYSNAME,
    /* A DWARF block.  */
    DWARF_BLOCK,
    /* An array of addresses.  */
    ADDRESSES,
  };

  void set_loc_physaddr (unrelocated_addr physaddr)
  {
    m_loc_kind = PHYSADDR;
    m_loc.physaddr = physaddr;
  }

  void set_loc_physname (const char *physname)
    {
      m_loc_kind = PHYSNAME;
      m_loc.physname = physname;
    }

  void set_loc_dwarf_block (dwarf2_locexpr_baton *dwarf_block)
    {
      m_loc_kind = DWARF_BLOCK;
      m_loc.dwarf_block = dwarf_block;
    }

  void set_loc_array (unsigned length, const unrelocated_addr *data)
  {
    m_loc_kind = ADDRESSES;
    m_loc.addresses.length = length;
    m_loc.addresses.values = data;
  }

  /* Callback type for iterate_over_addresses.  */

  using iterate_ftype = gdb::function_view<void (CORE_ADDR)>;

  /* Call CALLBACK for each DW_TAG_call_site's DW_AT_call_target
     address.  CALLER_FRAME (for registers) can be NULL if it is not
     known.  This function always may throw NO_ENTRY_VALUE_ERROR.  */

  void iterate_over_addresses (struct gdbarch *call_site_gdbarch,
			       const struct call_site *call_site,
			       const frame_info_ptr &caller_frame,
			       iterate_ftype callback) const;

private:

  union
  {
    /* Address.  */
    unrelocated_addr physaddr;
    /* Mangled name.  */
    const char *physname;
    /* DWARF block.  */
    struct dwarf2_locexpr_baton *dwarf_block;
    /* Array of addresses.  */
    struct
    {
      unsigned length;
      const unrelocated_addr *values;
    } addresses;
  } m_loc;

  /* * Discriminant for union field_location.  */
  enum kind m_loc_kind;
};

union call_site_parameter_u
{
  /* * DW_TAG_formal_parameter's DW_AT_location's DW_OP_regX
     as DWARF register number, for register passed
     parameters.  */

  int dwarf_reg;

  /* * Offset from the callee's frame base, for stack passed
     parameters.  This equals offset from the caller's stack
     pointer.  */

  CORE_ADDR fb_offset;

  /* * Offset relative to the start of this PER_CU to
     DW_TAG_formal_parameter which is referenced by both
     caller and the callee.  */

  cu_offset param_cu_off;
};

struct call_site_parameter
{
  ENUM_BITFIELD (call_site_parameter_kind) kind : 2;

  union call_site_parameter_u u;

  /* * DW_TAG_formal_parameter's DW_AT_call_value.  It is never NULL.  */

  const gdb_byte *value;
  size_t value_size;

  /* * DW_TAG_formal_parameter's DW_AT_call_data_value.
     It may be NULL if not provided by DWARF.  */

  const gdb_byte *data_value;
  size_t data_value_size;
};

/* * A place where a function gets called from, represented by
   DW_TAG_call_site.  It can be looked up from symtab->call_site_htab.  */

struct call_site
{
  call_site (unrelocated_addr pc, dwarf2_per_cu_data *per_cu,
	     dwarf2_per_objfile *per_objfile)
    : per_cu (per_cu), per_objfile (per_objfile), m_unrelocated_pc (pc)
  {}

  /* Return the relocated (using the objfile from PER_OBJFILE) address of the
     first instruction after this call.  */

  CORE_ADDR pc () const;

  /* Return the unrelocated address of the first instruction after this
     call.  */

  unrelocated_addr unrelocated_pc () const noexcept
  { return m_unrelocated_pc; }

  /* Call CALLBACK for each target address.  CALLER_FRAME (for
     registers) can be NULL if it is not known.  This function may
     throw NO_ENTRY_VALUE_ERROR.  */

  void iterate_over_addresses (struct gdbarch *call_site_gdbarch,
			       const frame_info_ptr &caller_frame,
			       call_site_target::iterate_ftype callback)
    const
  {
    return target.iterate_over_addresses (call_site_gdbarch, this,
					  caller_frame, callback);
  }

  /* * List successor with head in FUNC_TYPE.TAIL_CALL_LIST.  */

  struct call_site *tail_call_next = nullptr;

  /* * Describe DW_AT_call_target.  Missing attribute uses
     FIELD_LOC_KIND_DWARF_BLOCK with FIELD_DWARF_BLOCK == NULL.  */

  struct call_site_target target {};

  /* * Size of the PARAMETER array.  */

  unsigned parameter_count = 0;

  /* * CU of the function where the call is located.  It gets used
     for DWARF blocks execution in the parameter array below.  */

  dwarf2_per_cu_data *const per_cu = nullptr;

  /* objfile of the function where the call is located.  */

  dwarf2_per_objfile *const per_objfile = nullptr;

private:
  /* Unrelocated address of the first instruction after this call.  */
  const unrelocated_addr m_unrelocated_pc;

public:
  /* * Describe DW_TAG_call_site's DW_TAG_formal_parameter.  */

  struct call_site_parameter parameter[];
};

/* Key hash type to store call_site objects in gdb::unordered_set, identified by
   their unrelocated PC.  */

struct call_site_hash_pc
{
  using is_transparent = void;

  std::size_t operator() (const call_site *site) const noexcept
  { return (*this) (site->unrelocated_pc ()); }

  std::size_t operator() (unrelocated_addr pc) const noexcept
  { return std::hash<unrelocated_addr> () (pc); }
};

/* Key equal type to store call_site objects in gdb::unordered_set, identified
   by their unrelocated PC.  */

struct call_site_eq_pc
{
  using is_transparent = void;

  bool operator() (const call_site *a, const call_site *b) const noexcept
  { return (*this) (a->unrelocated_pc (), b); }

  bool operator() (unrelocated_addr pc, const call_site *site) const noexcept
  { return pc == site->unrelocated_pc (); }
};

/* Set of call_site objects identified by their unrelocated PC.  */

using call_site_htab_t
  = gdb::unordered_set<call_site *, call_site_hash_pc, call_site_eq_pc>;

#endif /* GDB_DWARF2_CALL_SITE_H */
