// GNU D Compiler exception personality routines.
// Copyright (C) 2011-2024 Free Software Foundation, Inc.

// GCC 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 3, or (at your option) any later
// version.

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

// Under Section 7 of GPL version 3, you are granted additional
// permissions described in the GCC Runtime Library Exception, version
// 3.1, as published by the Free Software Foundation.

// You should have received a copy of the GNU General Public License and
// a copy of the GCC Runtime Library Exception along with this program;
// see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
// <http://www.gnu.org/licenses/>.

// This code is based on the libstdc++ exception handling routines.

module gcc.deh;

import gcc.unwind;
import gcc.unwind.pe;
import gcc.builtins;
import gcc.config;
import gcc.attributes;

extern(C)
{
    int _d_isbaseof(ClassInfo, ClassInfo) @nogc nothrow pure @safe;
    void _d_createTrace(Throwable, void*);
    void _d_print_throwable(Throwable t);
}

/**
 * Declare all known and handled exception classes.
 * D exceptions -- "GNUCD\0\0\0".
 * C++ exceptions -- "GNUCC++\0"
 * C++ dependent exceptions -- "GNUCC++\x01"
 */
static if (GNU_ARM_EABI_Unwinder)
{
    enum _Unwind_Exception_Class gdcExceptionClass = "GNUCD\0\0\0";
    enum _Unwind_Exception_Class gxxExceptionClass = "GNUCC++\0";
    enum _Unwind_Exception_Class gxxDependentExceptionClass = "GNUCC++\x01";
}
else
{
    enum _Unwind_Exception_Class gdcExceptionClass =
        (cast(_Unwind_Exception_Class)'G' << 56) |
        (cast(_Unwind_Exception_Class)'N' << 48) |
        (cast(_Unwind_Exception_Class)'U' << 40) |
        (cast(_Unwind_Exception_Class)'C' << 32) |
        (cast(_Unwind_Exception_Class)'D' << 24);

    enum _Unwind_Exception_Class gxxExceptionClass =
        (cast(_Unwind_Exception_Class)'G' << 56) |
        (cast(_Unwind_Exception_Class)'N' << 48) |
        (cast(_Unwind_Exception_Class)'U' << 40) |
        (cast(_Unwind_Exception_Class)'C' << 32) |
        (cast(_Unwind_Exception_Class)'C' << 24) |
        (cast(_Unwind_Exception_Class)'+' << 16) |
        (cast(_Unwind_Exception_Class)'+' <<  8) |
        (cast(_Unwind_Exception_Class)0 <<  0);

    enum _Unwind_Exception_Class gxxDependentExceptionClass =
        gxxExceptionClass + 1;
}

/**
 * Checks for GDC exception class.
 */
bool isGdcExceptionClass(_Unwind_Exception_Class c) @nogc
{
    static if (GNU_ARM_EABI_Unwinder)
    {
        return c[0] == gdcExceptionClass[0]
            && c[1] == gdcExceptionClass[1]
            && c[2] == gdcExceptionClass[2]
            && c[3] == gdcExceptionClass[3]
            && c[4] == gdcExceptionClass[4]
            && c[5] == gdcExceptionClass[5]
            && c[6] == gdcExceptionClass[6]
            && c[7] == gdcExceptionClass[7];
    }
    else
    {
        return c == gdcExceptionClass;
    }
}

/**
 * Checks for any C++ exception class.
 */
bool isGxxExceptionClass(_Unwind_Exception_Class c) @nogc
{
    static if (GNU_ARM_EABI_Unwinder)
    {
        return c[0] == gxxExceptionClass[0]
            && c[1] == gxxExceptionClass[1]
            && c[2] == gxxExceptionClass[2]
            && c[3] == gxxExceptionClass[3]
            && c[4] == gxxExceptionClass[4]
            && c[5] == gxxExceptionClass[5]
            && c[6] == gxxExceptionClass[6]
            && (c[7] == gxxExceptionClass[7]
                || c[7] == gxxDependentExceptionClass[7]);
    }
    else
    {
        return c == gxxExceptionClass
            || c == gxxDependentExceptionClass;
    }
}

/**
 * Checks for primary or dependent, but not that it is a C++ exception.
 */
