/* seh.c: Structured exception handling in the compiler */
#include "c.h"
extern int getTvalValue(void);
extern int getLevel(void);

int hasExceptions=0;
typedef struct tagExceptionInfo {
	struct tagExceptionInfo *Next;
	struct tagExceptionInfo *Previous;
	int l1;
	int l2;
	int l3;
	int value;
	int Flags;
} ExceptionInfo;

#define FINISHED 1

static ExceptionInfo *pCurrentException=NULL;
static ExceptionInfo *rootException=NULL;
static int StaticDataEmitted = 0;
/* Add another exception to the exception stack */
ExceptionInfo *PushException(void)
{
	ExceptionInfo *result;

	if (rootException == NULL)	{
		rootException =  (ExceptionInfo *)allocate(sizeof(ExceptionInfo),FUNC);
		result = rootException;
		memset(result,0,sizeof(ExceptionInfo));
	}
	else {
		/* search the last element */
		result = rootException;
		while (result->Next) {
			result = result->Next;
		}
		result->Next = (ExceptionInfo *)allocate(sizeof(ExceptionInfo),FUNC);
		memset(result->Next,0,sizeof(ExceptionInfo));
		result->Next->Previous = result;
		result = result->Next;
	}
	return(result);
}

/*
	No 'free' is done, since all the data is allocated in the FUNC heap
	that will be freed at the end of the function.
*/
void PopException(void)
{
	if (pCurrentException->Previous) {
		pCurrentException = pCurrentException->Previous;
		pCurrentException->Next = NULL;
	}
	else {
		rootException = NULL;
		pCurrentException = NULL;
	}
}

/*
	Utility function to add an assembler instruction to the code list.
*/
static void AddAsm(char *str)
{
	char *s;

	s = stringn(str,strlen(str));
	walk(0, 0, 0);
	code(Start);    /* prevent unreachable code message */
	code(Asm);
	codelist->u.acode.code = s;
	codelist->u.acode.argv = NULL;
}

int doTry(void)
{
	char tmpnom[25],tmpbuf[256];
	int l1,l2,l3;
	Symbol sl;

	/* 1. Push the exceptions stack by one */
	pCurrentException = PushException();
	/* Announce to the runtime that the static data is active
	   within this block. Exceptions are enabled */
	AddAsm("\tmovl\t$0x0,-4(%ebp)");
	/* Update this global variable used in a few parts of lcc */
	hasExceptions++;
	/* Generate 3 labels:
	   The first (label 1) marks the beginning of the first part
	   of code that will be called to determine if the handler
	   handles the exception or not.
	   The second label will announce the start of the exception
	   handler.
	   The third announces the continuation of the normal code
	   section
	*/
	l1 = genlabel(1);
	sl = findlabel(l1);
	sl->ref = 1.0;
	l2 = genlabel(1);
	sl = findlabel(l2);
	sl->ref = 1.0;
	l3 = genlabel(1);
	sl = findlabel(l3);
	sl->ref = 1.0;
	pCurrentException->l1 = l1;
	pCurrentException->l2 = l2;
	pCurrentException->l3 = l3;
	/* The static data needed here should be emitted only once
	   per compiled file
	*/
	if (StaticDataEmitted == 0) {
		AddAsm("\t.data");
		sprintf(tmpbuf,"_$ExcepData:",tmpnom);
		AddAsm(tmpbuf);
		AddAsm("\t.long\t0xffffffff");
		sprintf(tmpbuf,"\t.long\t_$%d",l1);
		AddAsm(tmpbuf);
		sprintf(tmpbuf,"\t.long\t_$%d",l2);
		AddAsm(tmpbuf);
		AddAsm("\t.text");
		StaticDataEmitted = 1;
	}
	/* If we are in a nested exception block, the static data
	   should point to the two labels that contain the two blocks
	   of code to be called by the runtime
	*/
	if (pCurrentException != rootException) {
		AddAsm("\tmovl\t$_$ExcepData,%eax");
		sprintf(tmpbuf,"\tmovl\t$_$%d,4(%%eax)",l1);
		AddAsm(tmpbuf);
		sprintf(tmpbuf,"\tmovl\t$_$%d,8(%%eax)",l2);
		AddAsm(tmpbuf);
	}
	return(1);
}

static void RestorePreviousException(void)
{
	char tmpbuf[256];

	/*
		To avoid traps in the trap handlers, the runtime stores
		a continuation address at -4(%ebp). If we arrive here,
		everything is O.K. We have to restore then the zero at
		-4(%ebp) to go on normally
	*/
	AddAsm("\tmovl\t$0x0,-4(%ebp)");

	AddAsm("\tmovl\t$_$ExcepData,%eax");
	sprintf(tmpbuf,"\tmovl\t$_$%d,4(%%eax)",pCurrentException->Previous->l1);
	AddAsm(tmpbuf);
	sprintf(tmpbuf,"\tmovl\t$_$%d,8(%%eax)",pCurrentException->Previous->l2);
	AddAsm(tmpbuf);		
}

static void EmitFilterCode(void)
{
	char tmpbuf[256];

	sprintf(tmpbuf,"_$%d:",pCurrentException->l1);
	AddAsm(tmpbuf);
	sprintf(tmpbuf,"\tmovl\t$%d,%%eax",pCurrentException->value);
	AddAsm(tmpbuf);
	AddAsm("\tret");
}

int FinishTryBlock(void)
{
	char tmpbuf[256];

	if (pCurrentException->Flags & FINISHED) return(0);
	if (pCurrentException == rootException) {
		/* no exceptions are now active. Announce it to the
		   runtime
		*/
		AddAsm("\tmovl\t$-1,-4(%ebp)");
	}
	else {
		RestorePreviousException();
	}
	/* emit a jump to the end of the exception stuff */
	sprintf(tmpbuf,"\tjmp\t_$%d",pCurrentException->l3);
	AddAsm(tmpbuf);
	/* Avoid emitting this code again */
	pCurrentException->Flags |= FINISHED;
	return(1);
}

int doExcept(void)
{
	char tmpbuf[20];

	/* No error treatment yet. Any syntax error leaves the parser
	   in a mess...
	 */
	t = gettok();
	if (t != '(') {
		error("Incorrect __except syntax\n");
		return(0);
	}
	t = gettok();
	if (t != ICON) {
		error("Incorrect __except expression!");
		return(0);
	}
	/* This should be actually an evaluation of an integer expression */
	pCurrentException->value = getTvalValue();
	t = gettok();
	if (t != ')') {
		error("missing ')' in __except expression");
		return(0);
	}
	FinishTryBlock();
	EmitFilterCode();
	/* Emit the label that marks the beginning of the exception handler
	   code
	*/
	sprintf(tmpbuf,"_$%d:",pCurrentException->l2);
	AddAsm(tmpbuf);
	/* Emit code to restore the stack  when the exception 
	   handler is called
	*/
	AddAsm("\tmovl\t-24(%ebp),%esp");
	/* Parse the exception handler code */
	t = gettok(); /* swallow the '{' */
	compound(0,NULL,getLevel());
	if (pCurrentException == rootException) {
		/*
			We are at the end of the top level exception block.
			Announce to the runtime there is no exception handler active
		*/
		AddAsm("\tmovl\t$-1,-4(%ebp)");
	}
	else {
		RestorePreviousException();
	}
	/* Continuation of the normal code sequence */
	sprintf(tmpbuf,"_$%d:",pCurrentException->l3);
	AddAsm(tmpbuf);
	/* Clean up the exception stack, popping one level */
	PopException();
	return(1);
}

void ResetExceptions(void)
{
	rootException = NULL;
}
