/* Virtual tail call frames unwinder for GDB.

   Copyright (C) 2010-2026 Free Software Foundation, Inc.

   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/>.  */

#include "exceptions.h"
#include "frame.h"
#include "dwarf2/frame-tailcall.h"
#include "dwarf2/loc.h"
#include "frame-unwind.h"
#include "hashtab.h"
#include "regcache.h"
#include "value.h"
#include "dwarf2/frame.h"
#include "gdbarch.h"

/* Contains struct tailcall_cache indexed by next_bottom_frame.  */
static htab_t cache_htab;

/* Associate structure of the unwinder to call_site_chain.  Lifetime of this
   structure is maintained by REFC decremented by dealloc_cache, all of them
   get deleted during reinit_frame_cache.  */
struct tailcall_cache
{
  /* It must be the first one of this struct.  It is the furthest callee.  */
  frame_info *next_bottom_frame;

  /* Reference count.  The whole chain of virtual tail call frames shares one
     tailcall_cache.  */
  int refc;

  /* Associated found virtual tail call frames chain, it is never NULL.  */
  struct call_site_chain *chain;

  /* Cached pretended_chain_levels result.  */
  int chain_levels;

  /* Unwound PC from the top (caller) frame, as it is not contained
     in CHAIN.  */
  CORE_ADDR prev_pc;

  /* Compensate SP in caller frames appropriately.  prev_sp and
     entry_cfa_sp_offset are valid only if PREV_SP_P.  PREV_SP is SP at the top
     (caller) frame.  ENTRY_CFA_SP_OFFSET is shift of SP in tail call frames
     against next_bottom_frame SP.  */
  unsigned prev_sp_p : 1;
  CORE_ADDR prev_sp;
  LONGEST entry_cfa_sp_offset;
};

/* hash_f for htab_create_alloc of cache_htab.  */

static hashval_t
cache_hash (const void *arg)
{
  const struct tailcall_cache *cache = (const struct tailcall_cache *) arg;

  return htab_hash_pointer (cache->next_bottom_frame);
}

/* eq_f for htab_create_alloc of cache_htab.  */

static int
cache_eq (const void *arg1, const void *arg2)
{
  const struct tailcall_cache *cache1 = (const struct tailcall_cache *) arg1;
  const struct tailcall_cache *cache2 = (const struct tailcall_cache *) arg2;

  return cache1->next_bottom_frame == cache2->next_bottom_frame;
}

/* Create new tailcall_cache for NEXT_BOTTOM_FRAME, NEXT_BOTTOM_FRAME must not
   yet have been indexed by cache_htab.  Caller holds one reference of the new
   tailcall_cache.  */

static struct tailcall_cache *
cache_new_ref1 (const frame_info_ptr &next_bottom_frame)
{
  struct tailcall_cache *cache = XCNEW (struct tailcall_cache);
  void **slot;

  cache->next_bottom_frame = next_bottom_frame.get ();
  cache->refc = 1;

  slot = htab_find_slot (cache_htab, cache, INSERT);
  gdb_assert (*slot == NULL);
  *slot = cache;

  return cache;
}

/* Create new reference to CACHE.  */

static void
cache_ref (struct tailcall_cache *cache)
{
  gdb_assert (cache->refc > 0);

  cache->refc++;
}

/* Drop reference to CACHE, possibly fully freeing it and unregistering it from
   cache_htab.  */

static void
cache_unref (struct tailcall_cache *cache)
{
  gdb_assert (cache->refc > 0);

  if (!--cache->refc)
    {
      gdb_assert (htab_find_slot (cache_htab, cache, NO_INSERT) != NULL);
      htab_remove_elt (cache_htab, cache);

      xfree (cache->chain);
      xfree (cache);
    }
}

/* Return true if FI is a non-bottom (not the callee) tail call frame.
   Otherwise return false.  */

static bool
frame_is_tailcall (const frame_info_ptr &fi)
{
  return frame_unwinder_is (fi, &dwarf2_tailcall_frame_unwind);
}

