/* Test whether all data races are detected in a multithreaded program with
 * barriers.
 */


#define _GNU_SOURCE

/***********************/
/* Include directives. */
/***********************/

#include <assert.h>
#include <limits.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


/*********************/
/* Type definitions. */
/*********************/

struct threadinfo
{
  pthread_barrier_t* b;
  pthread_t          tid;
  int8_t*            array;
  int                iterations;
};


/********************/
/* Local variables. */
/********************/

static int s_silent;


/*************************/
/* Function definitions. */
/*************************/

/** Single thread, which touches p->iterations elements of array p->array.
 * Each modification of an element of p->array is a data race. */
static void* threadfunc(struct threadinfo* p)
{
  int i;
  int8_t* const array = p->array;
  pthread_barrier_t* const b = p->b;
  if (! s_silent)
    printf("thread %lx iteration 0\n", pthread_self());
  pthread_barrier_wait(b);
  for (i = 0; i < p->iterations; i++)
  {
    if (! s_silent)
      printf("thread %lx iteration %d; writing to %p\n",
             pthread_self(), i + 1, &array[i]);
    array[i] = i;
    pthread_barrier_wait(b);
  }
  return 0;
}

/** Actual test, consisting of nthread threads. */
static void barriers_and_races(const int nthread, const int iterations)
{
  int i, res;
  pthread_attr_t attr;
  struct threadinfo* t;
  pthread_barrier_t b;
  int8_t* array;

  t = malloc(nthread * sizeof(struct threadinfo));
  array = malloc(iterations * sizeof(array[0]));

  if (! s_silent)
    printf("&array[0] = %p\n", array);

  pthread_barrier_init(&b, 0, nthread);

  pthread_attr_init(&attr);
  res = pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN + 4096);
  assert(res == 0);

  for (i = 0; i < nthread; i++)
  {
    t[i].b = &b;
    t[i].array = array;
    t[i].iterations = iterations;
    res = pthread_create(&t[i].tid, &attr, (void*(*)(void*))threadfunc, &t[i]);
    if (res != 0) {
      fprintf(stderr, "Could not create thread #%d (of %d): %s\n",
              i, nthread, strerror(res));
      exit(1);
    }
  }

  pthread_attr_destroy(&attr);

  for (i = 0; i < nthread; i++)
  {
    pthread_join(t[i].tid, 0);
  }

  pthread_barrier_destroy(&b);

  free(array);
  free(t);
}

int main(int argc, char** argv)
{
  int nthread;
  int iterations;

  nthread    = (argc > 1) ? atoi(argv[1]) : 2;
  iterations = (argc > 2) ? atoi(argv[2]) : 3;
  s_silent   = (argc > 3) ? atoi(argv[3]) : 0;

  barriers_and_races(nthread, iterations);

  return 0;
}