//
// Copyright (c) 1999-2003 by Digital Mars, www.digitalmars.com
// All Rights Reserved
// Written by Walter Bright

// Exception handling support

#include	<stdio.h>
#include	<string.h>
#include	<assert.h>
#include	<stdlib.h>

/* ======================== Win32 =============================== */

#if _WIN32

#include	<excpt.h>
#include	<windows.h>

//#include	"\sc\src\include\ehsup.h"

/*** From Digital Mars C runtime library ***/
EXCEPTION_DISPOSITION __cdecl _local_except_handler (EXCEPTION_RECORD *ExceptionRecord,
    void* EstablisherFrame,
	void *ContextRecord,
	void *DispatcherContext
	);
void __cdecl _global_unwind(void *frame,EXCEPTION_RECORD *eRecord);
#define EXCEPTION_UNWIND  6  // Flag to indicate if the system is unwinding

extern DWORD _except_list;
/*** ***/

#include	"mars.h"

extern ClassInfo D9Exception7__ClassZ;

#define _Class_9Exception D9Exception7__ClassZ

typedef int (__pascal *fp_t)();   // function pointer in ambient memory model

// The layout of DEstablisherFrame is the same for C++

struct DEstablisherFrame
{
    void *prev;			// pointer to previous exception list
    void *handler;		// pointer to routine for exception handler
    DWORD table_index;		// current index into handler_info[]
    DWORD ebp;			// this is EBP of routine
};

struct DHandlerInfo
{
    int prev_index;		// previous table index
    unsigned cioffset;		// offset to DCatchInfo data from start of table (!=0 if try-catch)
    void *finally_code;		// pointer to finally code to execute
				// (!=0 if try-finally)
};

// Address of DHandlerTable is passed in EAX to _d_framehandler()

struct DHandlerTable
{
    void *fptr;			// pointer to start of function
    unsigned espoffset;		// offset of ESP from EBP
    unsigned retoffset;		// offset from start of function to return code
    struct DHandlerInfo handler_info[1];
};

struct DCatchBlock
{
    ClassInfo *type;		// catch type
    unsigned bpoffset;		// EBP offset of catch var
    void *code;			// catch handler code
};

// Create one of these for each try-catch
struct DCatchInfo
{
    unsigned ncatches;			// number of catch blocks
    struct DCatchBlock catch_block[1];	// data for each catch block
};

// Macro to make our own exception code
#define MAKE_EXCEPTION_CODE(severity, facility, exception)	\
	(((severity) << 30) | (1 << 29) | (0 << 28) | ((facility) << 16) | (exception))

#define STATUS_DIGITAL_MARS_D_EXCEPTION		MAKE_EXCEPTION_CODE(3,'D',1)

Object *_d_translate_se_to_d_exception(EXCEPTION_RECORD *exception_record);
void __cdecl _d_local_unwind(struct DHandlerTable *handler_table, struct DEstablisherFrame *frame, int stop_index);


/***********************************
 * The frame handler, this is called for each frame that has been registered
 * in the OS except_list.
 * Input:
 * 	EAX	the handler table for the frame
 */

