/*
 * machine-baseline.c — regenerate every hardware constant in numbers.md on YOUR machine.
 *
 *     cc -O2 -o machine-baseline machine-baseline.c && ./machine-baseline
 *     ./machine-baseline --json > baseline.json      # for tools/baseline.py to diff
 *
 * Sections, matching numbers.md:
 *   1. latency hierarchy   (random-cycle pointer chase)
 *   2. memory bandwidth    (sequential read / write / read-modify-write)
 *   3. boundary crossings  (syscall, context switch, fsync)
 *   4. concurrency         (atomics, mutex, malloc)
 *   5. arithmetic          (naive / reordered / blocked matmul)
 *
 * WHY THIS FILE EXISTS
 * --------------------
 * numbers.md quoted six families of measurement. Shipping the prose without the code
 * asks you to take them on faith, which is exactly what ai-policy.md rule 8 forbids.
 * This is the code. Run it, and numbers.md becomes a claim you can check rather than a
 * table you must believe.
 *
 * FOUR TRAPS THIS FILE IS BUILT AROUND
 * ------------------------------------
 * Each of these produced a wrong number during development, and each is defended
 * against below with a comment marked TRAP. Read them: the defences are the lesson.
 *
 *   TRAP 1  a strided pointer chase is prefetched -> reported DRAM at 1.30 ns
 *   TRAP 2  a dead timed loop is deleted          -> reported 0.00 ns
 *   TRAP 3  getpid() is cached by libc            -> reported a "syscall" at 1.23 ns
 *   TRAP 4  the page cache impersonates the disk  -> reported 14.9 GB/s from an SSD
 *
 * PORTABILITY. Written for POSIX + clang/gcc. Uses clock_gettime(CLOCK_MONOTONIC),
 * pthreads and stdatomic. No external dependencies. F_NOCACHE is macOS-only and
 * O_DIRECT is Linux-only; both are attempted and neither is trusted (see TRAP 4).
 */

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdarg.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdatomic.h>
#include <sys/wait.h>

/* ------------------------------------------------------------------ plumbing */

static int JSON = 0;
static int nres = 0;
static struct { const char *sect, *name, *unit; double val; } res[256];

static void emit(const char *sect, const char *name, double val, const char *unit) {
    if (nres < 256) {
        res[nres].sect = sect; res[nres].name = name;
        res[nres].val = val;   res[nres].unit = unit; nres++;
    }
}

static double now(void) {
    struct timespec t;
    clock_gettime(CLOCK_MONOTONIC, &t);
    return t.tv_sec + t.tv_nsec / 1e9;
}

/* TRAP 2 DEFENCE. A file-scope volatile. A *local* volatile is not enough: clang
 * proved the local dead and deleted the entire timed loop, reporting 0.00 ns at every
 * working-set size. If a benchmark reports zero, it did not run — which is why every
 * section below also prints its raw elapsed time under --verbose. */
volatile void *g_ptr_sink;
volatile long  g_long_sink;

static uint64_t rs = 88172645463325252ULL;
static uint64_t rnd(void) { rs ^= rs << 13; rs ^= rs >> 7; rs ^= rs << 17; return rs; }

static void banner(const char *s) { if (!JSON) printf("\n== %s ==\n", s); }
static void row(const char *fmt, ...) {
    if (JSON) return;
    va_list ap; va_start(ap, fmt); vprintf(fmt, ap); va_end(ap);
}

/* ------------------------------------------------- 1. the latency hierarchy */

/*
 * One pointer per 64-byte cache line, wired into a SINGLE RANDOM CYCLE.
 *
 * Two properties are required and neither is optional:
 *
 *   dependent  — each load's address is the previous load's result, so the core
 *                cannot overlap them. Without this you measure throughput (many
 *                loads in flight), not latency.
 *
 *   random     — TRAP 1. A constant 64-byte stride is exactly sequential cache
 *                lines, the easiest possible pattern for a hardware prefetcher.
 *                Every "miss" is already in flight and DRAM appears to cost ~1 ns.
 *                Sattolo's algorithm gives a uniformly random single cycle.
 *
 * Sattolo (not Fisher-Yates): drawing j from [0, i) rather than [0, i] guarantees a
 * single cycle of length n rather than a permutation with several cycles. Several
 * cycles would mean the walk only ever visits a fraction of the working set.
 */
