// peterson.c — dos locks hechos solo con loads y stores, y por qué fallan.
//
// Dos hilos suman N veces cada uno a un contador, dentro de un lock:
//   flag      lock con una bandera: "mirar y luego tomar" (roto siempre)
//   peterson  algoritmo de Peterson sin barreras de memoria
//   fence     Peterson con una barrera (fence) entre escribir y leer
//
// En un procesador con memoria débil (ARM, RISC-V) o con store buffer (x86)
// el modo peterson también pierde sumas: la escritura de flag[yo] puede
// quedar en el store buffer mientras se lee flag[otro].
//
// Uso:  ./peterson flag|peterson|fence [N]
#include <pthread.h>
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static long loops;
static long counter; // protegido por el lock (si el lock funciona)
static int mode;     // 0 flag, 1 peterson, 2 fence

// --- lock con bandera (OSTEP fig. 28.1) ---
static atomic_int busy;

static void flag_lock(void) {
    while (atomic_load_explicit(&busy, memory_order_relaxed) == 1)
        ; // esperar
    // <-- aquí otro hilo puede ver busy == 0 también
    atomic_store_explicit(&busy, 1, memory_order_relaxed);
}

static void flag_unlock(void) {
    atomic_store_explicit(&busy, 0, memory_order_relaxed);
}

// --- Peterson ---
static atomic_int flag[2]; // flag[i] = 1: el hilo i quiere entrar
static atomic_int turn;    // a quién se le cede el paso

static void peterson_lock(int me) {
    int other = 1 - me;
    atomic_store_explicit(&flag[me], 1, memory_order_relaxed);
    atomic_store_explicit(&turn, other, memory_order_relaxed);
    if (mode == 2)
        // Ningún load de abajo puede adelantarse a los stores de arriba.
        atomic_thread_fence(memory_order_seq_cst);
    while (atomic_load_explicit(&flag[other], memory_order_acquire) == 1 &&
           atomic_load_explicit(&turn, memory_order_relaxed) == other)
        ; // esperar
}

static void peterson_unlock(int me) {
    atomic_store_explicit(&flag[me], 0, memory_order_release);
}

static void *worker(void *arg) {
    int me = (int)(long)arg;
    for (long i = 0; i < loops; i++) {
        if (mode == 0)
            flag_lock();
        else
            peterson_lock(me);
        counter = counter + 1; // sección crítica
        if (mode == 0)
            flag_unlock();
        else
            peterson_unlock(me);
    }
    return NULL;
}

int main(int argc, char *argv[]) {
    if (argc < 2) {
        fprintf(stderr, "uso: %s flag|peterson|fence [N]\n", argv[0]);
        return 1;
    }
    if (strcmp(argv[1], "flag") == 0)
        mode = 0;
    else if (strcmp(argv[1], "peterson") == 0)
        mode = 1;
    else if (strcmp(argv[1], "fence") == 0)
        mode = 2;
    else {
        fprintf(stderr, "modo desconocido: %s\n", argv[1]);
        return 1;
    }
    loops = (argc > 2) ? atol(argv[2]) : 1000000;

    pthread_t t0, t1;
    pthread_create(&t0, NULL, worker, (void *)0L);
    pthread_create(&t1, NULL, worker, (void *)1L);
    pthread_join(t0, NULL);
    pthread_join(t1, NULL);

    long expected = 2 * loops;
    printf("%s: perdidas %ld de %ld\n", argv[1], expected - counter,
           expected);
    return 0;
}
