// This defines a "shared object" that contains a mutex and a data field that is
// protected by the mutex.  The implementation of the mutex is selected by USE_*
// macros.


#include <iostream>

#ifdef USE_PTHREADS
#include <pthread.h>
#endif

#ifdef USE_YIELD
#include <sched.h>
#endif

#include "atomic.hh"


// For the futex implementation, the lock and unlock methods have a fast inline path for the 
// non-contended case and a slow non-inlined path ("body")for the contended case.  To ensure 
// that this non-inline path doesn't get inlined too, it's in another file:

#if defined(USE_FUTEX)
void futex_lock_body(int* futexp, int c);
void futex_unlock_body(int* futexp);
#endif



struct sharedobj {

#if defined(USE_NO_LOCKING)
// Does no locking at all!  Gives the wrong answer if there is more than one thread.

  inline sharedobj(): data(0) {}

  inline void lock() {
  }

  inline void unlock() {
  }


#elif defined(USE_PTHREADS)
// Use the standard pthreads mutex functions.

  pthread_mutex_t mutex;

  inline sharedobj(): data(0) {
    pthread_mutex_init(&mutex, NULL);
  }

  inline ~sharedobj() {
    pthread_mutex_destroy(&mutex);
  }

  inline void lock() {
    pthread_mutex_lock(&mutex);
  }

  inline void unlock() {
    pthread_mutex_unlock(&mutex);
  }


#elif defined(USE_SPINLOCK)
// Lock using a spinlock, with an atomic instruction instruction to test-and-set.

  int mutex;

  inline sharedobj(): mutex(0), data(0) {}

  inline void lock() {
    while (simple_atomic_swap(mutex,1)) {}
    // the atomic instruction guaratees a barrier here.
  }

  inline void unlock() {
    barrier();  // need an explicit barrier because none is implied by the next line.
    mutex = 0;
  }


#elif defined(USE_YIELD)
// Loop as in USE_SPINLOCK, but call sched_yield if it's locked.
// The disadvantage of this compared to the pthreads or futex methods is that the
// kernel doesn't have a good idea which thread to wake up.
// Same barrier issues as above.

  int mutex;

  inline sharedobj(): mutex(0), data(0) {}

  inline void lock() {
    while (simple_atomic_swap(mutex,1)) {
      sched_yield();
    }
  }

  inline void unlock() {
    barrier();
    mutex = 0;
  }


#elif defined(USE_FUTEX)
// This is an attempt at the algorithm called "mutex3" in Ullrich Drepper's paper
// "Futexes are Tricky".  I do hope that I have not missed any subtleties.
// The common uncontended path is inline here, with the contended path out-of-line.

  int futex;

  inline sharedobj(): futex(0), data(0) {}

  inline void lock() {
    int c;
    if ((c=atomic_compare_and_swap(futex,0,1)) != 0) {
      futex_lock_body(&futex,c);
    }
  }

  inline void unlock() {
    if (atomic_post_dec(futex) != 1) {
      futex_unlock_body(&futex);
    }
  }

#endif


  // The data that's protected by the mutex:
  int data;

};

