/* vim: set filetype=c:
 *
 * exception.c86  -  MGL runtime exception handling
 *
 * Copyright (C) 2004-2007 Gero Kuhlmann   <gero@gkminix.han.de>
 *
 *  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 2 of the License, or
 *  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, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: exception.c86,v 1.7 2007/01/06 18:31:38 gkminix Exp $
 */

#include "rtcommon.h86"
#include <i386/memory.h86>
#include <i386/setjmp.h86>



/*
 *****************************************************************************
 *
 * Global exception code
 */
short exception = ENOERROR;



/*
 *****************************************************************************
 *
 * Structure holding exception information
 */
struct exceptinfo {
	jmp_buf            env;
	struct exceptinfo *next;
};

static struct exceptinfo *exceptstack = NULL;



/*
 *****************************************************************************
 *
 * Export variables and constants
 */
__EXPORT_VARCONST(exception, NUM, 0);

__EXPORT_CONST(EGeneralError, NUM, EGENERROR);
__EXPORT_CONST(EInternal, NUM, EINTERNAL);
__EXPORT_CONST(EDivByZero, NUM, EDIVZERO);
__EXPORT_CONST(EOutOfBounds, NUM, EOUTOFBOUNDS);
__EXPORT_CONST(EIntOverflow, NUM, EOVERFLOW);
__EXPORT_CONST(ENoMemory, NUM, ENOMEMORY);
__EXPORT_CONST(EStackOverflow, NUM, ESTKOVERFLOW);
__EXPORT_CONST(EInvalidPointer, NUM, EINVPOINTER);
__EXPORT_CONST(ELoadUnsupported, NUM, ELOADUNSUPPORTED);
__EXPORT_CONST(ELoadPXE, NUM, ELOADPXE);
__EXPORT_CONST(ELoadDisk, NUM, ELOADDISK);
__EXPORT_CONST(ELoadHeap, NUM, ELOADHEAP);
__EXPORT_CONST(ELoadCopy, NUM, ELOADCOPY);
__EXPORT_CONST(ELoadHeader, NUM, ELOADHEADER);
__EXPORT_CONST(ELoadMemory, NUM, ELOADMEMORY);



/*
 *****************************************************************************
 *
 * Raise a new exception. This routine can also be called from within
 * any runtime function.
 */
void rtint_raise(short val)
{
  char *msg;
  struct exceptinfo *cur;
  static jmp_buf env;
  bool_t userflag = ((val & NOUSER_ERROR_FLAG) == 0);

  /* Remove any flags from exception value */
  val &= ~NOUSER_ERROR_FLAG;

  /* Check if we have to use the previous error code */
  if (val == 0) {
	if (exception == ENOERROR)
		val = EGENERROR;
	else
		val = exception;
  }
  exception = val;

  /* If we have a valid exception info, jump to the handler */
  if (userflag && exceptstack != NULL) {
	cur = exceptstack;
	exceptstack = cur->next;
	memcpy(&env, &(cur->env), sizeof(env));
	free(cur);
	longjmp(&env, val);
  }

  /* If we don't have a handler, generate a runtime error */
  switch (val) {
	case EGENERROR:
		msg = "General error";
		break;
	case EINTERNAL:
		msg = "Internal runtime error";
		break;
	case EDIVZERO:
		msg = "Division by zero";
		break;
	case EOUTOFBOUNDS:
		msg = "Scalar value out of bounds";
		break;
	case EOVERFLOW:
		msg = "Integer overflow";
		break;
	case ENOMEMORY:
		msg = "Not enough heap memory";
		break;
	case ESTKOVERFLOW:
		msg = "Stack overflow";
		break;
	case ELOADUNSUPPORTED:
		msg = "Boot image loading unsupported";
		break;
	case ELOADPXE:
		msg = "PXE error while loading boot image";
		break;
	case ELOADDISK:
		msg = "Disk error while loading boot image";
		break;
	case ELOADHEAP:
		msg = "Not enough free memory to load boot image";
		break;
	case ELOADCOPY:
		msg = "Error while copying boot image";
		break;
	case ELOADHEADER:
		msg = "Invalid boot image header";
		break;
	case ELOADMEMORY:
		msg = "Not enough extended memory to load boot image";
		break;
	default:
		msg = "Unknown error";
		break;
  }
  rtint_error(msg);
}



/*
 *****************************************************************************
 *
 * Exportable version of raise(). We dont use the __INTERNAL and
 * __PUBLIC macros because this routine should not check the stack.
 */
__attribute__((used_by_asm,noreturn)) static void rtproc_raise(void)
{
  register __u8 *_args = ((__u8 *)__builtin_frame_address(0)) + 8;
  rtint_raise(*((short *)&(_args[0])));
}


/* Export info */
__EXPORT_PROC(raise, NONE, 1, __ARGS(__D(NUM, NONE)));



/*
 *****************************************************************************
 *
 * Start handling a try...except...end block
 */
__INTERNAL(handletry, short, void)
{
  struct exceptinfo *new;
  register __u16 envptr;

  /*
   * Reset the global exception code. This way, endexcept knows if
   * it has been called due to an exception or just because the
   * try-block has been finished.
   */
  exception = ENOERROR;

  /*
   * Create a new exception info block. If this fails, we can
   * safely use rtint_raise here, because it will then still use
   * the old exception info list.
   */
  new = (struct exceptinfo *)malloc(sizeof(struct exceptinfo));
  if (new == NULL)
	rtint_raise(ENOMEMORY);

  /*
   * Insert new info block into exception handler list, and
   * initialize the longjmp environment. This routine will
   * return with an exception code when the longjmp has been
   * executed.
   * The assembly code adjusts the stack so that it seems like
   * the setjmp routine has been called by the calling routine
   * directly. This is necessary because the stack frame becomes
   * invalid when this routine returns normally - and the current
   * stack frame gets stored in the setjmp environment.
   */
  new->next = exceptstack;
  exceptstack = new;
  envptr = (__u16)((int)(&(new->env)));
  __asm__ __volatile__(
	"leave\n\t"
	"popw %%bx\n\t"
	"jmp _setjmp"
	: "=a" (envptr)
	: "a" (envptr));

  /*
   * We need to insert a return here to make the compiler happy, even though
   * it will never get reached by the execution flow.
   */
  return((short)envptr);
}


/* Function wrapper */
__PUBLIC(handletry, 0);


/* Export info */
__EXPORT_PROC(handletry, NUM, 0);



/*
 *****************************************************************************
 *
 * End handling a try...except...end block
 */
__INTERNAL(endexcept, short, void)
{
  struct exceptinfo *ep;

  /* Remove last exception handler info from stack */
  if (exception == ENOERROR) {
	ep = exceptstack;
	if (ep != NULL) {
		exceptstack = ep->next;
		free(ep);
	}
  }
  return(0);
}


/* Function wrapper */
__PUBLIC(endexcept, 0);


/* Export info */
__EXPORT_PROC(endexcept, NONE, 0);