/* Try to find tailcall_cache in cache_htab if FI is a part of its virtual tail
   call chain.  Otherwise return NULL.  No new reference is created.  */

static struct tailcall_cache *
cache_find (const frame_info_ptr &initial_fi)
{
  struct tailcall_cache *cache;
  struct tailcall_cache search;
  void **slot;

  frame_info_ptr fi = initial_fi;
  while (frame_is_tailcall (fi))
    {
      fi = get_next_frame (fi);
      gdb_assert (fi != NULL);
    }

  search.next_bottom_frame = fi.get();
  search.refc = 1;
  slot = htab_find_slot (cache_htab, &search, NO_INSERT);
  if (slot == NULL)
    return NULL;

  cache = (struct tailcall_cache *) *slot;
  gdb_assert (cache != NULL);
  return cache;
}

/* Number of virtual frames between THIS_FRAME and CACHE->NEXT_BOTTOM_FRAME.
   If THIS_FRAME is CACHE-> NEXT_BOTTOM_FRAME return -1.  */

static int
existing_next_levels (const frame_info_ptr &this_frame,
		      struct tailcall_cache *cache)
{
  int retval = (frame_relative_level (this_frame)
		- frame_relative_level (frame_info_ptr (cache->next_bottom_frame)) - 1);

  gdb_assert (retval >= -1);

  return retval;
}

/* The number of virtual tail call frames in CHAIN.  With no virtual tail call
   frames the function would return 0 (but CHAIN does not exist in such
   case).  */

static int
pretended_chain_levels (struct call_site_chain *chain)
{
  int chain_levels;

  gdb_assert (chain != NULL);

  if (chain->callers == chain->length && chain->callees == chain->length)
    return chain->length;

  chain_levels = chain->callers + chain->callees;
  gdb_assert (chain_levels <= chain->length);

  return chain_levels;
}

/* Implementation of frame_this_id_ftype.  THIS_CACHE must be already
   initialized with tailcall_cache, THIS_FRAME must be a part of THIS_CACHE.

   Specific virtual tail call frames are tracked by INLINE_DEPTH.  */

static void
tailcall_frame_this_id (const frame_info_ptr &this_frame, void **this_cache,
			struct frame_id *this_id)
{
  struct tailcall_cache *cache = (struct tailcall_cache *) *this_cache;
  frame_info_ptr next_frame;

  /* Tail call does not make sense for a sentinel frame.  */
  next_frame = get_next_frame (this_frame);
  gdb_assert (next_frame != NULL);

  *this_id = get_frame_id (next_frame);
  (*this_id).code_addr = get_frame_pc (this_frame);
  (*this_id).code_addr_p = true;
  (*this_id).artificial_depth = (cache->chain_levels
				 - existing_next_levels (this_frame, cache));
  gdb_assert ((*this_id).artificial_depth > 0);
}

/* Find PC to be unwound from THIS_FRAME.  THIS_FRAME must be a part of
   CACHE.  */

static CORE_ADDR
pretend_pc (const frame_info_ptr &this_frame, struct tailcall_cache *cache)
{
  int next_levels = existing_next_levels (this_frame, cache);
  struct call_site_chain *chain = cache->chain;

  gdb_assert (chain != NULL);

  next_levels++;
  gdb_assert (next_levels >= 0);

  if (next_levels < chain->callees)
    return chain->call_site[chain->length - next_levels - 1]->pc ();
  next_levels -= chain->callees;

  /* Otherwise CHAIN->CALLEES are already covered by CHAIN->CALLERS.  */
  if (chain->callees != chain->length)
    {
      if (next_levels < chain->callers)
	return chain->call_site[chain->callers - next_levels - 1]->pc ();
      next_levels -= chain->callers;
    }

  gdb_assert (next_levels == 0);
  return cache->prev_pc;
}

/* Implementation of frame_prev_register_ftype.  If no specific register
   override is supplied NULL is returned (this is incompatible with
   frame_prev_register_ftype semantics).  next_bottom_frame and tail call
   frames unwind the NULL case differently.  */

