// The six custom contenders used by "When a sleeping spinlock beats an atomic".
// C23, reduced from the benchmark sources without changing the hot loops.

#define _GNU_SOURCE
#include <stdatomic.h>
#include <stdint.h>
#include <time.h>

#if defined(_MSC_VER)
#define NOINLINE __declspec(noinline)
#elif defined(__GNUC__) || defined(__clang__)
#define NOINLINE __attribute__((noinline))
#else
#error Unsupported compiler: define a noinline spelling for this target
#endif

static inline void cpu_pause(void) {
#if defined(__aarch64__)
  __asm__ volatile("yield" ::: "memory");
#elif defined(__x86_64__)
  __asm__ volatile("pause" ::: "memory");
#endif
}

static inline void wait_for_event(void) {
#if defined(__aarch64__)
  __asm__ volatile("wfe" ::: "memory");
#else
  cpu_pause();
#endif
}

static inline void signal_event(void) {
#if defined(__aarch64__)
  __asm__ volatile("sev" ::: "memory");
#endif
}

static inline void nanosleep_one(void) {
  const struct timespec ts = {.tv_sec = 0, .tv_nsec = 1};
  nanosleep(&ts, 0);
}

NOINLINE void fetch_add_increment(_Atomic uint64_t *counter) {
  atomic_fetch_add_explicit(counter, UINT64_C(1), memory_order_relaxed);
}

NOINLINE void weak_cas_increment(_Atomic uint64_t *counter) {
  uint64_t current = atomic_load_explicit(counter, memory_order_relaxed);
  while (!atomic_compare_exchange_weak_explicit(
      counter, &current, current + UINT64_C(1),
      memory_order_relaxed, memory_order_relaxed)) {
  }
}

NOINLINE void strong_cas_increment(_Atomic uint64_t *counter) {
  uint64_t current = atomic_load_explicit(counter, memory_order_relaxed);
  while (!atomic_compare_exchange_strong_explicit(
      counter, &current, current + UINT64_C(1),
      memory_order_relaxed, memory_order_relaxed)) {
  }
}

NOINLINE void backed_off_cas_increment(_Atomic uint64_t *counter) {
  uint64_t current = atomic_load_explicit(counter, memory_order_relaxed);
  while (!atomic_compare_exchange_strong_explicit(
      counter, &current, current + UINT64_C(1),
      memory_order_relaxed, memory_order_relaxed)) {
    nanosleep_one();
  }
}

NOINLINE void naive_tas_lock(_Atomic uint32_t *state) {
  while (atomic_exchange_explicit(
             state, UINT32_C(1), memory_order_acquire) != UINT32_C(0)) {
  }
}

NOINLINE void naive_tas_unlock(_Atomic uint32_t *state) {
  atomic_store_explicit(state, UINT32_C(0), memory_order_release);
}

NOINLINE void sleeping_lock(_Atomic uint32_t *state) {
  for (;;) {
    for (uint32_t probe = UINT32_C(0); probe < UINT32_C(8); ++probe) {
      if (atomic_load_explicit(
              state, memory_order_relaxed) == UINT32_C(0) &&
          atomic_exchange_explicit(
              state, UINT32_C(1), memory_order_acquire) == UINT32_C(0)) {
        return;
      }
    }
    nanosleep_one();
  }
}

NOINLINE void sleeping_unlock(_Atomic uint32_t *state) {
  atomic_store_explicit(state, UINT32_C(0), memory_order_release);
}

NOINLINE void event_lock(_Atomic uint32_t *state) {
  uint32_t spins = UINT32_C(0);
  for (;;) {
    if (atomic_exchange_explicit(
            state, UINT32_C(1), memory_order_acquire) == UINT32_C(0)) {
      return;
    }
    while (atomic_load_explicit(
               state, memory_order_relaxed) != UINT32_C(0)) {
      if (spins < UINT32_C(64)) {
        cpu_pause();
      } else {
        wait_for_event();
      }
      if (spins < UINT32_C(1024)) {
        ++spins;
      }
    }
  }
}

NOINLINE void event_unlock(_Atomic uint32_t *state) {
  atomic_store_explicit(state, UINT32_C(0), memory_order_release);
  signal_event();
}