bool isDependentException(_Unwind_Exception_Class c) @nogc
{
    static if (GNU_ARM_EABI_Unwinder)
        return (c[7] == '\x01');
    else
        return (c & 1);
}

/**
 * A D exception object consists of a header, which is a wrapper
 * around an unwind object header with additional D specific
 * information, prefixed by the exception object itself.
 */
struct ExceptionHeader
{
    // Because of a lack of __aligned__ style attribute, our object
    // and the unwind object are the first two fields.
    static if (Throwable.alignof < _Unwind_Exception.alignof)
        ubyte[_Unwind_Exception.alignof - Throwable.alignof] pad;

    // The object being thrown.  The compiled code expects this to
    // be immediately before the generic exception header.
    Throwable object;

    // The generic exception header.
    _Unwind_Exception unwindHeader;

    static assert(unwindHeader.offsetof - object.offsetof == object.sizeof);

    // Cache handler details between Phase 1 and Phase 2.
    static if (GNU_ARM_EABI_Unwinder)
    {
        // Nothing here yet.
    }
    else
    {
        // Which catch was found.
        int handler;

        // Language Specific Data Area for function enclosing the handler.
        const(ubyte)* languageSpecificData;

        // Pointer to catch code.
        _Unwind_Ptr landingPad;

        // Canonical Frame Address (CFA) for the enclosing handler.
        _Unwind_Word canonicalFrameAddress;
    }

    // Stack other thrown exceptions in current thread through here.
    ExceptionHeader* next;

    // Thread local stack of chained exceptions.
    static ExceptionHeader* stack;

    // Pre-allocate storage for 1 instance per thread.
    // Use calloc/free for multiple exceptions in flight.
    static ExceptionHeader ehstorage;

    /**
     * Allocate and initialize an ExceptionHeader.
     */
    static ExceptionHeader* create(Throwable o) @nogc
    {
        auto eh = &ehstorage;

        // Check exception object in use.
        if (eh.object)
        {
            eh = cast(ExceptionHeader*) __builtin_calloc(ExceptionHeader.sizeof, 1);
            // Out of memory while throwing - not much else can be done.
            if (!eh)
                terminate("out of memory", __LINE__);
        }
        eh.object = o;

        eh.unwindHeader.exception_class = gdcExceptionClass;

        return eh;
    }

    /**
     * Free ExceptionHeader that was created by create().
     */
    static void free(ExceptionHeader* eh) @nogc
    {
        __builtin_memset(eh, 0, ExceptionHeader.sizeof);
        if (eh != &ehstorage)
            __builtin_free(eh);
    }

    /**
     * Push this onto stack of chained exceptions.
     */
    void push() @nogc
    {
        next = stack;
        stack = &this;
    }

    /**
     * Pop and return top of chained exception stack.
     */
    static ExceptionHeader* pop() @nogc
    {
        auto eh = stack;
        stack = eh.next;
        return eh;
    }

    /**
     * Save stage1 handler information in the exception object.
     */
    static void save(_Unwind_Exception* unwindHeader,
                     _Unwind_Word cfa, int handler,
                     const(ubyte)* lsda, _Unwind_Ptr landingPad) @nogc
    {
        static if (GNU_ARM_EABI_Unwinder)
        {
            unwindHeader.barrier_cache.sp = cfa;
            unwindHeader.barrier_cache.bitpattern[1] = cast(_uw)handler;
            unwindHeader.barrier_cache.bitpattern[2] = cast(_uw)lsda;
            unwindHeader.barrier_cache.bitpattern[3] = cast(_uw)landingPad;
        }
        else
        {
            ExceptionHeader* eh = toExceptionHeader(unwindHeader);
            eh.canonicalFrameAddress = cfa;
            eh.handler = handler;
            eh.languageSpecificData = lsda;
            eh.landingPad = landingPad;
        }
    }

