/* eh.c
   Extracted from libgcc2.c and modified by Eberhard Mattes, Jun 1998 */

/* Copyright (C) 1989, 92-97, 1998 Free Software Foundation, Inc.

This file is part of GNU CC.

GNU CC 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 2, or (at your option)
any later version.

GNU CC 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 GNU CC; see the file COPYING.  If not, write to
the Free Software Foundation, 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.  */

/* As a special exception, if you link this library with other files,
   some of which are compiled with GCC, to produce an executable,
   this library does not by itself cause the resulting executable
   to be covered by the GNU General Public License.
   This exception does not however invalidate any other reasons why
   the executable file might be covered by the GNU General Public License.  */

#include "tconfig.h"
#include "machmode.h"
#include "defaults.h"
#include <stddef.h>

typedef int word_type __attribute__ ((mode (__word__)));

/* Shared exception handling support routines.  */

/* Language-specific information about the active exception(s).  If there
   are no active exceptions, it is set to 0.  */
void *__eh_info;

void
__default_terminate ()
{
  abort ();
}

void (*__terminate_func)() = __default_terminate;

void
__terminate ()
{
  (*__terminate_func)();
}

void *
__throw_type_match (void *catch_type, void *throw_type, void *obj)
{
#if 0
 printf ("__throw_type_match (): catch_type = %s, throw_type = %s\n",
	 catch_type, throw_type);
#endif
 if (strcmp ((const char *)catch_type, (const char *)throw_type) == 0)
   return obj;
 return 0;
}

void
__empty ()
{
}

/* Support routines for setjmp/longjmp exception handling.  */

/* Calls to __sjthrow are generated by the compiler when an exception
   is raised when using the setjmp/longjmp exception handling codegen
   method.  */

extern void longjmp (void *, int);

static void *top_elt[2];
void **__dynamic_handler_chain = top_elt;

/* Routine to get the head of the current thread's dynamic handler chain
   use for exception handling.

   TODO: make thread safe.  */

void ***
__get_dynamic_handler_chain ()
{
  return &__dynamic_handler_chain;
}

/* This is used to throw an exception when the setjmp/longjmp codegen
   method is used for exception handling.

   We call __terminate if there are no handlers left (we know this
   when the dynamic handler chain is top_elt).  Otherwise we run the
   cleanup actions off the dynamic cleanup stack, and pop the top of
   the dynamic handler chain, and use longjmp to transfer back to the
   associated handler.  */

void
__sjthrow ()
{
  void ***dhc = __get_dynamic_handler_chain ();
  void *jmpbuf;
  void (*func)(void *, int);
  void *arg;
  void ***cleanup;

  /* The cleanup chain is one word into the buffer.  Get the cleanup
     chain.  */
  cleanup = (void***)&(*dhc)[1];

  /* If there are any cleanups in the chain, run them now.  */
  if (cleanup[0])
    {
      double store[200];
      void **buf = (void**)store;
      buf[1] = 0;
      buf[0] = (*dhc);

      /* try { */
#ifdef DONT_USE_BUILTIN_SETJMP
      if (! setjmp (&buf[2]))
#else
      if (! __builtin_setjmp (&buf[2]))
#endif
	{
	  *dhc = buf;
	  while (cleanup[0])
	    {
	      func = (void(*)(void*, int))cleanup[0][1];
	      arg = (void*)cleanup[0][2];

	      /* Update this before running the cleanup.  */
	      cleanup[0] = (void **)cleanup[0][0];

	      (*func)(arg, 2);
	    }
	  *dhc = buf[0];
	}
      /* catch (...) */
      else
	{
	  __terminate ();
	}
    }
  
  /* We must call terminate if we try and rethrow an exception, when
     there is no exception currently active and when there are no
     handlers left.  */
  if (! __eh_info || (*dhc) == top_elt)
    __terminate ();
    
  /* Find the jmpbuf associated with the top element of the dynamic
     handler chain.  The jumpbuf starts two words into the buffer.  */
  jmpbuf = &(*dhc)[2];

  /* Then we pop the top element off the dynamic handler chain.  */
  *dhc = (void**)(*dhc)[0];

  /* And then we jump to the handler.  */

#ifdef DONT_USE_BUILTIN_SETJMP
  longjmp (jmpbuf, 1);
#else
  __builtin_longjmp (jmpbuf, 1);
#endif
}