struct value *
dwarf2_tailcall_prev_register_first (const frame_info_ptr &this_frame,
				     void **tailcall_cachep, int regnum)
{
  struct gdbarch *this_gdbarch = get_frame_arch (this_frame);
  struct tailcall_cache *cache = (struct tailcall_cache *) *tailcall_cachep;
  CORE_ADDR addr;

  if (regnum == gdbarch_pc_regnum (this_gdbarch))
    addr = pretend_pc (this_frame, cache);
  else if (cache->prev_sp_p && regnum == gdbarch_sp_regnum (this_gdbarch))
    {
      int next_levels = existing_next_levels (this_frame, cache);

      if (next_levels == cache->chain_levels - 1)
	addr = cache->prev_sp;
      else
	addr = dwarf2_frame_cfa (this_frame) - cache->entry_cfa_sp_offset;
    }
  else
    return NULL;

  return frame_unwind_got_address (this_frame, regnum, addr);
}

/* Implementation of frame_prev_register_ftype for tail call frames.  Register
   set of virtual tail call frames is assumed to be the one of the top (caller)
   frame - assume unchanged register value for NULL from
   dwarf2_tailcall_prev_register_first.  */

static struct value *
tailcall_frame_prev_register (const frame_info_ptr &this_frame,
			       void **this_cache, int regnum)
{
  struct tailcall_cache *cache = (struct tailcall_cache *) *this_cache;
  struct value *val;

  gdb_assert (this_frame != cache->next_bottom_frame);

  val = dwarf2_tailcall_prev_register_first (this_frame, this_cache, regnum);
  if (val)
    return val;

  return frame_unwind_got_register (this_frame, regnum, regnum);
}

/* Implementation of frame_sniffer_ftype.  It will never find a new chain, use
   dwarf2_tailcall_sniffer_first for the bottom (callee) frame.  It will find
   all the predecessing virtual tail call frames, it will return false when
   there exist no more tail call frames in this chain.  */

static int
tailcall_frame_sniffer (const struct frame_unwind *self,
			 const frame_info_ptr &this_frame, void **this_cache)
{
  frame_info_ptr next_frame;
  int next_levels;
  struct tailcall_cache *cache;

  /* Inner tail call element does not make sense for a sentinel frame.  */
  next_frame = get_next_frame (this_frame);
  if (next_frame == NULL)
    return 0;

  cache = cache_find (next_frame);
  if (cache == NULL)
    return 0;

  cache_ref (cache);

  next_levels = existing_next_levels (this_frame, cache);

  /* NEXT_LEVELS is -1 only in dwarf2_tailcall_sniffer_first.  */
  gdb_assert (next_levels >= 0);
  gdb_assert (next_levels <= cache->chain_levels);

  if (next_levels == cache->chain_levels)
    {
      cache_unref (cache);
      return 0;
    }

  *this_cache = cache;
  return 1;
}

/* The initial "sniffer" whether THIS_FRAME is a bottom (callee) frame of a new
   chain to create.  Keep TAILCALL_CACHEP NULL if it did not find any chain,
   initialize it otherwise.  No tail call chain is created if there are no
   unambiguous virtual tail call frames to report.

   ENTRY_CFA_SP_OFFSETP is NULL if no special SP handling is possible,
   otherwise *ENTRY_CFA_SP_OFFSETP is the number of bytes to subtract from tail
   call frames frame base to get the SP value there - to simulate return
   address pushed on the stack.  */

