// SJLJ exception runtime for the W65816 backend.
//
// Works with the IR generated by clang's `-fsjlj-exceptions` lowering
// after our W65816SjLjFinalize pass has rewritten things.  The shape:
//
//   - clang+SjLjEHPrepare emit a per-function "function context" alloca:
//       struct FnCtx {
//           void  *prev;          // [0] linked list ptr (set by Register)
//           int    call_site;     // [1] active call_site index (set by clang)
//           int    data[4];       // [2] [exn_obj, selector, ...] — set by us
//           void  *personality;   // [3] @__gxx_personality_sj0 (unused here)
//           void  *lsda;          // [4] our pass replaces with catch_table_ptr
//           void  *jbuf[5];       // [5] jmp_buf
//       };
//   - On entry: clang code calls _Unwind_SjLj_Register(&FnCtx) and then
//     setjmp(&FnCtx.jbuf).  setjmp returns 0 first time → normal flow;
//     when an exception unwinds to here, longjmp(&jbuf, 1) returns
//     non-zero → our finalize pass dispatches via switch on FnCtx.call_site.
//   - At each invoke: clang stores N → FnCtx.call_site, calls the
//     function, then clears it (or stores next CS).
//   - Landing pad expects FnCtx.data[0] = exception ptr, FnCtx.data[1]
//     = selector, where selector is the (i32)(uintptr_t)&typeinfo for
//     the matched catch (per our pass's eh.typeid.for rewrite).
//
// Catch table layout (built by W65816SjLjFinalize):
//   short call_site_1
//   short typeinfo_addr_1
//   short call_site_2
//   short typeinfo_addr_2
//   ...
//   short 0   ; sentinel
//   short 0
// Stored as FnCtx.lsda; matches "if active call_site equals this row's
// call_site AND thrown type matches this row's typeinfo, this catch fires".

#include <stdint.h>
#include <stddef.h>

extern void *malloc(size_t);
extern void free(void *);
extern int  setjmp(void *jb);
extern void longjmp(void *jb, int v) __attribute__((noreturn));

typedef struct TypeInfo {
    const void *vptr;
    const char *name;
} TypeInfo;

// __dynamic_cast lives in libcxxabi.c — we reuse it for catch matching.
extern void *abiDynamicCast(const void *src, const TypeInfo *srcType,
                             const TypeInfo *dstType, int32_t hint)
    __asm__("__dynamic_cast");

// Function context layout (matches what SjLjEHPrepare emits).  We
// only access fields we read; everything else is opaque.
typedef struct FnCtx {
    struct FnCtx *prev;
    uint32_t      call_site;
    uint32_t      data[4];
    void         *personality;
    uint16_t     *lsda;          // catch table ptr (set by our pass)
    void         *jbuf[5];
} FnCtx;

// Active fn_ctx stack.  SjLjEHPrepare doesn't actually require a
// thread-local because the IIgs is single-threaded; one global suffices.
static FnCtx *gActive = 0;


// Currently-in-flight exception.  Set by __cxa_throw; consumed by
// __cxa_begin_catch.  Holds the user object (the part returned by
// __cxa_begin_catch) and the typeinfo pointer used for matching.
typedef struct ExcHeader {
    const TypeInfo *type;
    void          (*dtor)(void *);
    // The user exception object follows immediately after this header.
} ExcHeader;

static ExcHeader *gCurrentExc = 0;

void _Unwind_SjLj_Register(FnCtx *ctx) {
    ctx->prev = gActive;
    gActive   = ctx;
}


void _Unwind_SjLj_Unregister(FnCtx *ctx) {
    // SjLjEHPrepare puts unregister at every return.  Pop the stack
    // assuming LIFO order (which it is for non-pathological code).
    if (gActive == ctx) {
        gActive = ctx->prev;
    }
}