/* Run cleanups on the dynamic cleanup stack for the current dynamic
   handler, then pop the handler off the dynamic handler stack, and
   then throw.  This is used to skip the first handler, and transfer
   control to the next handler in the dynamic handler stack.  */

void
__sjpopnthrow ()
{
  void ***dhc = __get_dynamic_handler_chain ();
  void *jmpbuf;
  void (*func)(void *, int);
  void *arg;
  void ***cleanup;

  /* The cleanup chain is one word into the buffer.  Get the cleanup
     chain.  */
  cleanup = (void***)&(*dhc)[1];

  /* If there are any cleanups in the chain, run them now.  */
  if (cleanup[0])
    {
      double store[200];
      void **buf = (void**)store;
      buf[1] = 0;
      buf[0] = (*dhc);

      /* try { */
#ifdef DONT_USE_BUILTIN_SETJMP
      if (! setjmp (&buf[2]))
#else
      if (! __builtin_setjmp (&buf[2]))
#endif
	{
	  *dhc = buf;
	  while (cleanup[0])
	    {
	      func = (void(*)(void*, int))cleanup[0][1];
	      arg = (void*)cleanup[0][2];

	      /* Update this before running the cleanup.  */
	      cleanup[0] = (void **)cleanup[0][0];

	      (*func)(arg, 2);
	    }
	  *dhc = buf[0];
	}
      /* catch (...) */
      else
	{
	  __terminate ();
	}
    }

  /* Then we pop the top element off the dynamic handler chain.  */
  *dhc = (void**)(*dhc)[0];

  __sjthrow ();
}

/* Support code for all exception region-based exception handling.  */

/* This value identifies the place from which an exception is being
   thrown.  */

void *__eh_pc;

#ifdef EH_TABLE_LOOKUP

EH_TABLE_LOOKUP

#else

typedef struct exception_table {
  void *start;
  void *end;
  void *exception_handler;
} exception_table;

/* This routine takes a PC and a pointer to the exception region TABLE for
   its translation unit, and returns the address of the exception handler
   associated with the closest exception table handler entry associated
   with that PC, or 0 if there are no table entries the PC fits in.

   In the advent of a tie, we have to give the last entry, as it represents
   an inner block.  */

static void *
find_exception_handler (void *pc, exception_table *table)
{
  if (table)
    {
      int pos;
      int best = -1;

      /* We can't do a binary search because the table isn't guaranteed
	 to be sorted from function to function.  */
      for (pos = 0; table[pos].exception_handler != (void *) -1; ++pos)
	{
	  if (table[pos].start <= pc && table[pos].end > pc)
	    {
	      /* This can apply.  Make sure it is at least as small as
		 the previous best.  */
	      if (best == -1 || (table[pos].end <= table[best].end
				 && table[pos].start >= table[best].start))
		best = pos;
	    }
	  /* But it is sorted by starting PC within a function.  */
	  else if (best >= 0 && table[pos].start > pc)
	    break;
	}
      if (best != -1)
	return table[best].exception_handler;
    }

  return (void *) 0;
}
#endif /* EH_TABLE_LOOKUP */

#ifndef DWARF2_UNWIND_INFO
/* Support code for exception handling using inline unwinders or
   __unwind_function.  */

#ifndef EH_TABLE_LOOKUP
typedef struct exception_table_node {
  exception_table *table;
  void *start;
  void *end;
  struct exception_table_node *next;
} exception_table_node;

static struct exception_table_node *exception_table_list;

void *
__find_first_exception_table_match (void *pc)
{
  register exception_table_node *tnp;

  for (tnp = exception_table_list; tnp != 0; tnp = tnp->next)
    {
      if (tnp->start <= pc && tnp->end >= pc)
	return find_exception_handler (pc, tnp->table);
    }

  return (void *) 0;
}