static double chase(size_t bytes, long iters) {
    size_t lines = bytes / 64; if (lines < 2) lines = 2;
    char *buf = aligned_alloc(64, lines * 64);
    if (!buf) { fprintf(stderr, "alloc failed for %zu bytes\n", bytes); exit(1); }
    memset(buf, 0, lines * 64);

    size_t *ord = malloc(lines * sizeof(size_t));
    for (size_t i = 0; i < lines; i++) ord[i] = i;
    for (size_t i = lines - 1; i > 0; i--) {          /* Sattolo */
        size_t j = rnd() % i;
        size_t t = ord[i]; ord[i] = ord[j]; ord[j] = t;
    }
    for (size_t i = 0; i < lines; i++)
        *(void **)(buf + ord[i] * 64) = (void *)(buf + ord[(i + 1) % lines] * 64);

    void **p = (void **)(buf + ord[0] * 64);
    long warm = iters / 10; if (warm > 1000000) warm = 1000000;
    for (long i = 0; i < warm; i++) p = (void **)*p;   /* warm cache + TLB */
    g_ptr_sink = p;

    double t0 = now();
    for (long i = 0; i < iters; i++) p = (void **)*p;
    double el = now() - t0;

    g_ptr_sink = p;                                    /* TRAP 2 defence */
    free(ord); free(buf);
    return el / iters * 1e9;
}

static void section_latency(void) {
    banner("1. latency hierarchy (random-cycle pointer chase)");
    row("%14s %10s   %s\n", "working set", "ns/load", "level (infer from the jumps)");
    struct { size_t b; const char *label; } sizes[] = {
        {4ul<<10,"4 KB"},{32ul<<10,"32 KB"},{64ul<<10,"64 KB"},{128ul<<10,"128 KB"},
        {192ul<<10,"192 KB"},{256ul<<10,"256 KB"},{512ul<<10,"512 KB"},{1ul<<20,"1 MB"},
        {2ul<<20,"2 MB"},{4ul<<20,"4 MB"},{8ul<<20,"8 MB"},{16ul<<20,"16 MB"},
        {32ul<<20,"32 MB"},{64ul<<20,"64 MB"},{128ul<<20,"128 MB"},{512ul<<20,"512 MB"},
    };
    double first = 0, last = 0;
    for (unsigned i = 0; i < sizeof(sizes)/sizeof(*sizes); i++) {
        long it = sizes[i].b < (1ul<<20) ? 10000000 : 2000000;
        double ns = chase(sizes[i].b, it);
        if (i == 0) first = ns;
        last = ns;
        emit("latency", sizes[i].label, ns, "ns");
        row("%14s %10.2f\n", sizes[i].label, ns);
    }
    row("\n  L1:DRAM ratio = 1 : %.0f    (reference machine: 1 : 133)\n", last / first);
    row("  If this line is flat, the prefetcher won -- see TRAP 1 in the source.\n");
    emit("latency", "l1_dram_ratio", last / first, "x");
}

/* ------------------------------------------------------ 2. memory bandwidth */

static void section_bandwidth(void) {
    banner("2. memory bandwidth (512 MB sequential)");
    size_t N = (512ul << 20) / 8;
    uint64_t *b = aligned_alloc(64, N * 8);
    if (!b) { fprintf(stderr, "alloc failed\n"); exit(1); }
    for (size_t i = 0; i < N; i++) b[i] = i;

    uint64_t s = 0; double t = now();
    for (size_t i = 0; i < N; i++) s += b[i];
    double rd = (double)(N * 8) / (now() - t) / 1e9;
    g_long_sink = (long)s;

    t = now();
    for (size_t i = 0; i < N; i++) b[i] = s + i;
    double wr = (double)(N * 8) / (now() - t) / 1e9;

    t = now();
    for (size_t i = 0; i < N; i++) b[i] += 1;
    double rw = (double)(N * 16) / (now() - t) / 1e9;   /* counts both directions */

    g_long_sink = (long)b[N - 1];
    emit("bandwidth", "seq_read_GBs", rd, "GB/s");
    emit("bandwidth", "seq_write_GBs", wr, "GB/s");
    emit("bandwidth", "read_modify_write_GBs", rw, "GB/s");
    row("  sequential read        %7.1f GB/s\n", rd);
    row("  sequential write       %7.1f GB/s\n", wr);
    row("  read+write             %7.1f GB/s  (both directions counted)\n", rw);

    /* Little's Law: bytes that must be in flight to sustain this bandwidth at the
     * measured DRAM latency. A single dependent-load chain keeps ONE line in flight,
     * which is why the pointer chase in section 1 is ~100x below this number. */
    double dram_ns = 0;
    for (int i = 0; i < nres; i++)
        if (!strcmp(res[i].sect, "latency") && !strcmp(res[i].name, "512 MB")) dram_ns = res[i].val;
    if (dram_ns > 0) {
        double inflight = rd * 1e9 * dram_ns * 1e-9;
        row("\n  Little's Law: %.0f bytes (%.0f cache lines) must be in flight\n",
            inflight, inflight / 64);
        row("  to sustain that read bandwidth at %.0f ns latency.\n", dram_ns);
        emit("bandwidth", "inflight_lines_required", inflight / 64, "lines");
    }
    free(b);
}