// Walk the catch table for a fn_ctx, find a matching catch for the
// thrown type, return its row's typeinfo (which is what we'll store
// in selector).  Returns 0 if no match.
static const TypeInfo *findCatch(FnCtx *ctx, const TypeInfo *thrownType, void *thrownObj, void **adjustedObjOut) {
    if (!ctx->lsda) {
        return 0;
    }
    uint16_t *p = ctx->lsda;
    uint16_t cs = (uint16_t)ctx->call_site;
    for (;;) {
        uint16_t row_cs = p[0];
        uint16_t row_ti = p[1];
        if (row_cs == 0 && row_ti == 0) {
            return 0;
        }
        if (row_cs == cs) {
            const TypeInfo *catchType = (const TypeInfo *)(uintptr_t)row_ti;
            if (thrownType == catchType) {
                *adjustedObjOut = thrownObj;
                return catchType;
            }
        }
        p += 2;
    }
}


void _Unwind_SjLj_RaiseException(ExcHeader *exc) __attribute__((noreturn));
void _Unwind_SjLj_RaiseException(ExcHeader *exc) {
    gCurrentExc = exc;
    for (FnCtx *ctx = gActive; ctx; ctx = ctx->prev) {
        void *adjustedObj = (void *)(exc + 1);
        const TypeInfo *match =
            findCatch(ctx, exc->type, adjustedObj, &adjustedObj);
        if (match) {
            gActive = ctx;
            ctx->data[0] = (uint32_t)(uintptr_t)exc;
            ctx->data[1] = (uint32_t)(uintptr_t)match;
            longjmp(ctx->jbuf, 1);
        }
    }
    extern void abort(void) __attribute__((noreturn));
    abort();
}


void _Unwind_SjLj_Resume(void *unused) __attribute__((noreturn));
void _Unwind_SjLj_Resume(void *unused) {
    (void)unused;
    if (gCurrentExc) {
        _Unwind_SjLj_RaiseException(gCurrentExc);
    }
    extern void abort(void) __attribute__((noreturn));
    abort();
}


// Personality routine — never actually called in our scheme (we
// dispatch via call_site directly).  Provided as a symbol because
// SjLjEHPrepare emits `store @__gxx_personality_sj0, fn_ctx[3]`
// at function entry.  Returns "continue unwinding" if ever invoked.
int __gxx_personality_sj0(int version, int actions, uint64_t excClass,
                           void *exc, void *ctx) {
    (void)version; (void)actions; (void)excClass; (void)exc; (void)ctx;
    return 8;  // _URC_CONTINUE_UNWIND
}


// Itanium C++ ABI surface.

void *__cxa_allocate_exception(size_t sz) {
    void *p = malloc(sizeof(ExcHeader) + sz);
    if (!p) {
        extern void abort(void) __attribute__((noreturn));
        abort();
    }
    // Zero the header; the user object isn't initialized.
    ExcHeader *h = (ExcHeader *)p;
    h->type = 0;
    h->dtor = 0;
    return (void *)(h + 1);  // user object pointer
}


void __cxa_free_exception(void *user) {
    if (user) {
        free((char *)user - sizeof(ExcHeader));
    }
}


void __cxa_throw(void *user, const TypeInfo *type, void (*dtor)(void *))
    __attribute__((noreturn));
void __cxa_throw(void *user, const TypeInfo *type, void (*dtor)(void *)) {
    ExcHeader *h = (ExcHeader *)user - 1;
    h->type = type;
    h->dtor = dtor;
    _Unwind_SjLj_RaiseException(h);
    extern void abort(void) __attribute__((noreturn));
    abort();
}


void *__cxa_begin_catch(void *exc) {
    // exc is the ExcHeader pointer (per landing pad's data[0] write).
    ExcHeader *h = (ExcHeader *)exc;
    return (void *)(h + 1);  // hand back the user object pointer
}


void __cxa_end_catch(void) {
    if (gCurrentExc) {
        if (gCurrentExc->dtor) {
            gCurrentExc->dtor(gCurrentExc + 1);
        }
        free(gCurrentExc);
        gCurrentExc = 0;
    }
}


void __cxa_rethrow(void) __attribute__((noreturn));
void __cxa_rethrow(void) {
    if (gCurrentExc) {
        _Unwind_SjLj_RaiseException(gCurrentExc);
    }
    extern void abort(void) __attribute__((noreturn));
    abort();
}