EXCEPTION_DISPOSITION _d_framehandler(
	    EXCEPTION_RECORD *exception_record, 
	    struct DEstablisherFrame *frame, 
	    CONTEXT context,
	    void *dispatcher_context)
{
    struct DHandlerTable *handler_table;

    __asm { mov handler_table,EAX }

    if (exception_record->ExceptionFlags & EXCEPTION_UNWIND)
    {
         // Call all the finally blocks in this frame
         _d_local_unwind(handler_table, frame, -1);
    }
    else
    {
	// Jump to catch block if matching one is found

	int ndx,prev_ndx,i;
	struct DHandlerInfo *phi;
	struct DCatchInfo *pci;
	struct DCatchBlock *pcb;
	unsigned ncatches;		// number of catches in the current handler
	Object *pti;
	ClassInfo *ci;

	ci = NULL;			// only compute it if we need it

	// walk through handler table, checking each handler
	// with an index smaller than the current table_index
	for (ndx = frame->table_index; ndx != -1; ndx = prev_ndx)
	{
	    phi = &handler_table->handler_info[ndx];
	    prev_ndx = phi->prev_index;
	    if (phi->cioffset)
	    {
		// this is a catch handler (no finally)
		pci = (struct DCatchInfo *)((char *)handler_table + phi->cioffset);
		ncatches = pci->ncatches;
		for (i = 0; i < ncatches; i++)
		{
		    pcb = &pci->catch_block[i];

		    if (!ci)
		    {
			// This code must match the translation code
			if (exception_record->ExceptionCode == STATUS_DIGITAL_MARS_D_EXCEPTION)
			{
			    //printf("ei[0] = %p\n", exception_record->ExceptionInformation[0]);
			    ci = **(ClassInfo ***)(exception_record->ExceptionInformation[0]);
			}
			else
			    ci = &_Class_9Exception;
		    }

		    if (_d_isbaseof(ci, pcb->type))
		    {   // Matched the catch type, so we've found the handler.
			int regebp;

			pti = _d_translate_se_to_d_exception(exception_record);

			// Initialize catch variable
			regebp = (int)&frame->ebp;		// EBP for this frame
			*(void **)(regebp + (pcb->bpoffset)) = pti;

			// Have system call all finally blocks in intervening frames
			_global_unwind(frame, exception_record);

			// Call all the finally blocks skipped in this frame
			_d_local_unwind(handler_table, frame, ndx);

			frame->table_index = prev_ndx;  // we are out of this handler

			// Jump to catch block. Does not return.
			{
			    unsigned catch_esp;
			    fp_t catch_addr;

			    catch_addr = (fp_t)(pcb->code);
			    catch_esp = regebp - handler_table->espoffset - sizeof(fp_t);
			    _asm
			    {
				mov	EAX,catch_esp
				mov	ECX,catch_addr
				mov	[EAX],ECX
				mov	EBP,regebp
				mov	ESP,EAX		// reset stack
				ret			// jump to catch block
			    }
			}
		    }
		}
	    }
	}
    }
    return ExceptionContinueSearch;
}

/***********************************
 * Exception filter for use in __try..__except block
 * surrounding call to Dmain()
 */

int _d_exception_filter(struct _EXCEPTION_POINTERS *eptrs,
			int retval,
			Object **exception_object) 
{
    *exception_object = _d_translate_se_to_d_exception(eptrs->ExceptionRecord);
    return retval;
}

/***********************************
 * Throw a D object.
 */

void __stdcall _d_throw(Object *h)
{
    //printf("_d_throw(h = %p, &h = %p)\n", h, &h);
    //printf("\tvptr = %p\n", *(void **)h);
    RaiseException(STATUS_DIGITAL_MARS_D_EXCEPTION,
		   EXCEPTION_NONCONTINUABLE,
		   1, (DWORD *)&h);
}

/***********************************
 * Create an exception object
 */

Object *_d_create_exception_object(ClassInfo *ci, char *msg)
{
    Exception *exc;

    exc = (Exception *)_d_newclass(ci);
    // BUG: what if _d_newclass() throws an out of memory exception?

    if (msg)
    {
	exc->msglen = strlen(msg);
	exc->msg = msg;
    }
    return (Object *)exc;
}

/***********************************
 * Converts a Windows Structured Exception code to a D Exception Object.
 */

Object *_d_translate_se_to_d_exception(EXCEPTION_RECORD *exception_record)
{
    Object *pti;

    switch (exception_record->ExceptionCode) {
        case STATUS_DIGITAL_MARS_D_EXCEPTION:
            // Generated D exception
            pti = (Object *)(exception_record->ExceptionInformation[0]);
            break;

        case STATUS_INTEGER_DIVIDE_BY_ZERO:
            pti = _d_create_exception_object(&_Class_9Exception, "Integer Divide by Zero");
            break;

        case STATUS_FLOAT_DIVIDE_BY_ZERO:
            pti = _d_create_exception_object(&_Class_9Exception, "Float Divide by Zero");
            break;

        case STATUS_ACCESS_VIOLATION:
            pti = _d_create_exception_object(&_Class_9Exception, "Access Violation");
            break;

        case STATUS_STACK_OVERFLOW:
            pti = _d_create_exception_object(&_Class_9Exception, "Stack Overflow");
            break;

        // convert all other exception codes into a Win32Exception
        default:
            pti = _d_create_exception_object(&_Class_9Exception, "Win32 Exception");
            break;
    }

    return pti;
}

/**************************************
 * Call finally blocks in the current stack frame until stop_index.
 * This is roughly equivalent to _local_unwind() for C in \src\win32\ehsup.c
 */

void __cdecl _d_local_unwind(struct DHandlerTable *handler_table,
	struct DEstablisherFrame *frame, int stop_index)
{
    struct DHandlerInfo *phi;
    struct DCatchInfo *pci;
    int i;