/* --------------------------------------------------- 3. boundary crossings */

static void section_boundaries(void) {
    banner("3. boundary crossings");
    const long N = 2000000;

    volatile long x = 0;
    double t = now();
    for (long i = 0; i < N; i++) x += i;
    double loop_ns = (now() - t) / N * 1e9;
    g_long_sink = x;

    /* TRAP 3. getpid() is measured ONLY to demonstrate that it is not a syscall.
     * libc caches the pid, so this times a function call and a load. Four times an
     * empty loop iteration is physically impossible for a mode switch, and nearly
     * every "syscalls are nearly free" claim traces to exactly this benchmark. */
    t = now();
    for (long i = 0; i < N; i++) getpid();
    double getpid_ns = (now() - t) / N * 1e9;

    const long M = 200000;
    t = now();
    for (long i = 0; i < M; i++) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); }
    double clk_ns = (now() - t) / M * 1e9;

    /* A real trap that fails immediately: validates the fd, fails, returns. Close to
     * a pure boundary-crossing measurement with no work attached. */
    t = now();
    for (long i = 0; i < M; i++) close(-1);
    double sys_ns = (now() - t) / M * 1e9;

    emit("boundary", "empty_loop_ns", loop_ns, "ns");
    emit("boundary", "getpid_ns", getpid_ns, "ns");
    emit("boundary", "clock_gettime_ns", clk_ns, "ns");
    emit("boundary", "syscall_ns", sys_ns, "ns");
    row("  empty loop iteration   %9.2f ns\n", loop_ns);
    row("  getpid()               %9.2f ns   <- NOT a syscall (libc caches the pid)\n", getpid_ns);
    row("  clock_gettime          %9.2f ns   <- NOT a syscall (vDSO / commpage)\n", clk_ns);
    row("  close(-1) real trap    %9.2f ns   = %.0f L1 hits\n", sys_ns, sys_ns / 0.91);

    /* Context switch via pipe ping-pong. A round trip is 4 syscalls + 2 switches, so
     * subtract the measured syscall cost to isolate the switch.
     *
     * This is the BEST case and understates reality badly: two tiny processes with
     * warm caches. The dominant real cost is the cache and TLB pollution the incoming
     * process inflicts, which a ping-pong benchmark cannot see at all. */
    int p1[2], p2[2];
    if (pipe(p1) || pipe(p2)) { perror("pipe"); return; }
    const int R = 20000; char c = 1;
    pid_t pid = fork();
    if (pid == 0) {
        for (int i = 0; i < R; i++) { if (read(p1[0], &c, 1) != 1) _exit(1); write(p2[1], &c, 1); }
        _exit(0);
    }
    t = now();
    for (int i = 0; i < R; i++) { write(p1[1], &c, 1); if (read(p2[0], &c, 1) != 1) break; }
    double rt_ns = (now() - t) / R * 1e9;
    int st; waitpid(pid, &st, 0);
    double ctx_ns = (rt_ns - 4 * sys_ns) / 2;
    emit("boundary", "pipe_roundtrip_ns", rt_ns, "ns");
    emit("boundary", "context_switch_ns", ctx_ns, "ns");
    row("  pipe round trip        %9.2f ns   (4 syscalls + 2 context switches)\n", rt_ns);
    row("  => context switch      %9.2f ns   = %.0f syscalls. BEST case: see source\n",
        ctx_ns, ctx_ns / sys_ns);

    /* fsync. The one storage number that survives TRAP 4, because fsync must reach
     * durable media before it returns. Everything read-side is page cache. */
    const int NF = 200;
    char *blk = aligned_alloc(4096, 4096); memset(blk, 7, 4096);
    int fd = open(".mb_fsync_test", O_CREAT | O_TRUNC | O_WRONLY, 0644);
    if (fd >= 0) {
        t = now();
        for (int i = 0; i < NF; i++) { if (write(fd, blk, 4096) < 0) break; fsync(fd); }
        double f_us = (now() - t) / NF * 1e6;
        close(fd); unlink(".mb_fsync_test");
        emit("boundary", "fsync_4k_us", f_us, "us");
        row("  write(4K)+fsync        %9.2f us   => %.0f durable writes/s\n",
            f_us, 1e6 / f_us);
        row("                                     = %.0f DRAM round trips\n", f_us * 1000 / 121.0);
    }
    free(blk);

    row("\n  TRAP 4: read-side disk numbers are NOT reported. On this platform the\n");
    row("  page cache serves them (F_NOCACHE is advisory on APFS; macOS has no\n");
    row("  O_DIRECT), giving impossible figures like 14.9 GB/s and 1.06M IOPS.\n");
    row("  Measure device reads with a working set several times RAM, and sanity-\n");
    row("  check against the bandwidth in section 2 before believing anything.\n");
}

