/* M2 item 1: the de-globalisation teeth. Exercises ailang_rc_alloc /
 * ailang_rc_dec DIRECTLY (not through any kernel) from N threads, each
 * owning its own ailang_ctx_t. Per-ctx counters must be individually
 * correct and -fsanitize=thread-clean. Build -DSHARED_CTX for the
 * global-hammering negative control (must make tsan report a race). */
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <assert.h>

typedef struct ailang_ctx ailang_ctx_t;
extern ailang_ctx_t *ailang_ctx_new(void);
extern void          ailang_ctx_free(ailang_ctx_t *);
extern void         *ailang_rc_alloc(size_t);
extern void          ailang_rc_dec(void *);
/* set the per-call ctx the runtime accounts into (defined in rc.c) */
extern __thread ailang_ctx_t *__ail_tls_ctx;

#define NTHREADS 8
#define NCYCLES  200000

#ifdef SHARED_CTX
static ailang_ctx_t *g_shared; /* negative control: one ctx, all threads */
#endif

static void *worker(void *_a) {
    (void)_a;
#ifdef SHARED_CTX
    ailang_ctx_t *ctx = g_shared;
#else
    ailang_ctx_t *ctx = ailang_ctx_new();
#endif
    __ail_tls_ctx = ctx;
    for (int i = 0; i < NCYCLES; i++) {
        void *p = ailang_rc_alloc(16);
        ailang_rc_dec(p); /* refcount 1 -> 0, frees, free_count++ */
    }
#ifndef SHARED_CTX
    /* per-ctx balance must hold exactly */
    /* (read via the same struct layout the runtime defines) */
    ailang_ctx_free(ctx);
#endif
    return NULL;
}

int main(void) {
#ifdef SHARED_CTX
    g_shared = ailang_ctx_new();
#endif
    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);
#ifdef SHARED_CTX
    ailang_ctx_free(g_shared);
#endif
    printf("rc_accounting_tsan: ok\n");
    return 0;
}