    // Set up a special exception handler to catch double-fault exceptions.
    __asm
    {
	push	dword ptr -1
	push	dword ptr 0
	push	offset _local_except_handler	// defined in src\win32\ehsup.c
	push	dword ptr fs:_except_list
	mov	FS:_except_list,ESP
    }
  
    for (i = frame->table_index; i != -1 && i != stop_index; i = phi->prev_index)
    {
	phi = &handler_table->handler_info[i];
	if (phi->finally_code)
	{
	    // Note that it is unnecessary to adjust the ESP, as the finally block
	    // accesses all items on the stack as relative to EBP.

	    DWORD *catch_ebp = &frame->ebp;
	    void *blockaddr = phi->finally_code;

	    _asm
	    {
		push	EBX
		mov	EBX,blockaddr
		push	EBP
		mov	EBP,catch_ebp
		call	EBX
		pop	EBP
		pop	EBX
	    }
	}
    }

    _asm
    {
	pop	FS:_except_list
	add	ESP,12
    }     
}

/***********************************
 * external version of the unwinder
 */

__declspec(naked) void __cdecl _d_local_unwind2()
{
    __asm
    {
	jmp	_d_local_unwind
    }
}

/***********************************
 * The frame handler, this is called for each frame that has been registered
 * in the OS except_list.
 * Input:
 * 	EAX	the handler table for the frame
 */

EXCEPTION_DISPOSITION _d_monitor_handler(
	    EXCEPTION_RECORD *exception_record, 
	    struct DEstablisherFrame *frame, 
	    CONTEXT context,
	    void *dispatcher_context)
{
    if (exception_record->ExceptionFlags & EXCEPTION_UNWIND)
    {
	_d_monitorexit((Object *)frame->table_index);
    }
    else
    {
    }
    return ExceptionContinueSearch;
}

/***********************************
 */

void _d_monitor_prolog(void *x, void *y, Object *h)
{
    __asm
    {
	push	EAX
    }
    //printf("_d_monitor_prolog(x=%p, y=%p, h=%p)\n", x, y, h);
    _d_monitorenter(h);
    __asm
    {
	pop	EAX
    }
}

/***********************************
 */

void _d_monitor_epilog(void *x, void *y, Object *h)
{
    //printf("_d_monitor_epilog(x=%p, y=%p, h=%p)\n", x, y, h);
    __asm
    {
	push	EAX
	push	EDX
    }
    _d_monitorexit(h);
    __asm
    {
	pop	EDX
	pop	EAX
    }
}

#endif

/* ======================== linux =============================== */

#if linux

#include	"mars.h"

extern ClassInfo D9Exception7__ClassZ;

#define _Class_9Exception D9Exception7__ClassZ

typedef int (*fp_t)();   // function pointer in ambient memory model

struct DHandlerInfo
{
    unsigned offset;		// offset from function address to start of guarded section
    int prev_index;		// previous table index
    unsigned cioffset;		// offset to DCatchInfo data from start of table (!=0 if try-catch)
    void *finally_code;		// pointer to finally code to execute
				// (!=0 if try-finally)
};

// Address of DHandlerTable, searched for by eh_finddata()

struct DHandlerTable
{
    void *fptr;			// pointer to start of function
    unsigned espoffset;		// offset of ESP from EBP
    unsigned retoffset;		// offset from start of function to return code
    unsigned nhandlers;		// dimension of handler_info[]
    struct DHandlerInfo handler_info[1];
};

struct DCatchBlock
{
    ClassInfo *type;		// catch type
    unsigned bpoffset;		// EBP offset of catch var
    void *code;			// catch handler code
};

// Create one of these for each try-catch
struct DCatchInfo
{
    unsigned ncatches;			// number of catch blocks
    struct DCatchBlock catch_block[1];	// data for each catch block
};

// One of these is generated for each function with try-catch or try-finally

struct FuncTable
{
    void *fptr;			// pointer to start of function
    struct DHandlerTable *handlertable; // eh data for this function
    unsigned size;		// size of function in bytes
};

extern struct FuncTable *table_start;
extern struct FuncTable *table_end;

void terminate()
{
//    _asm
//    {
//	hlt
//    }
}

/*******************************************
 * Given address that is inside a function,
 * figure out which function it is in.
 * Return DHandlerTable if there is one, NULL if not.
 */

struct DHandlerTable *__eh_finddata(void *address)
{
    struct FuncTable *ft;