/* -------------------------------------------------------- 4. concurrency */

static void section_concurrency(void) {
    banner("4. concurrency primitives (uncontended, single thread)");
    const long M = 20000000;

    volatile long v = 0;
    double t = now();
    for (long i = 0; i < M; i++) v++;
    double vol_ns = (now() - t) / M * 1e9;
    g_long_sink = v;

    _Atomic long a1 = 0;
    t = now();
    for (long i = 0; i < M; i++) atomic_fetch_add_explicit(&a1, 1, memory_order_relaxed);
    double rel_ns = (now() - t) / M * 1e9;

    _Atomic long a2 = 0;
    t = now();
    for (long i = 0; i < M; i++) atomic_fetch_add(&a2, 1);      /* seq_cst */
    double sc_ns = (now() - t) / M * 1e9;

    pthread_mutex_t mu = PTHREAD_MUTEX_INITIALIZER;
    const long K = 5000000; long guarded = 0;
    t = now();
    for (long i = 0; i < K; i++) { pthread_mutex_lock(&mu); guarded++; pthread_mutex_unlock(&mu); }
    double mu_ns = (now() - t) / K * 1e9;
    g_long_sink = guarded;

    const long A = 2000000;
    t = now();
    for (long i = 0; i < A; i++) { void *q = malloc(64); g_ptr_sink = q; free(q); }
    double m64_ns = (now() - t) / A * 1e9;
    t = now();
    for (long i = 0; i < A / 4; i++) { void *q = malloc(1 << 16); g_ptr_sink = q; free(q); }
    double m64k_ns = (now() - t) / (A / 4) * 1e9;

    emit("concurrency", "volatile_inc_ns", vol_ns, "ns");
    emit("concurrency", "atomic_relaxed_ns", rel_ns, "ns");
    emit("concurrency", "atomic_seqcst_ns", sc_ns, "ns");
    emit("concurrency", "mutex_ns", mu_ns, "ns");
    emit("concurrency", "malloc64_ns", m64_ns, "ns");
    emit("concurrency", "malloc64k_ns", m64k_ns, "ns");
    row("  volatile long++        %9.2f ns\n", vol_ns);
    row("  atomic add, relaxed    %9.2f ns\n", rel_ns);
    row("  atomic add, seq_cst    %9.2f ns   (%.1fx relaxed -- the barrier)\n", sc_ns, sc_ns / rel_ns);
    row("  mutex lock+unlock      %9.2f ns   (%.1fx relaxed atomic)\n", mu_ns, mu_ns / rel_ns);
    row("  malloc(64)+free        %9.2f ns\n", m64_ns);
    row("  malloc(64K)+free       %9.2f ns\n", m64k_ns);
    row("\n  NOT MEASURED: branch misprediction. Three attempts each measured\n");
    row("  something else (cmov conversion, then store throughput). The honest\n");
    row("  output is 'not measured' -- see numbers.md section 14.\n");
}

/* --------------------------------------------------------- 5. arithmetic */

#define MN 512
static float MA[MN*MN], MB[MN*MN], MC[MN*MN];

