224 lines
7.1 KiB
C
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();
|
|
}
|