aristo-cli 0.6.0

Aristo CLI binary (the `aristo` command).
Documentation
/*
 * aristo.c - Aristo C instrumentation runtime.
 *
 * Vendored by `aristo instrument vendor-c` (aristo {{SDK_VERSION}}).
 * Compile this file only in instrumented builds (with -DARISTO_INSTRUMENT);
 * in production it holds no external symbols and need not be built.
 *
 * Guarded so a unity / amalgamation build may `#include "aristo.c"` once
 * without a duplicate-symbol error. (Two SEPARATE translation units must
 * still include it only once between them - the guard is per-TU.)
 */
#ifndef ARISTO_C_INCLUDED
#define ARISTO_C_INCLUDED

#include "aristo.h"

#ifdef ARISTO_INSTRUMENT

/*
 * Per-thread hook storage. C11 _Thread_local gives each harness thread its
 * own fault policy with no locking, so parallel scenarios never interfere.
 * `dirty` records whether the hook called the setter DURING its own dispatch.
 */
static _Thread_local aristo_yield_fn g_yield_fn    = 0;
static _Thread_local void           *g_yield_state = 0;
static _Thread_local int             g_yield_dirty = 0;
static _Thread_local aristo_fault_fn g_fault_fn    = 0;
static _Thread_local void           *g_fault_state = 0;
static _Thread_local int             g_fault_dirty = 0;

void aristo_set_hook(aristo_yield_fn fn, void *state) {
    g_yield_fn = fn;
    g_yield_state = state;
    g_yield_dirty = 1;
}

void aristo_set_fault_hook(aristo_fault_fn fn, void *state) {
    g_fault_fn = fn;
    g_fault_state = state;
    g_fault_dirty = 1;
}

/*
 * Take-call-restore. Before calling the hook we clear the slot (so a point
 * reached re-entrantly inside the hook is inert) AND clear `dirty`. After the
 * hook returns: if it did NOT call the setter (dirty still 0) we restore the
 * original {fn,state}; if it DID call the setter - installing a new hook, OR
 * clearing itself with set(NULL, NULL) - we keep whatever it set. Tracking the
 * setter *call* (not the resulting fn value) is what makes a self-clearing
 * one-shot actually stay cleared.
 */
void aristo_yield_point(const char *label) {
    aristo_yield_fn fn = g_yield_fn;
    void *state = g_yield_state;
    if (!fn) {
        return;
    }
    g_yield_fn = 0;
    g_yield_state = 0;
    g_yield_dirty = 0;
    fn(label, state);
    if (!g_yield_dirty) {
        g_yield_fn = fn;
        g_yield_state = state;
    }
}

aristo_decision aristo_fault_point(const char *label) {
    aristo_fault_fn fn = g_fault_fn;
    void *state = g_fault_state;
    if (!fn) {
        return ARISTO_CONTINUE;
    }
    g_fault_fn = 0;
    g_fault_state = 0;
    g_fault_dirty = 0;
    aristo_decision d = fn(label, state);
    if (!g_fault_dirty) {
        g_fault_fn = fn;
        g_fault_state = state;
    }
    return d;
}

#else /* !ARISTO_INSTRUMENT */

/*
 * Production build: no external symbols. ISO C forbids an empty translation
 * unit, so declare one file-scope typedef to keep -Wpedantic quiet. This file
 * need not be added to a production build at all.
 */
typedef int aristo_runtime_translation_unit_is_not_empty;

#endif /* ARISTO_INSTRUMENT */

#endif /* ARISTO_C_INCLUDED */