65816-llvm-mos/runtime/src/libunwindStub.c
Scott Duensing da095402ec Updated
2026-06-02 23:17:57 -05:00

152 lines
5.7 KiB
C

// 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.
}