    /**
     * Restore the catch handler data saved during phase1.
     */
    static void restore(_Unwind_Exception* unwindHeader, out int handler,
                        out const(ubyte)* lsda, out _Unwind_Ptr landingPad,
                        out _Unwind_Word cfa) @nogc
    {
        static if (GNU_ARM_EABI_Unwinder)
        {
            cfa = unwindHeader.barrier_cache.sp;
            handler = cast(int)unwindHeader.barrier_cache.bitpattern[1];
            lsda = cast(ubyte*)unwindHeader.barrier_cache.bitpattern[2];
            landingPad = cast(_Unwind_Ptr)unwindHeader.barrier_cache.bitpattern[3];
        }
        else
        {
            ExceptionHeader* eh = toExceptionHeader(unwindHeader);
            cfa = eh.canonicalFrameAddress;
            handler = eh.handler;
            lsda = eh.languageSpecificData;
            landingPad = cast(_Unwind_Ptr)eh.landingPad;
        }
    }

    /**
     * Convert from pointer to unwindHeader to pointer to ExceptionHeader
     * that it is embedded inside of.
     */
    static ExceptionHeader* toExceptionHeader(_Unwind_Exception* exc) @nogc
    {
        return cast(ExceptionHeader*)(cast(void*)exc - ExceptionHeader.unwindHeader.offsetof);
    }
}

/**
 * Map to C++ std::type_info's virtual functions from D,
 * being careful to not require linking with libstdc++.
 * So it is given a different name.
 */
extern(C++) interface CxxTypeInfo
{
    void dtor1();
    void dtor2();
    bool __is_pointer_p() const;
    bool __is_function_p() const;
    bool __do_catch(const CxxTypeInfo, void**, uint) const;
    bool __do_upcast(const void*, void**) const;
}

/**
 * Structure of a C++ exception, represented as a C structure.
 * See unwind-cxx.h for the full definition.
 */
struct CxaExceptionHeader
{
    union
    {
        CxxTypeInfo exceptionType;
        void* primaryException;
    }
    void function(void*) exceptionDestructor;
    void function() unexpectedHandler;
    void function() terminateHandler;
    CxaExceptionHeader* nextException;
    int handlerCount;

    static if (GNU_ARM_EABI_Unwinder)
    {
        CxaExceptionHeader* nextPropagatingException;
        int propagationCount;
    }
    else
    {
        int handlerSwitchValue;
        const(ubyte)* actionRecord;
        const(ubyte)* languageSpecificData;
        _Unwind_Ptr catchTemp;
        void* adjustedPtr;
    }

    _Unwind_Exception unwindHeader;

    /**
     * There's no saving between phases, so only cache pointer.
     * __cxa_begin_catch expects this to be set.
     */
    static void save(_Unwind_Exception* unwindHeader, void* thrownPtr) @nogc
    {
        static if (GNU_ARM_EABI_Unwinder)
            unwindHeader.barrier_cache.bitpattern[0] = cast(_uw) thrownPtr;
        else
        {
            auto eh = toExceptionHeader(unwindHeader);
            eh.adjustedPtr = thrownPtr;
        }
    }

    /**
     * Get pointer to the thrown object if the thrown object type behind the
     * exception is implicitly convertible to the catch type.
     */
    static void* getAdjustedPtr(_Unwind_Exception* exc, CxxTypeInfo catchType)
    {
        void* thrownPtr;

        // A dependent C++ exceptions is just a wrapper around the unwind header.
        // A primary C++ exception has the thrown object located immediately after it.
        if (isDependentException(exc.exception_class))
            thrownPtr = toExceptionHeader(exc).primaryException;
        else
            thrownPtr = cast(void*)(exc + 1);

        // Pointer types need to adjust the actual pointer, not the pointer that is
        // the exception object.  This also has the effect of passing pointer types
        // "by value" through the __cxa_begin_catch return value.
        const throw_type = (cast(CxaExceptionHeader*)thrownPtr - 1).exceptionType;

        if (throw_type.__is_pointer_p())
            thrownPtr = *cast(void**)thrownPtr;

        // Pointer adjustment may be necessary due to multiple inheritance
        if (catchType is throw_type
            || catchType.__do_catch(throw_type, &thrownPtr, 1))
            return thrownPtr;

        return null;
    }

    /**
     * Convert from pointer to unwindHeader to pointer to CxaExceptionHeader
     * that it is embedded inside of.
     */
    static CxaExceptionHeader* toExceptionHeader(_Unwind_Exception* exc) @nogc
    {
        return cast(CxaExceptionHeader*)(exc + 1) - 1;
    }
}

/**
 * Called if exception handling must be abandoned for any reason.
 */
