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