/* -*- c-file-style: "GNU" -*- */
/*
 * Copyright (C) CNRS, INRIA, Universite Bordeaux 1, Telecom SudParis
 * See COPYING in top-level directory.
 */

#ifndef EZTRACE_SPINLOCK_H
#define EZTRACE_SPINLOCK_H

#include <stdatomic.h>
#include <sched.h>
#include <unistd.h>

typedef volatile atomic_flag ezt_spinlock;

#ifdef __NVCOMPILER
/* When compiling with NVCC, the compiler complains of the atomic_flag type and we need to cast variables to void*
 * However, other compilers (eg clang) do complain when casting to void*
 * -> We need two different cast depending on the compiler
 */
#define ATOMIC_FLAG_CAST(ptr) (void*)ptr
#else
#define ATOMIC_FLAG_CAST(ptr) ptr
#endif

#define ezt_spin_unlocked ATOMIC_FLAG_INIT

#if defined(__x86_64__)
#include <immintrin.h>
#define PAUSE  _mm_pause()
#elif defined(__aarch64__) || defined(__arm64__)
#define PAUSE __asm__ __volatile__ ("yield")
#else
#define PAUSE
#endif


static void ezt_spin_init(ezt_spinlock *l) __attribute__((unused));
static void ezt_spin_init(ezt_spinlock *l) {
  atomic_flag_clear(ATOMIC_FLAG_CAST(l));
}

static void ezt_spin_lock(ezt_spinlock *l) __attribute__((unused));
static void ezt_spin_lock(ezt_spinlock *l) {
  uint64_t count=0;
  while(atomic_flag_test_and_set(ATOMIC_FLAG_CAST(l))) {
    PAUSE;
    if(count++ > 100)
      // we've been waiting for a while, let other thread work
      sched_yield();
    else if(count++ > 1000)
      // wow we really have trouble getting the lock. sleep for a while to decrease contention
      usleep(10);
  }
}

static void ezt_spin_unlock(ezt_spinlock *l) __attribute__((unused));
static void ezt_spin_unlock(ezt_spinlock *l) {
  atomic_flag_clear(ATOMIC_FLAG_CAST(l));
}


#endif	/* EZTRACE_SPINLOCK_H */