private void terminate(string msg, uint line) @nogc
{
    import core.stdc.stdio;
    import core.stdc.stdlib;

    static bool terminating;
    if (terminating)
    {
        fputs("terminate called recursively\n", stderr);
        abort();
    }
    terminating = true;

    fprintf(stderr, "gcc.deh(%u): %.*s\n", line, cast(int)msg.length, msg.ptr);

    abort();
}

/**
 * Called when fibers switch contexts.
 */
extern(C) void* _d_eh_swapContext(void* newContext) nothrow @nogc
{
    auto old = ExceptionHeader.stack;
    ExceptionHeader.stack = cast(ExceptionHeader*)newContext;
    return old;
}

/**
 * Called before starting a catch.  Returns the exception object.
 */
extern(C) void* __gdc_begin_catch(_Unwind_Exception* unwindHeader)
{
    ExceptionHeader* header = ExceptionHeader.toExceptionHeader(unwindHeader);

    void* objectp = cast(void*)header.object;
    // Remove our reference to the exception. We should not decrease its refcount,
    // because we pass the object on to the caller.
    header.object = null;

    // Something went wrong when stacking up chained headers...
    if (header != ExceptionHeader.pop())
        terminate("catch error", __LINE__);

    // Handling for this exception is complete.
    _Unwind_DeleteException(&header.unwindHeader);

    return objectp;
}

/**
 * Perform a throw, D style. Throw will unwind through this call,
 * so there better not be any handlers or exception thrown here.
 */
extern(C) void _d_throw(Throwable object)
{
    // If possible, avoid always allocating new memory for exception headers.
    ExceptionHeader *eh = ExceptionHeader.create(object);

    // Add to thrown exception stack.
    eh.push();

    // Increment reference count if object is a refcounted Throwable.
    auto refcount = object.refcount();
    if (refcount)
        object.refcount() = refcount + 1;

    // Called by unwinder when exception object needs destruction by other than our code.
    extern(C) void exception_cleanup(_Unwind_Reason_Code code, _Unwind_Exception* exc)
    {
        // If we haven't been caught by a foreign handler, then this is
        // some sort of unwind error.  In that case just die immediately.
        // _Unwind_DeleteException in the HP-UX IA64 libunwind library
        //  returns _URC_NO_REASON and not _URC_FOREIGN_EXCEPTION_CAUGHT
        // like the GCC _Unwind_DeleteException function does.
        if (code != _URC_FOREIGN_EXCEPTION_CAUGHT && code != _URC_NO_REASON)
            terminate("uncaught exception", __LINE__);

        auto eh = ExceptionHeader.toExceptionHeader(exc);
        ExceptionHeader.free(eh);
    }

    eh.unwindHeader.exception_cleanup = &exception_cleanup;

    // Runtime now expects us to do this first before unwinding.
    _d_createTrace(eh.object, null);

    // We're happy with setjmp/longjmp exceptions or region-based
    // exception handlers: entry points are provided here for both.
    _Unwind_Reason_Code r = void;

    version (GNU_SjLj_Exceptions)
        r = _Unwind_SjLj_RaiseException(&eh.unwindHeader);
    else
        r = _Unwind_RaiseException(&eh.unwindHeader);

    // If code == _URC_END_OF_STACK, then we reached top of stack without finding
    // a handler for the exception.  Since each thread is run in a try/catch,
    // this oughtn't happen.  If code is something else, we encountered some sort
    // of heinous lossage from which we could not recover.  As is the way of such
    // things, almost certainly we will have crashed before now, rather than
    // actually being able to diagnose the problem.
    if (r == _URC_END_OF_STACK)
    {
        __gdc_begin_catch(&eh.unwindHeader);
        _d_print_throwable(object);
        terminate("uncaught exception", __LINE__);
    }

    terminate("unwind error", __LINE__);
}

static if (GNU_ARM_EABI_Unwinder)
{
    enum personality_fn_attributes = attribute("target", ("general-regs-only"));
}
else
{
    enum personality_fn_attributes = "";
}

/**
 * Read and extract information from the LSDA (.gcc_except_table section).
 */
