//
// Copyright (c) Microsoft Corporation.   All rights reserved.
//

// * searching exception table
// * make machine faults raise CIL exceptions
//
// C code should always invoke the filter BartokMachineFaultFilter
// before it attempts to handle any of the machine faults handled
// below.   BartokMachineFaultFilter causes an appropriate exception
// to be thrown if a machine fault occurred in CIL code.

# include "hal.h"

extern BOOL KdDebuggerNotPresent;
#ifndef NOEVENTING
#include "eventing.h"
#endif

// See bartok\tables\ExceptionTable.cs for a description of ExceptionTableEntry

//////////////////////////////////////////////////////////////////////////////
//
struct ExceptionTableEntry {
    uintptr scopeBaseAddr;
    union {
        struct {
            Class_System_Type *exceptionClass; // low bit is zero
            uintptr handlerAddr;
        };
        struct {
            uintptr frameSetupInfo; // low bit is one
            uintptr spillSize;
        };
    };
};

struct TableEntry {
    ExceptionTableEntry * tableBaseAddr;
    ExceptionTableEntry * tableEndAddr;
};

extern TableEntry TableBase[1];
extern TableEntry TableBound[1];

// TODO: BUGBUG_X64:
//   ExceptionTableLookupReturn breaks down with 64-bit pointers.
//   We will need to revisit the code
//   that uses the ExceptionTableLookupReturn result (__throwDispatcher*).

#ifdef ISA_IX64
struct PtrPair {
    uintptr a;
    uintptr b;
};
typedef PtrPair lookupReturnType;
#else
typedef uint64 lookupReturnType;
#endif
union ExceptionTableLookupReturn {
    lookupReturnType etlReturn;
    struct {
        Class_System_Type *exceptionClass;
        uintptr handlerAddr;
    };
    struct {
        uintptr frameSetupInfo;
        uintptr spillSize;
    };
};

static uintptr LookupTable(uintptr throwAddr,
                           ExceptionTableEntry **tableBaseEntry,
                           ExceptionTableEntry **tableEndEntry) {
#if 0
    printf("LookupTable(throwAddr=%p, tableBase=%p, tableEnd=%p)\n",
           throwAddr, tableBaseEntry, tableEndEntry);
    printf("  TableBase=%p, TableBound=%p, maxIndex = %d\n",
           TableBase, TableBound, TableBound - TableBase);
    printf("  callSiteTableCount=%d\n",
           Class_System_GCs_CallStack::c_callSiteTableCount);
    printf("  codeBaseStartTable=%p\n",
           Class_System_GCs_CallStack::c_codeBaseStartTable);
    printf("  returnAddressToCallSiteSetNumbers=%p\n",
           Class_System_GCs_CallStack::c_returnAddressToCallSiteSetNumbers);
    printf("  callSiteSetCount=%p\n",
           Class_System_GCs_CallStack::c_callSiteSetCount);
#endif

    //  search to find which table to use
    int maxIndex = (int) (TableBound - TableBase);
    uintptr codeBase = (uintptr) -1;
    uintptr relCodeAddr = 0;
    for (int i = 0; i < maxIndex; i++) {
        TableEntry *entry = &TableBase[i];
#if 0
        printf("   TableBase[%d]  base=%p end=%p  codeBaseStartTable[]=%p\n",
               i, entry->tableBaseAddr, entry->tableEndAddr,
               Class_System_GCs_CallStack::c_codeBaseStartTable[i]);
#endif
        codeBase =
            ((uintptr*)Class_System_GCs_CallStack::c_codeBaseStartTable)[i];

        if (throwAddr < codeBase) {
            continue;
        }
        relCodeAddr = throwAddr - codeBase;
        *tableBaseEntry = entry->tableBaseAddr;
        *tableEndEntry = entry->tableEndAddr;

#if 0
        printf("   relCodeAddr = %p\n", relCodeAddr);
        printf("    tableBase scopeBaseAddr=%p class=%p handler=%p\n",
               (*tableBaseEntry)->scopeBaseAddr,
               (*tableBaseEntry)->exceptionClass,
               (*tableBaseEntry)->handlerAddr);
        printf("    tableEnd  scopeBaseAddr=%p class=%p handler=%p\n",
               (*tableEndEntry)->scopeBaseAddr,
               (*tableEndEntry)->exceptionClass,
               (*tableEndEntry)->handlerAddr);
#endif

        if ((relCodeAddr >= (*tableBaseEntry)->scopeBaseAddr)
            && (relCodeAddr <= (*tableEndEntry)->scopeBaseAddr)) {
            return codeBase;
        }
    }

    return (uintptr) -1;
    //exit(-2);
    //__asm int 3;
}

Class_System_VTable * getRealVTable(Class_System_VTable * vt)
{
    return (Class_System_VTable *)((uintptr)vt & (~((uintptr)3)));
}

//////////////////////////////////////////////////////////////////////////////

// ExceptionAssert is called when an unexpected condition happens during
// exception processing

void ExceptionAssert()
{
#if SPIN
    // Useful for native cases when no debugger is avaiable
    while (1)
        ;
#endif

    __debugbreak();
}

// search an exception table

//////////////////////////////////////////////////////////////////////////////

// search an exception table
// - Returns the exception in eax.
// - Returns the handler address in edx.
// OR if the shared unwind handler should be used
// - Returns the frameSetupInfo in eax.
// - Returns the spill area size in edx.

