// W65816 minimal UBSan runtime — handler stubs for
// `-fsanitize=undefined -fsanitize-minimal-runtime`.
//
// Mirrors compiler-rt/lib/ubsan_minimal/ubsan_minimal_handlers.cpp but
// stripped of:
//   - the `__sanitizer::atomic_*` dance (65816 is single-threaded — no
//     concurrent updates of the dedup table are possible).
//   - the `[[clang::preserve_all]]` variant (PRESERVE_HANDLERS is hard-
//     false on this target — the attribute is x86_64/aarch64 only).
//   - `android_set_abort_message` (no Android).
//   - the SANITIZER_DEBUG `CheckFailed` namespace gunk.
//
// Phase 6.2 (Phase 0.3 LOCKED): NO ASan — ASan's 8:1 shadow memory model
// does not fit a 16 MB 65816 address space (would need 2 MB of shadow;
// most IIgs programs run in 1-2 banks).
//
// Dependencies (both landed):
//   - Phase 1.4.a: ISD::RETURNADDR i32 Expand — makes
//     __builtin_return_address(0) compile (today expands to 0; the call
//     itself no longer ICEs clang).  We use the call as a stable "caller
//     PC" surface; the value is wrong-but-not-fatal (always 0), so the
//     dedup table effectively dedupes on the kind string alone.  A
//     follow-up Phase 1.4.a improvement can return the real RTL frame.
//   - Phase 1.4.b: ISD::TRAP Custom -> BRK_pseudo — makes
//     __builtin_trap() emit BRK + spin (and stash sentinel 0xBE @ $70).
//     Used as `abort()` in the `_abort` variants.
//
// Build: compile with `-fno-sanitize=undefined` (mandatory — without the
// flag the handlers would self-call recursively on integer overflow and
// stack-blow).  runtime/build.sh sets this flag for ubsan.c only.

#include <stdint.h>
#include <stddef.h>

// Console hooks shared with libc.c.  libcGno.c provides a strong
// definition that routes to GS/OS fd 3 (stderr).  In non-GNO links
// __putByteErr is undefined-weak (null) and we fall through to
// __putByte's $E2 / GS/OS-stdout sink — better than silent drop.
extern void __putByteErr(char c) __attribute__((weak));
extern void __putByte(char c)    __attribute__((weak));


// ---- dedup table ----
//
// kMaxCallerPcs entries; +1 special "too many errors" sentinel.  Since
// __builtin_return_address(0) currently returns 0 in this target's
// Phase 1.4.a Expand lowering, every caller looks identical to the
// dedup logic and you get exactly one "ubsan: <kind>" line per kind
// across the program run.  That is the desired behaviour for the
// minimal runtime — verbose-per-site reporting is what the full UBSan
// runtime is for.
#define UBSAN_MAX_CALLER_PCS  20

static uintptr_t callerPcs[UBSAN_MAX_CALLER_PCS];
static uint16_t  callerPcsSz = 0;


// ---- output ----

static void emitStr(const char *s) {
    void (*put)(char) = __putByteErr ? __putByteErr : __putByte;
    if (!put) {
        return;
    }
    while (*s) {
        put(*s);
        s++;
    }
}


static void emitHex(uintptr_t d) {
    void (*put)(char) = __putByteErr ? __putByteErr : __putByte;
    if (!put) {
        return;
    }
    // sizeof(uintptr_t) == 4 on this target (ptr32).  Emit 8 nibbles
    // MSB-first.
    uint8_t shift = 32;
    while (shift) {
        shift -= 4;
        uint8_t nibble = (uint8_t)((d >> shift) & 0x0f);
        char    c      = (char)(nibble < 10 ? nibble + '0' : nibble - 10 + 'a');
        put(c);
    }
}