@personality_fn_attributes
_Unwind_Reason_Code scanLSDA(const(ubyte)* lsda, _Unwind_Exception_Class exceptionClass,
                             _Unwind_Action actions, _Unwind_Exception* unwindHeader,
                             _Unwind_Context* context, _Unwind_Word cfa,
                             out _Unwind_Ptr landingPad, out int handler)
{
    // If no LSDA, then there are no handlers or cleanups.
    if (lsda is null)
        return CONTINUE_UNWINDING(unwindHeader, context);

    // Parse the LSDA header
    auto p = lsda;

    auto Start = (context ? _Unwind_GetRegionStart(context) : 0);

    // Find @LPStart, the base to which landing pad offsets are relative.
    ubyte LPStartEncoding = *p++;
    _Unwind_Ptr LPStart = 0;

    if (LPStartEncoding != DW_EH_PE_omit)
        LPStart = read_encoded_value(context, LPStartEncoding, p);
    else
        LPStart = Start;

    // Find @TType, the base of the handler and exception spec type data.
    ubyte TTypeEncoding = *p++;
    const(ubyte)* TType = null;

    if (TTypeEncoding != DW_EH_PE_omit)
    {
        static if (__traits(compiles, _TTYPE_ENCODING))
        {
            // Older ARM EABI toolchains set this value incorrectly, so use a
            // hardcoded OS-specific format.
            TTypeEncoding = _TTYPE_ENCODING;
        }
        auto TTbase = read_uleb128(p);
        TType = p + TTbase;
    }

    // The encoding and length of the call-site table; the action table
    // immediately follows.
    ubyte CSEncoding = *p++;
    auto CSTableSize = read_uleb128(p);
    const(ubyte)* actionTable = p + CSTableSize;

    auto TTypeBase = base_of_encoded_value(TTypeEncoding, context);

    // Get instruction pointer (ip) at start of instruction that threw.
    version (CRuntime_Glibc)
    {
        int ip_before_insn;
        auto ip = _Unwind_GetIPInfo(context, &ip_before_insn);
        if (!ip_before_insn)
            --ip;
    }
    else
    {
        auto ip = _Unwind_GetIP(context);
        --ip;
    }

    bool saw_cleanup = false;
    bool saw_handler = false;
    const(ubyte)* actionRecord = null;

    version (GNU_SjLj_Exceptions)
    {
        // The given "IP" is an index into the call-site table, with two
        // exceptions -- -1 means no-action, and 0 means terminate.
        // But since we're using uleb128 values, we've not got random
        // access to the array.
        if (cast(int) ip <= 0)
        {
            return _URC_CONTINUE_UNWIND;
        }
        else
        {
            _uleb128_t CSLandingPad, CSAction;
            do
            {
                CSLandingPad = read_uleb128(p);
                CSAction = read_uleb128(p);
            }
            while (--ip);

            // Can never have null landing pad for sjlj -- that would have
            // been indicated by a -1 call site index.
            landingPad = CSLandingPad + 1;
            if (CSAction)
                actionRecord = actionTable + CSAction - 1;
        }
    }
    else
    {
        // Search the call-site table for the action associated with this IP.
        while (p < actionTable)
        {
            // Note that all call-site encodings are "absolute" displacements.
            auto CSStart = read_encoded_value(null, CSEncoding, p);
            auto CSLen = read_encoded_value(null, CSEncoding, p);
            auto CSLandingPad = read_encoded_value(null, CSEncoding, p);
            auto CSAction = read_uleb128(p);

            // The table is sorted, so if we've passed the ip, stop.
            if (ip < Start + CSStart)
                p = actionTable;
            else if (ip < Start + CSStart + CSLen)
            {
                if (CSLandingPad)
                    landingPad = LPStart + CSLandingPad;
                if (CSAction)
                    actionRecord = actionTable + CSAction - 1;
                break;
            }
        }
    }

    if (landingPad == 0)
    {
        // IP is present, but has a null landing pad.
        // No cleanups or handlers to be run.
    }
    else if (actionRecord is null)
    {
        // If ip is present, has a non-null landing pad, and a null
        // action table offset, then there are only cleanups present.
        // Cleanups use a zero switch value, as set above.
        saw_cleanup = true;
    }
    else
    {
        // Otherwise we have a catch handler or exception specification.
        handler = actionTableLookup(actions, unwindHeader, actionRecord,
                                    lsda, exceptionClass, TTypeBase,
                                    TType, TTypeEncoding,
                                    saw_handler, saw_cleanup);
    }

    // IP is not in table.  No associated cleanups.
    if (!saw_handler && !saw_cleanup)
        return CONTINUE_UNWINDING(unwindHeader, context);

    if (actions & _UA_SEARCH_PHASE)
    {
        if (!saw_handler)
            return CONTINUE_UNWINDING(unwindHeader, context);

        // For domestic exceptions, we cache data from phase 1 for phase 2.
        if (isGdcExceptionClass(exceptionClass))
            ExceptionHeader.save(unwindHeader, cfa, handler, lsda, landingPad);

        return _URC_HANDLER_FOUND;
    }

    return 0;
}