void
__register_exceptions (exception_table *table)
{
  exception_table_node *node;
  exception_table *range = table + 1;

  if (range->start == (void *) -1)
    return;

  node = (exception_table_node *) malloc (sizeof (exception_table_node));
  node->table = table;

  /* This look can be optimized away either if the table
     is sorted, or if we pass in extra parameters.  */
  node->start = range->start;
  node->end = range->end;
  for (range++ ; range->start != (void *) (-1); range++)
    {
      if (range->start < node->start)
	node->start = range->start;
      if (range->end > node->end)
	node->end = range->end;
    }

  node->next = exception_table_list;
  exception_table_list = node;
}
#endif /* !EH_TABLE_LOOKUP */

/* Throw stub routine.

   This is work in progress, but not completed yet.  */

void
__throw ()
{
  abort ();
}

/* See expand_builtin_throw for details.  */

void **__eh_pcnthrow () {
  static void *buf[2] = {
    &__eh_pc,
    &__throw
  };
  return buf;
}

#if #machine(i386)
void
__unwind_function(void *ptr)
{
  asm("movl 8(%esp),%ecx");
  /* Undo current frame */
  asm("movl %ebp,%esp");
  asm("popl %ebp");
  /* like ret, but stay here */
  asm("addl $4,%esp");
  
  /* Now, undo previous frame.  */
  /* This is a test routine, as we have to dynamically probe to find out
     what to pop for certain, this is just a guess.  */
  asm("leal -16(%ebp),%esp");
  asm("pop %ebx");
  asm("pop %esi");
  asm("pop %edi");
  asm("movl %ebp,%esp");
  asm("popl %ebp");

  asm("movl %ecx,0(%esp)");
  asm("ret");
}
#elif #machine(rs6000) && !defined _ARCH_PPC
__unwind_function(void *ptr)
{
  asm("mr 31,1");
  asm("l 1,0(1)");
  asm("l 31,-4(1)");
  asm("# br");

  asm("mr 31,1");
  asm("l 1,0(1)");
  /* use 31 as a scratch register to restore the link register.  */
  asm("l 31, 8(1);mtlr 31 # l lr,8(1)");
  asm("l 31,-4(1)");
  asm("# br");
  asm("mtctr 3;bctr # b 3");
}
#elif (#machine(rs6000) || #machine(powerpc)) && defined _ARCH_PPC
__unwind_function(void *ptr)
{
  asm("mr 31,1");
  asm("lwz 1,0(1)");
  asm("lwz 31,-4(1)");
  asm("# br");

  asm("mr 31,1");
  asm("lwz 1,0(1)");
  /* use 31 as a scratch register to restore the link register.  */
  asm("lwz 31, 8(1);mtlr 31 # l lr,8(1)");
  asm("lwz 31,-4(1)");
  asm("# br");
  asm("mtctr 3;bctr # b 3");
}
#elif #machine(vax)
__unwind_function(void *ptr)
{
  __label__ return_again;

  /* Replace our frame's return address with the label below.
     During execution, we will first return here instead of to
     caller, then second return takes caller's frame off the stack.
     Two returns matches two actual calls, so is less likely to
     confuse debuggers.  `16' corresponds to RETURN_ADDRESS_OFFSET.  */
  __asm ("movl %0,16(fp)" : : "p" (&& return_again));
  return;

 return_again:
  return;
}
#else
__unwind_function(void *ptr)
{
  abort ();
}
#endif /* powerpc */

#else /* DWARF2_UNWIND_INFO */
/* Support code for exception handling using static unwind information.  */

#include "frame.h"

/* This type is used in get_reg and put_reg to deal with ABIs where a void*
   is smaller than a word, such as the Irix 6 n32 ABI.  We cast twice to
   avoid a warning about casting between int and pointer of different
   sizes.  */

typedef int ptr_type __attribute__ ((mode (pointer)));

/* Get the value of register REG as saved in UDATA, where SUB_UDATA is a
   frame called by UDATA or 0.  */

