65816-llvm-mos/runtime/src/libcxxabiSjlj.c
Scott Duensing 15c7fa0db2 Checkpoint
2026-05-07 19:59:20 -05:00

224 lines
7.1 KiB
C

// 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();
}