// unwindStubProbe.cpp — Phase 5.1 smoke for the `_Unwind_*` stub.
//
// Exercises the Itanium `_Unwind_*` surface from libunwindStub.o.  These
// entry points are what third-party C++ libraries reference from their
// own exception-handling paths (abseil, fmt, libcxx itself); confirming
// they link AND that the cleanup callback fires at runtime proves the
// stub is functional end-to-end.
//
// Why no `throw` / `catch` in this runtime probe: SJLJ-prepared C++
// exception code is documented to crash MAME's apple2gs CPU emulation
// intermittently (smokeTest.sh:4906-4912 notes the same and runs only a
// link check there).  This probe stays on the pure-C surface so we get
// a green runtime marker.  The companion link check
// (scripts/smokeTest.sh) already validates that clang++ + libunwindStub
// produces a linkable C++ binary that uses throw / catch.
//
// Markers (16-bit, bank-2):
//   $025000 = 0xC0DE  reached main()
//   $025002 = 0xBEEF  _Unwind_DeleteException cleanup callback fired
//   $025004 = 0x900D  end of main()

extern "C" {
#include <stdint.h>
}

// Itanium ABI shapes — duplicated locally so the probe is
// self-contained (no <unwind.h> shim in our tree).  Layout must match
// libunwindStub.c's _Unwind_Exception.
typedef enum {
    URC_NO_REASON                = 0,
    URC_FOREIGN_EXCEPTION_CAUGHT = 1
} UnwindReasonE;

struct _Unwind_Exception;
typedef void (*UnwindExceptionCleanupFn)(UnwindReasonE, _Unwind_Exception *);

struct _Unwind_Exception {
    uint64_t                 exception_class;
    UnwindExceptionCleanupFn exception_cleanup;
    uintptr_t                private_1;
    uintptr_t                private_2;
};

extern "C" void _Unwind_DeleteException(_Unwind_Exception *exc);

static volatile uint16_t gCleanupFired = 0;

static void onCleanup(UnwindReasonE reason, _Unwind_Exception *exc) {
    (void)reason;
    (void)exc;
    gCleanupFired = 0xBEEF;
}


int main(void) {
    *(volatile uint16_t *)0x025000UL = 0xC0DE;

    // Stack-allocate a _Unwind_Exception, register a cleanup callback,
    // hand it to _Unwind_DeleteException, and confirm the callback
    // fired.  This is the surface third-party code reaches when it
    // owns the exception storage itself rather than going through
    // __cxa_throw.
    _Unwind_Exception localExc;
    localExc.exception_class   = 0;
    localExc.exception_cleanup = &onCleanup;
    localExc.private_1         = 0;
    localExc.private_2         = 0;
    _Unwind_DeleteException(&localExc);
    *(volatile uint16_t *)0x025002UL = gCleanupFired;

    *(volatile uint16_t *)0x025004UL = 0x900D;

    // GNO commands return to gsh after main().
    return 0;
}