static void mm_naive(void) {
    for (int i = 0; i < MN; i++)
        for (int j = 0; j < MN; j++) {
            float s = 0;
            for (int k = 0; k < MN; k++) s += MA[i*MN+k] * MB[k*MN+j];
            MC[i*MN+j] = s;
        }
}
static void mm_reordered(void) {
    memset(MC, 0, sizeof(MC));
    for (int i = 0; i < MN; i++)
        for (int k = 0; k < MN; k++) {
            float a = MA[i*MN+k];
            for (int j = 0; j < MN; j++) MC[i*MN+j] += a * MB[k*MN+j];
        }
}
static void mm_blocked(int BS) {
    memset(MC, 0, sizeof(MC));
    for (int ii = 0; ii < MN; ii += BS)
        for (int kk = 0; kk < MN; kk += BS)
            for (int jj = 0; jj < MN; jj += BS)
                for (int i = ii; i < ii+BS && i < MN; i++)
                    for (int k = kk; k < kk+BS && k < MN; k++) {
                        float a = MA[i*MN+k];
                        for (int j = jj; j < jj+BS && j < MN; j++) MC[i*MN+j] += a * MB[k*MN+j];
                    }
}

static void section_arithmetic(void) {
    banner("5. arithmetic throughput (512x512 fp32 matmul, single thread)");
    for (int i = 0; i < MN*MN; i++) {
        MA[i] = (float)((i*31) % 7) - 3;
        MB[i] = (float)((i*17) % 5) - 2;
    }
    double f = 2.0 * MN * MN * MN, t;

    t = now(); mm_naive();     double gf_n = f / (now()-t) / 1e9;
    t = now(); mm_reordered(); double gf_r = f / (now()-t) / 1e9;
    emit("arithmetic", "matmul_naive_GFs", gf_n, "GF/s");
    emit("arithmetic", "matmul_reordered_GFs", gf_r, "GF/s");
    row("  naive   i,j,k          %9.2f GFLOP/s\n", gf_n);
    row("  reorder i,k,j          %9.2f GFLOP/s   <- %.1fx from LOOP ORDER alone\n",
        gf_r, gf_r / gf_n);

    double best = 0; int bestbs = 0;
    for (int bs = 16; bs <= 128; bs *= 2) {
        t = now(); mm_blocked(bs); double g = f / (now()-t) / 1e9;
        char nm[32]; snprintf(nm, sizeof nm, "matmul_blocked_%d_GFs", bs);
        /* name must outlive the call; emit stores the pointer, so use a static table */
        static char names[8][32]; static int ni = 0;
        snprintf(names[ni], 32, "%s", nm);
        emit("arithmetic", names[ni++], g, "GF/s");
        if (g > best) { best = g; bestbs = bs; }
        row("  blocked BS=%-3d         %9.2f GFLOP/s\n", bs, g);
    }
    row("\n  best block size %d at %.2f GF/s\n", bestbs, best);
    row("  Rebuild with -O3 -ffast-math -march=native (or -mcpu=native): blocking is\n");
    row("  often WORSE than plain reordering until the inner loop vectorises.\n");
    row("  Then compare against your BLAS: python3 -c \"import numpy as np,time;\\\n");
    row("  N=512;A=np.random.rand(N,N).astype('f');B=A.copy();\\\n");
    row("  [A@B for _ in range(5)];t=time.perf_counter();A@B;\\\n");
    row("  print(2*N**3/(time.perf_counter()-t)/1e9,'GFLOP/s')\"\n");
    emit("arithmetic", "matmul_best_blocked_GFs", best, "GF/s");
    emit("arithmetic", "matmul_best_block_size", bestbs, "");
}

/* -------------------------------------------------------------- driver */

int main(int argc, char **argv) {
    for (int i = 1; i < argc; i++) if (!strcmp(argv[i], "--json")) JSON = 1;

    if (!JSON) {
        printf("machine-baseline — regenerating the constants in numbers.md\n");
        printf("Reference machine: 12-core arm64 macOS, clang -O2, CPython 3.14.\n");
        printf("Yours will differ. The RATIOS should not.\n");
    }

    section_latency();
    section_bandwidth();
    section_boundaries();
    section_concurrency();
    section_arithmetic();

    if (JSON) {
        printf("{\n");
        for (int i = 0; i < nres; i++)
            printf("  \"%s.%s\": %.6f%s\n", res[i].sect, res[i].name, res[i].val,
                   i == nres - 1 ? "" : ",");
        printf("}\n");
    } else {
        printf("\nRecord these in notebook/001-machine-baseline.md.\n");
        printf("Diff against the reference: ./machine-baseline --json > mine.json &&"
               " python3 baseline.py mine.json\n");
    }
    return 0;
}