    for (ft = (struct FuncTable *)table_start;
	 ft < (struct FuncTable *)table_end;
	 ft++)
    {
	if (ft->fptr <= address &&
	    address < (void *)((char *)ft->fptr + ft->size))
	{
	    return ft->handlertable;
	}
    }
    return NULL;
}


/******************************
 * Given EBP, find return address to caller, and caller's EBP.
 * Input:
 *   regbp       Value of EBP for current function
 *   *pretaddr   Return address
 * Output:
 *   *pretaddr   return address to caller
 * Returns:
 *   caller's EBP
 */

unsigned __eh_find_caller(unsigned regbp, unsigned *pretaddr)
{
    unsigned bp = *(unsigned *)regbp;

    if (bp)         // if not end of call chain
    {
	// Perform sanity checks on new EBP.
	// If it is screwed up, terminate() hopefully before we do more damage.
	if (bp <= regbp) 
	    // stack should grow to smaller values
	    terminate();

        *pretaddr = *(unsigned *)(regbp + sizeof(int));
    }
    return bp;
}

/***********************************
 * Throw a D object.
 */

void __stdcall _d_throw(Object *h)
{
    unsigned regebp;

    //printf("_d_throw(h = %p, &h = %p)\n", h, &h);
    //printf("\tvptr = %p\n", *(void **)h);

    regebp = _EBP;

    while (1)		// for each function on the stack
    {
        struct DHandlerTable *handler_table;
	struct FuncTable *pfunc;
	struct DHandlerInfo *phi;
	unsigned retaddr;
        unsigned funcoffset;
	unsigned spoff;
	unsigned retoffset;
        int index;
        int dim;
	int ndx;
	int prev_ndx;

        regebp = __eh_find_caller(regebp,&retaddr);
        if (!regebp) 
            // if end of call chain
            break;
        
        handler_table = __eh_finddata((void *)retaddr);   // find static data associated with function
        if (!handler_table)         // if no static data
        {   
            continue;
        }
        funcoffset = (unsigned)handler_table->fptr;
        spoff = handler_table->espoffset;
        retoffset = handler_table->retoffset;

#ifdef DEBUG
        printf("retaddr = x%x\n",(unsigned)retaddr);
        printf("regebp=x%04x, funcoffset=x%04x, spoff=x%x, retoffset=x%x\n",
        regebp,funcoffset,spoff,retoffset);
#endif

        // Find start index for retaddr in static data
        dim = handler_table->nhandlers;
        index = -1;
        for (int i = 0; i < dim; i++)
        {   
	    phi = &handler_table->handler_info[i];

            if ((unsigned)retaddr >= funcoffset + phi->offset)
                index = i;
        }

	// walk through handler table, checking each handler
	// with an index smaller than the current table_index
	for (ndx = index; ndx != -1; ndx = prev_ndx)
	{
	    phi = &handler_table->handler_info[ndx];
	    prev_ndx = phi->prev_index;
	    if (phi->cioffset)
	    {
		// this is a catch handler (no finally)
		struct DCatchInfo *pci;
		int ncatches;
		int i;

		pci = (struct DCatchInfo *)((char *)handler_table + phi->cioffset);
		ncatches = pci->ncatches;
		for (i = 0; i < ncatches; i++)
		{
		    struct DCatchBlock *pcb;
		    ClassInfo *ci = **(ClassInfo ***)h;

		    pcb = &pci->catch_block[i];

		    if (_d_isbaseof(ci, pcb->type))
		    {   // Matched the catch type, so we've found the handler.

			// Initialize catch variable
			*(void **)(regebp + (pcb->bpoffset)) = h;

			// Jump to catch block. Does not return.
			{
			    unsigned catch_esp;
			    fp_t catch_addr;

			    catch_addr = (fp_t)(pcb->code);
			    catch_esp = regebp - handler_table->espoffset - sizeof(fp_t);
			    _asm
			    {
				mov	EAX,catch_esp
				mov	ECX,catch_addr
				mov	[EAX],ECX
				mov	EBP,regebp
				mov	ESP,EAX		// reset stack
				ret			// jump to catch block
			    }
			}
		    }
		}
	    }
	    else if (phi->finally_code)
	    {	// Call finally block
		// Note that it is unnecessary to adjust the ESP, as the finally block
		// accesses all items on the stack as relative to EBP.

		void *blockaddr = phi->finally_code;

		_asm
		{
		    push	EBX
		    mov		EBX,blockaddr
		    push	EBP
		    mov		EBP,regebp
		    call	EBX
		    pop		EBP
		    pop		EBX
		}
	    }
	}
    }
}


#endif