static void*
get_reg (unsigned reg, frame_state *udata, frame_state *sub_udata)
{
  if (udata->saved[reg] == REG_SAVED_OFFSET)
    return (void *)(ptr_type)
      *(word_type *)(udata->cfa + udata->reg_or_offset[reg]);
  else if (udata->saved[reg] == REG_SAVED_REG && sub_udata)
    return get_reg (udata->reg_or_offset[reg], sub_udata, 0);
  else
    abort ();
}

/* Overwrite the saved value for register REG in frame UDATA with VAL.  */

static void
put_reg (unsigned reg, void *val, frame_state *udata)
{
  if (udata->saved[reg] == REG_SAVED_OFFSET)
    *(word_type *)(udata->cfa + udata->reg_or_offset[reg])
      = (word_type)(ptr_type) val;
  else
    abort ();
}

/* Copy the saved value for register REG from frame UDATA to frame
   TARGET_UDATA.  Unlike the previous two functions, this can handle
   registers that are not one word large.  */

static void
copy_reg (unsigned reg, frame_state *udata, frame_state *target_udata)
{
  if (udata->saved[reg] == REG_SAVED_OFFSET
      && target_udata->saved[reg] == REG_SAVED_OFFSET)
    memcpy (target_udata->cfa + target_udata->reg_or_offset[reg],
	    udata->cfa + udata->reg_or_offset[reg],
	    __builtin_dwarf_reg_size (reg));
  else
    abort ();
}

/* Retrieve the return address for frame UDATA, where SUB_UDATA is a
   frame called by UDATA or 0.  */

static inline void *
get_return_addr (frame_state *udata, frame_state *sub_udata)
{
  return __builtin_extract_return_addr
    (get_reg (udata->retaddr_column, udata, sub_udata));
}

/* Overwrite the return address for frame UDATA with VAL.  */

static inline void
put_return_addr (void *val, frame_state *udata)
{
  val = __builtin_frob_return_addr (val);
  put_reg (udata->retaddr_column, val, udata);
}

/* Given the current frame UDATA and its return address PC, return the
   information about the calling frame in CALLER_UDATA.  */

static void *
next_stack_level (void *pc, frame_state *udata, frame_state *caller_udata)
{
  caller_udata = __frame_state_for (pc, caller_udata);
  if (! caller_udata)
    return 0;

  /* Now go back to our caller's stack frame.  If our caller's CFA register
     was saved in our stack frame, restore it; otherwise, assume the CFA
     register is SP and restore it to our CFA value.  */
  if (udata->saved[caller_udata->cfa_reg])
    caller_udata->cfa = get_reg (caller_udata->cfa_reg, udata, 0);
  else
    caller_udata->cfa = udata->cfa;
  caller_udata->cfa += caller_udata->cfa_offset;

  return caller_udata;
}

#ifdef INCOMING_REGNO
/* Is the saved value for register REG in frame UDATA stored in a register
   window in the previous frame?  */

static int
in_reg_window (int reg, frame_state *udata)
{
  if (udata->saved[reg] != REG_SAVED_OFFSET)
    return 0;

#ifdef STACK_GROWS_DOWNWARD
  return udata->reg_or_offset[reg] > 0;
#else
  return udata->reg_or_offset[reg] < 0;
#endif
}
#endif /* INCOMING_REGNO */

/* We first search for an exception handler, and if we don't find
   it, we call __terminate on the current stack frame so that we may
   use the debugger to walk the stack and understand why no handler
   was found.

   If we find one, then we unwind the frames down to the one that
   has the handler and transfer control into the handler.  */

