/* This program is a marginally modified copy of
   drd/tests/annotate_rwlock.c,

   which was originally written by Bart van Assche.

   Unfortunately due to the need to #include helgrind.h instead of
   drd.h, it can't be an exact copy.
*/

/**
 * @file  annotate_rwlock.c
 *
 * @brief Multithreaded test program that triggers various access patterns
 *        without triggering any race conditions using a reader-writer lock
 *        implemented via busy-waiting. Annotations are used to tell DRD
 *        which higher-level rwlock operations are being performed.
 */


#define _GNU_SOURCE 1

#include <assert.h>
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>        /* usleep() */
#include "../../config.h"
#include "../../helgrind/helgrind.h"


#ifndef HAVE_BUILTIN_ATOMIC
#error Sorry, but this test program can only be compiled by a compiler that\
has built-in functions for atomic memory access.
#endif


typedef struct {
  volatile int locked;
  int          writer_count;
  int          reader_count;
} rwlock_t;


static rwlock_t s_rwlock;
static int s_counter;


static void rwlock_init(rwlock_t* p)
{
  //  DRD_IGNORE_VAR(*p);
  p->locked       = 0;
  p->writer_count = 0;
  p->reader_count = 0;
  ANNOTATE_RWLOCK_CREATE(p);
}

static void rwlock_destroy(rwlock_t* p)
{
  ANNOTATE_RWLOCK_DESTROY(p);
  assert(p->locked       == 0);
  assert(p->writer_count == 0);
  assert(p->reader_count == 0);
}

static void rwlock_rdlock(rwlock_t* p)
{
  while (1)
  {
    while (__sync_val_compare_and_swap(&p->locked, 0, 1) == 1)
      ;
    if (p->writer_count == 0)
      break;
#ifdef __APPLE__
    /* Darwin doesn't have an implementation of pthread_yield(). */
    usleep(100 * 1000);
#else
    pthread_yield();
#endif
    (void) __sync_fetch_and_sub(&p->locked, 1);
  }
  p->reader_count++;
  assert(p->reader_count >= 0);
  assert(p->writer_count >= 0);
  assert(p->reader_count == 0 || p->writer_count == 0);
  (void) __sync_fetch_and_sub(&p->locked, 1);
  //ANNOTATE_READERLOCK_ACQUIRED(p);
  ANNOTATE_RWLOCK_ACQUIRED(p, 0);
}

static void rwlock_wrlock(rwlock_t* p)
{
  while (1)
  {
    while (__sync_val_compare_and_swap(&p->locked, 0, 1) == 1)
      ;
    if (p->reader_count == 0)
      break;
#ifdef __APPLE__
    /* Darwin doesn't have an implementation of pthread_yield(). */
    usleep(100 * 1000);
#else
    pthread_yield();
#endif
    (void) __sync_fetch_and_sub(&p->locked, 1);
  }
  p->writer_count++;
  assert(p->reader_count >= 0);
  assert(p->writer_count >= 0);
  assert(p->reader_count == 0 || p->writer_count == 0);
  (void) __sync_fetch_and_sub(&p->locked, 1);
  //  ANNOTATE_WRITERLOCK_ACQUIRED(p);
  ANNOTATE_RWLOCK_ACQUIRED(p, 1);
}

static void rwlock_unlock(rwlock_t* p)
{
  while (__sync_val_compare_and_swap(&p->locked, 0, 1) == 1)
    ;
  if (p->reader_count > 0)
  {
    p->reader_count--;
    //ANNOTATE_READERLOCK_RELEASED(p);
    ANNOTATE_RWLOCK_RELEASED(p, 0);
  }
  else
  {
    p->writer_count--;
    //ANNOTATE_WRITERLOCK_RELEASED(p);
    ANNOTATE_RWLOCK_RELEASED(p, 1);
  }
  assert(p->reader_count >= 0);
  assert(p->writer_count >= 0);
  assert(p->reader_count == 0 || p->writer_count == 0);
  (void) __sync_fetch_and_sub(&p->locked, 1);
}

static void* thread_func(void* arg)
{
  int i;
  int sum = 0;

  for (i = 0; i < 1000; i++)
  {
    rwlock_rdlock(&s_rwlock);
    sum += s_counter;
    rwlock_unlock(&s_rwlock);
    rwlock_wrlock(&s_rwlock);
    s_counter++;
    rwlock_unlock(&s_rwlock);
  }

  return 0;
}

int main(int argc, char** argv)
{
  const int thread_count = 10;
  pthread_t tid[thread_count];
  int i;

  rwlock_init(&s_rwlock);
  for (i = 0; i < thread_count; i++)
  {
    pthread_create(&tid[i], 0, thread_func, 0);
  }

  for (i = 0; i < thread_count; i++)
  {
    pthread_join(tid[i], 0);
  }
  rwlock_destroy(&s_rwlock);

  fprintf(stderr, "Finished.\n");

  return 0;
}