// libunwindStub.c — Itanium _Unwind_* surface mapped onto our SJLJ runtime.
//
// Phase 5.1 of GAP_CLOSURE_PLAN (Phase 0.1 LOCKED option A): NOT a real
// DWARF unwinder.  We expose the symbols third-party C++ libraries
// (libcxx, abseil, etc.) reference from their `<exception>` and
// `<typeinfo>` paths and route them through the existing SJLJ machinery
// in libcxxabiSjlj.c.
//
// Contract:
//   - `_Unwind_RaiseException(exc)` is invoked by user code that wants
//     to throw a pre-allocated `_Unwind_Exception`.  We delegate to the
//     SJLJ raiser, which walks gActive and longjmps to the first frame
//     whose catch table matches.
//   - `_Unwind_Resume(exc)` corresponds to a `resume` instruction at
//     the tail of a cleanup landing pad.  Our SJLJ landing pads
//     dispatch from data[0]/data[1] directly so this is rarely hit;
//     when it is, we keep unwinding by re-raising.
//   - `_Unwind_GetIP` / `_Unwind_SetIP` / `_Unwind_GetCFA` /
//     `_Unwind_GetLanguageSpecificData` operate on a
//     `_Unwind_Context *`.  Our SJLJ scheme never builds a real
//     context — we hand back 0/no-op values that match what a personality
//     routine asking "what was the IP?" would see in a stub
//     environment (i.e. "nothing useful here, continue unwinding").
//   - `_Unwind_DeleteException` calls the exception_cleanup callback if
//     non-null and is otherwise a no-op; user code allocates the
//     exception storage itself.
//
// All symbols are weak so user code (or a real unwinder ported later)
// can override.  Pure-C programs and C++ programs that don't use these
// entry points get link-GC'd to zero cost.
//
// Throwing across a non-SJLJ-instrumented frame terminates: the SJLJ
// raiser walks gActive, and frames not registered via
// _Unwind_SjLj_Register are invisible.  Document this in the
// reviewer-facing notes.

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

// Itanium ABI return codes.  Public surface.
typedef enum UnwindReasonE {
    URC_NO_REASON                  = 0,
    URC_FOREIGN_EXCEPTION_CAUGHT   = 1,
    URC_FATAL_PHASE2_ERROR         = 2,
    URC_FATAL_PHASE1_ERROR         = 3,
    URC_NORMAL_STOP                = 4,
    URC_END_OF_STACK               = 5,
    URC_HANDLER_FOUND              = 6,
    URC_INSTALL_CONTEXT            = 7,
    URC_CONTINUE_UNWIND            = 8
} UnwindReasonE;

// Opaque to user code; we never inspect the body — only the cleanup
// callback at a fixed offset that user code initialized.
struct _Unwind_Exception;
typedef void (*UnwindExceptionCleanupFn)(UnwindReasonE reason, struct _Unwind_Exception *exc);

// Layout per Itanium ABI: 8-byte class + cleanup fn + 2 private slots.
// We only need to reach `exception_cleanup`.
typedef struct _Unwind_Exception {
    uint64_t                 exception_class;
    UnwindExceptionCleanupFn exception_cleanup;
    uintptr_t                private_1;
    uintptr_t                private_2;
} _Unwind_Exception;

// Opaque context — see notes above.
typedef struct _Unwind_Context _Unwind_Context;

// Forward to the SJLJ raiser.  The signature differs from the public
// one (it takes an ExcHeader) but for the stub surface we treat the
// _Unwind_Exception as if it were the ExcHeader — both are pointers
// into user-allocated storage and the SJLJ matcher only reads the type
// off it, which user code with this entry point hasn't set up.  In
// practice third-party throwers that bypass __cxa_throw and go straight
// to _Unwind_RaiseException are rare and they don't reach our catch
// dispatch anyway; the contract here is "doesn't fail to link, terminates
// cleanly at runtime if actually invoked".
extern void _Unwind_SjLj_RaiseException(void *exc) __attribute__((noreturn));
extern void abort(void) __attribute__((noreturn));


// ---- raise / resume ----

__attribute__((weak, noreturn))
UnwindReasonE _Unwind_RaiseException(_Unwind_Exception *exc) {
    // Route to the SJLJ raiser.  If no frame matches it falls through
    // to abort() (see libcxxabiSjlj.c), which satisfies the
    // "terminates" semantics for un-SJLJ-instrumented throw paths.
    _Unwind_SjLj_RaiseException((void *)exc);
    // Unreachable; abort() above is noreturn and so is the raiser.
    abort();
}


__attribute__((weak, noreturn))
void _Unwind_Resume(_Unwind_Exception *exc) {
    // Cleanup landing pad finished and asked us to keep unwinding.
    // SJLJ scheme normally dispatches via data[0]/data[1] directly,
    // but if we land here we re-raise to walk the next outer frame.
    _Unwind_SjLj_RaiseException((void *)exc);
    abort();
}


// ---- context getters/setters ----
//
// In a real DWARF unwinder these inspect the saved register state of
// the frame being unwound.  Our SJLJ scheme never materializes such
// state, so we hand back conservative zeros / accept-and-discard.  A
// personality routine seeing IP=0 / LSDA=0 will return
// URC_CONTINUE_UNWIND, which is exactly the behavior we want.

__attribute__((weak))
uintptr_t _Unwind_GetIP(_Unwind_Context *ctx) {
    (void)ctx;
    return 0;
}


__attribute__((weak))
void _Unwind_SetIP(_Unwind_Context *ctx, uintptr_t ip) {
    (void)ctx;
    (void)ip;
}


__attribute__((weak))
uintptr_t _Unwind_GetCFA(_Unwind_Context *ctx) {
    (void)ctx;
    return 0;
}


__attribute__((weak))
uintptr_t _Unwind_GetLanguageSpecificData(_Unwind_Context *ctx) {
    // A real implementation returns the LSDA pointer for the
    // currently-being-unwound frame; we have no such notion here.
    (void)ctx;
    return 0;
}


// ---- delete ----

__attribute__((weak))
void _Unwind_DeleteException(_Unwind_Exception *exc) {
    if (exc && exc->exception_cleanup) {
        exc->exception_cleanup(URC_FOREIGN_EXCEPTION_CAUGHT, exc);
    }
    // User code owns the storage.  No free() here.
}