void
__throw ()
{
  void *pc, *handler, *retaddr;
  frame_state ustruct, ustruct2;
  frame_state *udata = &ustruct;
  frame_state *sub_udata = &ustruct2;
  frame_state my_ustruct, *my_udata = &my_ustruct;
  long args_size;

  /* This is required for C++ semantics.  We must call terminate if we
     try and rethrow an exception, when there is no exception currently
     active.  */
  if (! __eh_info)
    __terminate ();
    
  /* Start at our stack frame.  */
label:
  udata = __frame_state_for (&&label, udata);
  if (! udata)
    __terminate ();

  /* We need to get the value from the CFA register.  At this point in
     compiling __throw we don't know whether or not we will use the frame
     pointer register for the CFA, so we check our unwind info.  */
  if (udata->cfa_reg == __builtin_dwarf_fp_regnum ())
    udata->cfa = __builtin_fp ();
  else
    udata->cfa = __builtin_sp ();
  udata->cfa += udata->cfa_offset;

  memcpy (my_udata, udata, sizeof (*udata));

  /* Do any necessary initialization to access arbitrary stack frames.
     On the SPARC, this means flushing the register windows.  */
  __builtin_unwind_init ();

  /* Now reset pc to the right throw point.  */
  pc = __eh_pc;

  handler = 0;
  for (;;)
    { 
      frame_state *p = udata;
      udata = next_stack_level (pc, udata, sub_udata);
      sub_udata = p;

      /* If we couldn't find the next frame, we lose.  */
      if (! udata)
	break;

      handler = find_exception_handler (pc, udata->eh_ptr);

      /* If we found one, we can stop searching.  */
      if (handler)
	{
	  args_size = udata->args_size;
	  break;
	}

      /* Otherwise, we continue searching.  We subtract 1 from PC to avoid
	 hitting the beginning of the next region.  */
      pc = get_return_addr (udata, sub_udata) - 1;
    }

  /* If we haven't found a handler by now, this is an unhandled
     exception.  */
  if (! handler)
    __terminate ();

  if (pc == __eh_pc)
    /* We found a handler in the throw context, no need to unwind.  */
    udata = my_udata;
  else
    {
      int i;
      void *val;

      /* Unwind all the frames between this one and the handler by copying
	 their saved register values into our register save slots.  */

      /* Remember the PC where we found the handler.  */
      void *handler_pc = pc;

      /* Start from the throw context again.  */
      pc = __eh_pc;
      memcpy (udata, my_udata, sizeof (*udata));

      while (pc != handler_pc)
	{
	  frame_state *p = udata;
	  udata = next_stack_level (pc, udata, sub_udata);
	  sub_udata = p;

	  for (i = 0; i < FIRST_PSEUDO_REGISTER; ++i)
	    if (i != udata->retaddr_column && udata->saved[i])
	      {
#ifdef INCOMING_REGNO
		/* If you modify the saved value of the return address
		   register on the SPARC, you modify the return address for
		   your caller's frame.  Don't do that here, as it will
		   confuse get_return_addr.  */
		if (in_reg_window (i, udata)
		    && udata->saved[udata->retaddr_column] == REG_SAVED_REG
		    && udata->reg_or_offset[udata->retaddr_column] == i)
		  continue;
#endif
		copy_reg (i, udata, my_udata);
	      }

	  pc = get_return_addr (udata, sub_udata) - 1;
	}

#ifdef INCOMING_REGNO
      /* But we do need to update the saved return address register from
	 the last frame we unwind, or the handler frame will have the wrong
	 return address.  */
      if (udata->saved[udata->retaddr_column] == REG_SAVED_REG)
	{
	  i = udata->reg_or_offset[udata->retaddr_column];
	  if (in_reg_window (i, udata))
	    copy_reg (i, udata, my_udata);
	}
#endif
    }
  /* udata now refers to the frame called by the handler frame.  */

  /* Emit the stub to adjust sp and jump to the handler.  */
  retaddr = __builtin_eh_stub ();

  /* And then set our return address to point to the stub.  */
  if (my_udata->saved[my_udata->retaddr_column] == REG_SAVED_OFFSET)
    put_return_addr (retaddr, my_udata);
  else
    __builtin_set_return_addr_reg (retaddr);

  /* Set up the registers we use to communicate with the stub.
     We check STACK_GROWS_DOWNWARD so the stub can use adjust_stack.  */
  __builtin_set_eh_regs (handler,
#ifdef STACK_GROWS_DOWNWARD
			 udata->cfa - my_udata->cfa
#else
			 my_udata->cfa - udata->cfa
#endif
			 + args_size
			 );

  /* Epilogue:  restore the handler frame's register values and return
     to the stub.  */
}
#endif /* !DWARF2_UNWIND_INFO */