/**
 * Look up and return the handler index of the classType in Action Table.
 */
int actionTableLookup(_Unwind_Action actions, _Unwind_Exception* unwindHeader,
                      const(ubyte)* actionRecord, const(ubyte)* lsda,
                      _Unwind_Exception_Class exceptionClass,
                      _Unwind_Ptr TTypeBase, const(ubyte)* TType,
                      ubyte TTypeEncoding,
                      out bool saw_handler, out bool saw_cleanup)
{
    ClassInfo thrownType;
    if (isGdcExceptionClass(exceptionClass))
    {
        thrownType = getClassInfo(unwindHeader, lsda);
    }

    while (1)
    {
        auto ap = actionRecord;
        auto ARFilter = read_sleb128(ap);
        auto apn = ap;
        auto ARDisp = read_sleb128(ap);

        if (ARFilter == 0)
        {
            // Zero filter values are cleanups.
            saw_cleanup = true;
        }
        else if (actions & _UA_FORCE_UNWIND)
        {
            // During forced unwinding, we only run cleanups.
        }
        else if (ARFilter > 0)
        {
            // Positive filter values are handlers.
            auto encodedSize = size_of_encoded_value(TTypeEncoding);

            // ARFilter is the negative index from TType, which is where
            // the ClassInfo is stored.
            const(ubyte)* tp = TType - ARFilter * encodedSize;

            auto entry = read_encoded_value_with_base(TTypeEncoding, TTypeBase, tp);
            ClassInfo ci = cast(ClassInfo)cast(void*)(entry);

            // D does not have catch-all handlers, and so the following
            // assumes that we will never handle a null value.
            assert(ci !is null);

            if (ci.classinfo is __cpp_type_info_ptr.classinfo
                && isGxxExceptionClass(exceptionClass))
            {
                // catchType is the catch clause type_info.
                auto catchType = cast(CxxTypeInfo)((cast(__cpp_type_info_ptr)cast(void*)ci).ptr);
                auto thrownPtr = CxaExceptionHeader.getAdjustedPtr(unwindHeader, catchType);

                if (thrownPtr !is null)
                {
                    if (actions & _UA_SEARCH_PHASE)
                        CxaExceptionHeader.save(unwindHeader, thrownPtr);
                    saw_handler = true;
                    return cast(int)ARFilter;
                }
            }
            else if (isGdcExceptionClass(exceptionClass)
                     && _d_isbaseof(thrownType, ci))
            {
                saw_handler = true;
                return cast(int)ARFilter;
            }
            else
            {
                // ??? What to do about other GNU language exceptions.
            }
        }
        else
        {
            // Negative filter values are exception specifications,
            // which D does not use.
            break;
        }

        if (ARDisp == 0)
            break;
        actionRecord = apn + ARDisp;
    }

    return 0;
}

/**
 * Look at the chain of inflight exceptions and pick the class type that'll
 * be looked for in catch clauses.
 */
ClassInfo getClassInfo(_Unwind_Exception* unwindHeader,
                       const(ubyte)* currentLsd) @nogc
{
    ExceptionHeader* eh = ExceptionHeader.toExceptionHeader(unwindHeader);
    // The first thrown Exception at the top of the stack takes precedence
    // over others that are inflight, unless an Error was thrown, in which
    // case, we search for error handlers instead.
    Throwable ehobject = eh.object;
    for (ExceptionHeader* ehn = eh.next; ehn; ehn = ehn.next)
    {
        const(ubyte)* nextLsd = void;
        _Unwind_Ptr nextLandingPad = void;
        _Unwind_Word nextCfa = void;
        int nextHandler = void;

        ExceptionHeader.restore(&ehn.unwindHeader, nextHandler, nextLsd, nextLandingPad, nextCfa);

        // Don't combine when the exceptions are from different functions.
        if (currentLsd != nextLsd)
            break;

        Error e = cast(Error)ehobject;
        if (e is null || (cast(Error)ehn.object) !is null)
        {
            currentLsd = nextLsd;
            ehobject = ehn.object;
        }
    }
    return ehobject.classinfo;
}

