// 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 `` and // `` 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 #include // 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. }