// locks.h — tres locks hechos a mano con atómicos de C11 (<stdatomic.h>):
//
//   tas_lock     spinlock con test-and-set (atomic_exchange)
//   ticket_lock  ticket lock con fetch-and-add: atiende en orden de llegada
//   futex_lock   lock de dos fases: gira un rato y luego duerme con futex
//
// Todo vive en el header (funciones static inline) para que el compilador
// pueda integrarlas en el lazo del benchmark.
#ifndef LOCKS_H
#define LOCKS_H

#include <linux/futex.h>
#include <sched.h>
#include <stdatomic.h>
#include <sys/syscall.h>
#include <unistd.h>

// Pista para el procesador: "estoy girando". No cambia la semántica.
static inline void cpu_relax(void) {
#if defined(__aarch64__)
    __asm__ __volatile__("yield");
#elif defined(__x86_64__)
    __asm__ __volatile__("pause");
#endif
}

// ------------------------------------------------------------
// Spinlock con test-and-set
// ------------------------------------------------------------
typedef struct {
    atomic_int locked; // 0 libre, 1 tomado
} tas_lock;

static inline void tas_init(tas_lock *l) { atomic_init(&l->locked, 0); }

static inline void tas_acquire(tas_lock *l) {
    // TestAndSet: escribe 1 y devuelve el valor anterior, en un solo paso.
    while (atomic_exchange_explicit(&l->locked, 1, memory_order_acquire) == 1)
        cpu_relax();
}

// Variante que cede la CPU en vez de girar (OSTEP 28.13).
static inline void tas_acquire_yield(tas_lock *l) {
    while (atomic_exchange_explicit(&l->locked, 1, memory_order_acquire) == 1)
        sched_yield();
}

static inline void tas_release(tas_lock *l) {
    atomic_store_explicit(&l->locked, 0, memory_order_release);
}

// ------------------------------------------------------------
// Ticket lock con fetch-and-add
// ------------------------------------------------------------
typedef struct {
    atomic_uint ticket; // siguiente número a repartir
    atomic_uint turn;   // número que se atiende ahora
} ticket_lock;

static inline void ticket_init(ticket_lock *l) {
    atomic_init(&l->ticket, 0);
    atomic_init(&l->turn, 0);
}

static inline void ticket_acquire(ticket_lock *l) {
    unsigned my = atomic_fetch_add_explicit(&l->ticket, 1, memory_order_relaxed);
    while (atomic_load_explicit(&l->turn, memory_order_acquire) != my)
        cpu_relax();
}

static inline void ticket_release(ticket_lock *l) {
    // Solo el owner escribe turn, pero se usa fetch_add por claridad.
    atomic_fetch_add_explicit(&l->turn, 1, memory_order_release);
}

// ------------------------------------------------------------
// Lock de dos fases con futex (estilo glibc, 3 estados)
//   0 = libre, 1 = tomado sin esperas, 2 = tomado y quizá hay quien duerme
// ------------------------------------------------------------
typedef struct {
    atomic_int state;
} futex_lock;

#define FUTEX_SPINS 100 // fase 1: cuántas veces girar antes de dormir

static inline long sys_futex(atomic_int *addr, int op, int val) {
    return syscall(SYS_futex, addr, op, val, NULL, NULL, 0);
}

static inline void futex_init(futex_lock *l) { atomic_init(&l->state, 0); }

static inline void futex_acquire(futex_lock *l) {
    int c = 0;
    // Camino rápido: 0 -> 1 sin entrar al kernel.
    if (atomic_compare_exchange_strong(&l->state, &c, 1))
        return;
    // Fase 1: girar un poco, por si el owner suelta pronto.
    for (int i = 0; i < FUTEX_SPINS; i++) {
        c = 0;
        if (atomic_compare_exchange_strong(&l->state, &c, 1))
            return;
        cpu_relax();
    }
    // Fase 2: marcar "hay esperas" (2) y dormir mientras siga tomado.
    while (atomic_exchange(&l->state, 2) != 0)
        sys_futex(&l->state, FUTEX_WAIT_PRIVATE, 2); // duerme si state == 2
}

static inline void futex_release(futex_lock *l) {
    // Si nadie esperaba (estaba en 1), no hace falta llamar al kernel.
    if (atomic_exchange(&l->state, 0) == 2)
        sys_futex(&l->state, FUTEX_WAKE_PRIVATE, 1); // despierta a uno
}

#endif