lookupReturnType __fastcall ExceptionTableLookup(Class_System_Exception *exception,
                                                 uintptr throwAddr) {

#if 0
    printf("\n");
    printf("ExceptionTableLookup(exception=%p, vtable=%p, throwAddr=%p)\n",
           exception, ((uintptr *)exception)[0], throwAddr);
#endif

    if (exception->_throwAddress == NULL) {
        exception->_throwAddress = throwAddr;
    }

    //  search for table using throwAddr
    ExceptionTableEntry *baseEntry = NULL;
    ExceptionTableEntry *endEntry = NULL;
    uintptr codeBase = LookupTable(throwAddr, &baseEntry, &endEntry);

#if SINGULARITY_KERNEL
#if 0
    printf("  codeBase=%p baseEntry=%p endEntry=%p\n",
           codeBase, baseEntry, endEntry);
#endif
#endif

    if (codeBase == (uintptr) -1) {
#if SINGULARITY_KERNEL
#if 0
        printf("Exception outside of any known code regions!\n");
#endif
        if (!KdDebuggerNotPresent) {
            ExceptionAssert();
        }
#if 0
        printf("Terminating by exception!\n");
#endif
        Class_System_VTable::g_TerminateByException(exception);
        ExceptionAssert();
#elif SINGULARITY_PROCESS
        Assert("Exception outside of any known code regions!\n");
        Class_System_VTable::g_TerminateByException(exception);
#endif
    }

    // bsearch for throwAddr
    int minIndex = 0;
    int maxIndex = (int) (endEntry-baseEntry);
    throwAddr -= codeBase;

    if (throwAddr < baseEntry[minIndex].scopeBaseAddr ||
        throwAddr > baseEntry[maxIndex].scopeBaseAddr) {
        // BUGBUG: callback to C# code that may trigger GC
#if SINGULARITY_KERNEL
#if 0
        printf("Exception outside of known code region for %p\n", codeBase);
#endif
        if (!KdDebuggerNotPresent) {
            __debugbreak();
        }
        Class_System_VTable::g_TerminateByException(exception);
#if 0
        printf("top-level exception handling code failed\n");
#endif
        __debugbreak();
#elif SINGULARITY_PROCESS
        Assert("Exception outside of known code region for.\n");
        Class_System_VTable::g_TerminateByException(exception);
        Assert("top-level exception handling code failed\n");
#endif
    }
    while (minIndex+1 < maxIndex) {
        int midIndex = (minIndex+maxIndex)/2;
        uintptr midAddr = baseEntry[midIndex].scopeBaseAddr;
        if (throwAddr < midAddr) {
            maxIndex = midIndex;
        } else {
            minIndex = midIndex;
        }
    }
    ExceptionTableEntry *entry = &baseEntry[minIndex];

    //  back up to first entry containing throwAddr (there may be several)

    uintptr baseAddr;
    for (baseAddr = entry->scopeBaseAddr;
         entry->scopeBaseAddr == baseAddr && entry >= baseEntry;
         entry--) {
        continue;
    }

    //  check each of the handlers in turn

    for (entry++; entry->scopeBaseAddr <= throwAddr; entry++) {
#if 0
        printf("    entry=%p[%d]  "
               "scopeBaseAddr=%p exceptionClass=%p handler=%p\n",
               entry, entry - baseEntry,
               entry->scopeBaseAddr, entry->exceptionClass, entry->handlerAddr);
#endif

        // 0 now means "no frame pointer omission and no callee save registers
        // have been saved to the stack":
        // Assert(entry->exceptionClass);

        Assert(((entry->frameSetupInfo & 0x1) != 0)
               || (entry->handlerAddr != NULL));
        if (((entry->frameSetupInfo & 0x1) != 0)
            || Class_System_VTable::g_IsExceptionHandler(entry->exceptionClass,
                                                         exception)) {
#if 0
            printf("Found matching exception entry: %p\n", entry);
#endif
            break;
        }
    }
    Assert(entry->scopeBaseAddr == baseAddr);

    ExceptionTableLookupReturn retval;

    if((entry->frameSetupInfo & 0x1) != 0) {
        retval.frameSetupInfo = entry->frameSetupInfo;
        retval.spillSize = entry->spillSize;
#ifndef NOEVENTING
        LogExceptionInfo((UIntPtr)(codeBase + throwAddr),
                         0,
                         getRealVTable(exception->postHeader.vtableObject)->vtableType->name);
#endif
    } else {
        retval.exceptionClass = entry->exceptionClass;
        retval.handlerAddr = entry->handlerAddr + codeBase;
#ifndef NOEVENTING
        LogExceptionInfo((UIntPtr)(codeBase + throwAddr),
                         (UIntPtr)(retval.handlerAddr),
                         getRealVTable(exception->postHeader.vtableObject)->vtableType->name);
#endif
    }

#if SINGULARITY_KERNEL
    if (!exception->_notifiedDebugger && !KdDebuggerNotPresent) {
        exception->_notifiedDebugger = true;

        bool iflag =
            Class_Microsoft_Singularity_Processor::g_DisableInterrupts();
        Class_System_VTable * vtable = getRealVTable(exception->postHeader.vtableObject);
#if 0
        if ((entry->frameSetupInfo & 0x1) != 0) {
            printf("\n---- First chance %ls at %p.  Handler is shared ----\n",
                   &(vtable->vtableType->name->m_firstChar),
                   codeBase + throwAddr);
        }
        else {
            printf("\n---- First chance %ls at %p.  Handler is %p ----\n",
                   &(vtable->vtableType->name->m_firstChar),
                   codeBase + throwAddr,
                   retval.handlerAddr);
        }
        if (exception->_message != NULL) {
            printf("------ Message: %ls\n",
                   &(exception->_message->m_firstChar));
        }
#endif
        KdNotifyException(exception, (uint32) throwAddr);
        Class_Microsoft_Singularity_Processor::g_RestoreInterrupts(iflag);
    }
#endif

    return(retval.etlReturn);
}