void
dwarf2_tailcall_sniffer_first (const frame_info_ptr &this_frame,
			       void **tailcall_cachep,
			       const LONGEST *entry_cfa_sp_offsetp)
{
  CORE_ADDR prev_pc = 0, prev_sp = 0;	/* GCC warning.  */
  int prev_sp_p = 0;
  CORE_ADDR this_pc;
  struct gdbarch *prev_gdbarch;
  gdb::unique_xmalloc_ptr<call_site_chain> chain;
  struct tailcall_cache *cache;

  gdb_assert (*tailcall_cachep == NULL);

  /* PC may be after the function if THIS_FRAME calls noreturn function,
     get_frame_address_in_block will decrease it by 1 in such case.  */
  this_pc = get_frame_address_in_block (this_frame);

  try
    {
      int sp_regnum;

      prev_gdbarch = frame_unwind_arch (this_frame);

      /* Simulate frame_unwind_pc without setting this_frame->prev_pc.p.  */
      prev_pc = gdbarch_unwind_pc (prev_gdbarch, this_frame);

      /* call_site_find_chain can throw an exception.  */
      chain = call_site_find_chain (prev_gdbarch, prev_pc, this_pc);

      if (entry_cfa_sp_offsetp != NULL)
	{
	  sp_regnum = gdbarch_sp_regnum (prev_gdbarch);
	  if (sp_regnum != -1)
	    {
	      prev_sp = frame_unwind_register_unsigned (this_frame, sp_regnum);
	      prev_sp_p = 1;
	    }
	}
    }
  catch (const gdb_exception_error &except)
    {
      if (entry_values_debug)
	exception_print (gdb_stdout, except);

      switch (except.error)
	{
	case NO_ENTRY_VALUE_ERROR:
	  /* Thrown by call_site_find_chain.  */
	case MEMORY_ERROR:
	case OPTIMIZED_OUT_ERROR:
	case NOT_AVAILABLE_ERROR:
	  /* These can normally happen when we try to access an
	     optimized out or unavailable register, either in a
	     physical register or spilled to memory.  */
	  return;
	}

      /* Let unexpected errors propagate.  */
      throw;
    }

  /* Ambiguous unwind or unambiguous unwind verified as matching.  */
  if (chain == NULL || chain->length == 0)
    return;

  cache = cache_new_ref1 (this_frame);
  *tailcall_cachep = cache;
  cache->chain = chain.release ();
  cache->prev_pc = prev_pc;
  cache->chain_levels = pretended_chain_levels (cache->chain);
  cache->prev_sp_p = prev_sp_p;
  if (cache->prev_sp_p)
    {
      cache->prev_sp = prev_sp;
      cache->entry_cfa_sp_offset = *entry_cfa_sp_offsetp;
    }
  gdb_assert (cache->chain_levels > 0);
}

/* Implementation of frame_dealloc_cache_ftype.  It can be called even for the
   bottom chain frame from dwarf2_frame_dealloc_cache which is not a real
   TAILCALL_FRAME.  */

static void
tailcall_frame_dealloc_cache (frame_info *self, void *this_cache)
{
  struct tailcall_cache *cache = (struct tailcall_cache *) this_cache;

  cache_unref (cache);
}

/* Implementation of frame_prev_arch_ftype.  We assume all the virtual tail
   call frames have gdbarch of the bottom (callee) frame.  */

static struct gdbarch *
tailcall_frame_prev_arch (const frame_info_ptr &this_frame,
			  void **this_prologue_cache)
{
  struct tailcall_cache *cache = (struct tailcall_cache *) *this_prologue_cache;

  return get_frame_arch (frame_info_ptr (cache->next_bottom_frame));
}

/* Virtual tail call frame unwinder if dwarf2_tailcall_sniffer_first finds
   a chain to create.  */

const struct frame_unwind_legacy dwarf2_tailcall_frame_unwind (
  "dwarf2 tailcall",
  TAILCALL_FRAME,
  FRAME_UNWIND_DEBUGINFO,
  default_frame_unwind_stop_reason,
  tailcall_frame_this_id,
  tailcall_frame_prev_register,
  NULL,
  tailcall_frame_sniffer,
  tailcall_frame_dealloc_cache,
  tailcall_frame_prev_arch
);

INIT_GDB_FILE (tailcall_frame)
{
  cache_htab = htab_create_alloc (50, cache_hash, cache_eq, NULL, xcalloc,
				  xfree);
}