/**
 * Called when the personality function has found neither a cleanup or handler.
 * To support ARM EABI personality routines, that must also unwind the stack.
 */
@personality_fn_attributes
_Unwind_Reason_Code CONTINUE_UNWINDING(_Unwind_Exception* unwindHeader, _Unwind_Context* context)
{
    static if (GNU_ARM_EABI_Unwinder)
    {
        if (__gnu_unwind_frame(unwindHeader, context) != _URC_OK)
            return _URC_FAILURE;
    }
    return _URC_CONTINUE_UNWIND;
}

/**
 * Using a different personality function name causes link failures
 * when trying to mix code using different exception handling models.
 */
version (GNU_SEH_Exceptions)
{
    enum PERSONALITY_FUNCTION = "__gdc_personality_imp";

    extern(C) EXCEPTION_DISPOSITION __gdc_personality_seh0(void* ms_exc, void* this_frame,
                                                           void* ms_orig_context, void* ms_disp)
    {
        return _GCC_specific_handler(ms_exc, this_frame, ms_orig_context,
                                     ms_disp, &gdc_personality);
    }
}
else version (GNU_SjLj_Exceptions)
{
    enum PERSONALITY_FUNCTION = "__gdc_personality_sj0";

    private int __builtin_eh_return_data_regno(int x) { return x; }
}
else
{
    enum PERSONALITY_FUNCTION = "__gdc_personality_v0";
}

/**
 * The "personality" function, specific to each language.
 */
static if (GNU_ARM_EABI_Unwinder)
{
    pragma(mangle, PERSONALITY_FUNCTION)
    @personality_fn_attributes
    extern(C) _Unwind_Reason_Code gdc_personality(_Unwind_State state,
                                                  _Unwind_Exception* unwindHeader,
                                                  _Unwind_Context* context)
    {
        _Unwind_Action actions;

        switch (state & _US_ACTION_MASK)
        {
            case _US_VIRTUAL_UNWIND_FRAME:
                // If the unwind state pattern is (_US_VIRTUAL_UNWIND_FRAME | _US_FORCE_UNWIND)
                // then we don't need to search for any handler as it is not a real exception.
                // Just unwind the stack.
                if (state & _US_FORCE_UNWIND)
                    return CONTINUE_UNWINDING(unwindHeader, context);
                actions = _UA_SEARCH_PHASE;
                break;

            case _US_UNWIND_FRAME_STARTING:
                actions = _UA_CLEANUP_PHASE;
                if (!(state & _US_FORCE_UNWIND)
                    && unwindHeader.barrier_cache.sp == _Unwind_GetGR(context, UNWIND_STACK_REG))
                    actions |= _UA_HANDLER_FRAME;
                break;

            case _US_UNWIND_FRAME_RESUME:
                return CONTINUE_UNWINDING(unwindHeader, context);

            default:
                terminate("unwind error", __LINE__);
        }
        actions |= state & _US_FORCE_UNWIND;

        // The dwarf unwinder assumes the context structure holds things like
        // the function and LSDA pointers.  The ARM implementation caches these
        // in the exception header (UCB).  To avoid rewriting everything we make
        // the virtual IP register point at the UCB.
        _Unwind_SetGR(context, UNWIND_POINTER_REG, cast(_Unwind_Ptr)unwindHeader);

        return __gdc_personality(actions, unwindHeader.exception_class,
                                 unwindHeader, context);
    }
}
else
{
    pragma(mangle, PERSONALITY_FUNCTION)
    extern(C) _Unwind_Reason_Code gdc_personality(int iversion,
                                                  _Unwind_Action actions,
                                                  _Unwind_Exception_Class exceptionClass,
                                                  _Unwind_Exception* unwindHeader,
                                                  _Unwind_Context* context)
    {
        // Interface version check.
        if (iversion != 1)
            return _URC_FATAL_PHASE1_ERROR;

        return __gdc_personality(actions, exceptionClass, unwindHeader, context);
    }
}