// One unified emitter — every handler funnels through this so the
// output shape stays consistent and the .o is as small as possible.
static void reportError(const char *kind, uintptr_t caller) {
    // Dedup: scan the table, return if seen; otherwise append.  Strings
    // are static, so pointer-equality on `kind` suffices — no strcmp.
    if (callerPcsSz > UBSAN_MAX_CALLER_PCS) {
        return;
    }

    uint16_t i;
    for (i = 0; i < callerPcsSz; i++) {
        if (callerPcs[i] == caller) {
            return;
        }
    }

    if (callerPcsSz == UBSAN_MAX_CALLER_PCS) {
        callerPcsSz++;
        emitStr("ubsan: too many errors\n");
        return;
    }
    callerPcs[callerPcsSz++] = caller;

    emitStr("ubsan: ");
    emitStr(kind);
    emitStr(" by 0x");
    emitHex(caller);
    emitStr("\n");
}


static void abortWithMessage(const char *kind, uintptr_t caller) {
    reportError(kind, caller);
    // Phase 1.4.b BRK_pseudo lowering: stashes 0xBE @ $70 then spins.
    __builtin_trap();
}


// ---- handler macros ----
//
// Each HANDLER name emits BOTH a recovering and an aborting entry
// point, matching upstream's recover/abort split.  HANDLER_RECOVER is
// recover-only (for kinds where the abort form is never emitted —
// builtin_unreachable, missing_return).
//
// We don't use [[clang::preserve_all]] (not supported on w65816) so
// every JSL into a handler is a normal C calling convention.  Caller-
// saves A/X/Y/DPF0 are already declared on JSLpseudo (see
// feedback_jslpseudo_caller_save.md) — instrumented code keeps working.

#define UBSAN_CALLER_PC()  ((uintptr_t)__builtin_return_address(0))

#define HANDLER_RECOVER(name, kind)                                       \
    void __ubsan_handle_##name##_minimal(void) {                          \
        reportError(kind, UBSAN_CALLER_PC());                             \
    }

#define HANDLER_NORECOVER(name, kind)                                     \
    void __ubsan_handle_##name##_minimal_abort(void) {                    \
        abortWithMessage(kind, UBSAN_CALLER_PC());                        \
    }

#define HANDLER(name, kind)                                               \
    HANDLER_RECOVER(name, kind)                                           \
    HANDLER_NORECOVER(name, kind)


// ---- the 25 handler kinds ----
// 23 HANDLER pairs (recover + abort) + 2 HANDLER_RECOVER-only =
// 25 distinct names / 48 total functions.  Order/spelling exactly
// matches upstream compiler-rt's ubsan_minimal_handlers.cpp so a probe
// built against the upstream symbol set links cleanly.  (Phase 6.2's
// brief overcounted by 1 — upstream has 23 full pairs, not 24.)

HANDLER(type_mismatch,            "type-mismatch")
HANDLER(alignment_assumption,     "alignment-assumption")
HANDLER(add_overflow,             "add-overflow")
HANDLER(sub_overflow,             "sub-overflow")
HANDLER(mul_overflow,             "mul-overflow")
HANDLER(negate_overflow,          "negate-overflow")
HANDLER(divrem_overflow,          "divrem-overflow")
HANDLER(shift_out_of_bounds,      "shift-out-of-bounds")
HANDLER(out_of_bounds,            "out-of-bounds")
HANDLER(local_out_of_bounds,      "local-out-of-bounds")
HANDLER_RECOVER(builtin_unreachable, "builtin-unreachable")
HANDLER_RECOVER(missing_return,      "missing-return")
HANDLER(vla_bound_not_positive,   "vla-bound-not-positive")
HANDLER(float_cast_overflow,      "float-cast-overflow")
HANDLER(load_invalid_value,       "load-invalid-value")
HANDLER(invalid_builtin,          "invalid-builtin")
HANDLER(invalid_objc_cast,        "invalid-objc-cast")
HANDLER(function_type_mismatch,   "function-type-mismatch")
HANDLER(implicit_conversion,      "implicit-conversion")
HANDLER(nonnull_arg,              "nonnull-arg")
HANDLER(nonnull_return,           "nonnull-return")
HANDLER(nullability_arg,          "nullability-arg")
HANDLER(nullability_return,       "nullability-return")
HANDLER(pointer_overflow,         "pointer-overflow")
HANDLER(cfi_check_fail,           "cfi-check-fail")
