/*
 *  Copyright (C) 1999-2005 by Digital Mars, www.digitalmars.com
 *  Written by Walter Bright
 *
 *  This software is provided 'as-is', without any express or implied
 *  warranty. In no event will the authors be held liable for any damages
 *  arising from the use of this software.
 *
 *  Permission is granted to anyone to use this software for any purpose,
 *  including commercial applications, and to alter it and redistribute it
 *  freely, in both source and binary form, subject to the following
 *  restrictions:
 *
 *  o  The origin of this software must not be misrepresented; you must not
 *     claim that you wrote the original software. If you use this software
 *     in a product, an acknowledgment in the product documentation would be
 *     appreciated but is not required.
 *  o  Altered source versions must be plainly marked as such, and must not
 *     be misrepresented as being the original software.
 *  o  This notice may not be removed or altered from any source
 *     distribution.
 */

module rt.deh2;

// Exception handling support for linux

//debug=1;

extern (C)
{
    extern void* _deh_beg;
    extern void* _deh_end;

    int _d_isbaseof(ClassInfo oc, ClassInfo c);
}

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

struct DHandlerInfo
{
    uint offset;                // offset from function address to start of guarded section
    uint endoffset;             // offset of end of guarded section
    int prev_index;             // previous table index
    uint 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
    uint espoffset;             // offset of ESP from EBP
    uint retoffset;             // offset from start of function to return code
    uint nhandlers;             // dimension of handler_info[]
    DHandlerInfo handler_info[1];
}

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

// Create one of these for each try-catch
struct DCatchInfo
{
    uint ncatches;                      // number of catch blocks
    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
    DHandlerTable *handlertable; // eh data for this function
    uint fsize;         // size of function in bytes
}

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

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

//    debug printf("__eh_finddata(address = x%x)\n", address);
//    debug printf("_deh_beg = x%x, _deh_end = x%x\n", &_deh_beg, &_deh_end);
    for (ft = cast(FuncTable *)&_deh_beg;
         ft < cast(FuncTable *)&_deh_end;
         ft++)
    {
//      debug printf("\tfptr = x%x, fsize = x%03x, handlertable = x%x\n",
//              ft.fptr, ft.fsize, ft.handlertable);

        if (ft.fptr <= address &&
            address < cast(void *)(cast(char *)ft.fptr + ft.fsize))
        {
//          debug printf("\tfound handler table\n");
            return ft.handlertable;
        }
    }
//    debug printf("\tnot found\n");
    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
 */

uint __eh_find_caller(uint regbp, uint *pretaddr)
{
    uint bp = *cast(uint *)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 = *cast(uint *)(regbp + int.sizeof);
    }
    return bp;
}

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

extern (Windows) void _d_throw(Object *h)
{
    uint regebp;

    debug
    {
        printf("_d_throw(h = %p, &h = %p)\n", h, &h);
        printf("\tvptr = %p\n", *cast(void **)h);
    }

    asm
    {
        mov regebp,EBP  ;
    }

//static uint abc;
//if (++abc == 2) *(char *)0=0;

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

        regebp = __eh_find_caller(regebp,&retaddr);
        if (!regebp)
        {   // if end of call chain
            debug printf("end of call chain\n");
            break;
        }

        debug printf("found caller, EBP = x%x, retaddr = x%x\n", regebp, retaddr);
//if (++count == 12) *(char*)0=0;
        handler_table = __eh_finddata(cast(void *)retaddr);   // find static data associated with function
        if (!handler_table)         // if no static data
        {
            debug printf("no handler table\n");
            continue;
        }
        funcoffset = cast(uint)handler_table.fptr;
        spoff = handler_table.espoffset;
        retoffset = handler_table.retoffset;

        debug
        {
            printf("retaddr = x%x\n",cast(uint)retaddr);
            printf("regebp=x%04x, funcoffset=x%04x, spoff=x%x, retoffset=x%x\n",
            regebp,funcoffset,spoff,retoffset);
        }

        // Find start index for retaddr in static data
        dim = handler_table.nhandlers;

        debug
        {
            printf("handler_info[]:\n");
            for (int i = 0; i < dim; i++)
            {
                phi = &handler_table.handler_info[i];
                printf("\t[%d]: offset = x%04x, endoffset = x%04x, prev_index = %d, cioffset = x%04x, finally_code = %x\n",
                        i, phi.offset, phi.endoffset, phi.prev_index, phi.cioffset, phi.finally_code);
            }
        }

        index = -1;
        for (int i = 0; i < dim; i++)
        {
            phi = &handler_table.handler_info[i];

            debug printf("i = %d, phi.offset = %04x\n", i, funcoffset + phi.offset);
            if (cast(uint)retaddr > funcoffset + phi.offset &&
                cast(uint)retaddr <= funcoffset + phi.endoffset)
                index = i;
        }
        debug printf("index = %d\n", index);

        // 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)
                DCatchInfo *pci;
                int ncatches;
                int i;

                pci = cast(DCatchInfo *)(cast(char *)handler_table + phi.cioffset);
                ncatches = pci.ncatches;
                for (i = 0; i < ncatches; i++)
                {
                    DCatchBlock *pcb;
                    ClassInfo ci = **cast(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
                        *cast(void **)(regebp + (pcb.bpoffset)) = h;

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

                            catch_addr = cast(fp_t)(pcb.code);
                            catch_esp = regebp - handler_table.espoffset - fp_t.sizeof;
                            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             ;
                }
            }
        }
    }
}
