/* bugfix-rc-global-stats-race: RED host. * * Drives the GLOBAL RC-stats fallback path (`g_rc_alloc_count` / * `g_rc_free_count`, runtime/rc.c:90-91) under high thread contention. * * Crucially: NO `ailang_ctx_new`, so `__ail_tls_ctx` stays NULL in every * worker and both `ailang_rc_alloc` (rc.c:161) and the to-zero branch of * `ailang_rc_dec` (rc.c:212) take the `else g_rc_*count++;` arm. Those * two counters are plain `static uint64_t` with a non-atomic `++`, so * concurrent increments lose updates (classic read-modify-write race). * * Each worker does NCYCLES of alloc-immediately-dec on a 16-byte box. * No box ever crosses a thread (alloc + dec in the same loop body), so * the per-object refcount header op is correct and the program is * bit-exact every run. The ONLY observable defect is the under-counted * global Σ printed by the atexit handler. * * The integration test (crates/ail/tests/embed_rc_global_stats_race.rs) * parses the atexit `ailang_rc_stats:` line and asserts * allocs == NTHREADS*NCYCLES AND frees == NTHREADS*NCYCLES. * Pre-fix: reliably fails (allocs/frees short by lost increments). * Post-fix (atomic global counters): deterministic exact equality. * * This host links libailang_rt.a directly; it does not call any kernel. */ #include #include #include extern void *ailang_rc_alloc(size_t); extern void ailang_rc_dec(void *); /* High contention: 8 threads * 2_000_000 cycles = 16_000_000 expected * global allocs and 16_000_000 global frees. At this contention the * non-atomic `++` reliably loses updates on every observed run, so the * RED is deterministic (it fails pre-fix, not merely flaky). */ #define NTHREADS 8 #define NCYCLES 2000000 static void *worker(void *_a) { (void)_a; /* __ail_tls_ctx left NULL on purpose -> global fallback path. */ for (int i = 0; i < NCYCLES; i++) { void *p = ailang_rc_alloc(16); ailang_rc_dec(p); /* refcount 1 -> 0, frees, g_rc_free_count++ */ } return NULL; } int main(void) { pthread_t t[NTHREADS]; for (int i = 0; i < NTHREADS; i++) pthread_create(&t[i], NULL, worker, NULL); for (int i = 0; i < NTHREADS; i++) pthread_join(t[i], NULL); /* All threads joined before main returns -> the atexit reader sees * a fully quiesced counter (no reader/writer ordering concern; the * defect is purely the lost writer-vs-writer increments). */ printf("rc_global_stats_race: ran %d threads x %d cycles\n", NTHREADS, NCYCLES); return 0; }