@personality_fn_attributes
private _Unwind_Reason_Code __gdc_personality(_Unwind_Action actions,
                                              _Unwind_Exception_Class exceptionClass,
                                              _Unwind_Exception* unwindHeader,
                                              _Unwind_Context* context)
{
    const(ubyte)* lsda;
    _Unwind_Ptr landingPad;
    _Unwind_Word cfa;
    int handler;

    // Shortcut for phase 2 found handler for domestic exception.
    if (actions == (_UA_CLEANUP_PHASE | _UA_HANDLER_FRAME)
        && isGdcExceptionClass(exceptionClass))
    {
        ExceptionHeader.restore(unwindHeader, handler, lsda, landingPad, cfa);
        // Shouldn't have cached a null landing pad in phase 1.
        if (landingPad == 0)
            terminate("unwind error", __LINE__);
    }
    else
    {
        lsda = cast(ubyte*)_Unwind_GetLanguageSpecificData(context);

        static if (GNU_ARM_EABI_Unwinder)
            cfa = _Unwind_GetGR(context, UNWIND_STACK_REG);
        else
            cfa = _Unwind_GetCFA(context);

        auto result = scanLSDA(lsda, exceptionClass, actions, unwindHeader,
                               context, cfa, landingPad, handler);

        // Positive on handler found in phase 1, continue unwinding, or failure.
        if (result)
            return result;
    }

    // Unexpected negative handler, call terminate directly.
    if (handler < 0)
        terminate("unwind error", __LINE__);

    // We can't use any of the deh routines with foreign exceptions,
    // because they all expect unwindHeader to be an ExceptionHeader.
    if (isGdcExceptionClass(exceptionClass))
    {
        // If there are any in-flight exceptions being thrown, chain our
        // current object onto the end of the prevous object.
        ExceptionHeader* eh = ExceptionHeader.toExceptionHeader(unwindHeader);
        auto currentLsd = lsda;
        bool bypassed = false;

        while (eh.next)
        {
            ExceptionHeader* ehn = eh.next;
            const(ubyte)* nextLsd = void;
            _Unwind_Ptr nextLandingPad = void;
            _Unwind_Word nextCfa = void;
            int nextHandler = void;

            ExceptionHeader.restore(&ehn.unwindHeader, nextHandler, nextLsd, nextLandingPad, nextCfa);

            Error e = cast(Error)eh.object;
            if (e !is null && !cast(Error)ehn.object)
            {
                // We found an Error, bypass the exception chain.
                currentLsd = nextLsd;
                eh = ehn;
                bypassed = true;
                continue;
            }

            // Don't combine when the exceptions are from different functions.
            if (currentLsd != nextLsd)
                break;

            // Add our object onto the end of the existing chain and replace
            // our exception object with in-flight one.
            eh.object = Throwable.chainTogether(ehn.object, eh.object);

            if (nextHandler != handler && !bypassed)
            {
                handler = nextHandler;
                ExceptionHeader.save(unwindHeader, cfa, handler, lsda, landingPad);
            }

            // Exceptions chained, can now throw away the previous header.
            eh.next = ehn.next;
            _Unwind_DeleteException(&ehn.unwindHeader);
        }

        if (bypassed)
        {
            eh = ExceptionHeader.toExceptionHeader(unwindHeader);
            Error e = cast(Error)eh.object;
            auto ehn = eh.next;
            e.bypassedException = ehn.object;
            eh.next = ehn.next;
            _Unwind_DeleteException(&ehn.unwindHeader);
        }
    }

    // Set up registers and jump to cleanup or handler.
    // For targets with pointers smaller than the word size, we must extend the
    // pointer, and this extension is target dependent.
    _Unwind_SetGR(context, __builtin_eh_return_data_regno(0),
                  cast(_Unwind_Ptr)unwindHeader);
    _Unwind_SetGR(context, __builtin_eh_return_data_regno(1), handler);
    _Unwind_SetIP(context, landingPad);

    return _URC_INSTALL_CONTEXT;
}
