#include <sys/param.h>

#include <err.h>
#include <pthread.h>
#include <sched.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>

enum { N = 12 };

pthread_mutex_t mutex[N];
pthread_cond_t cond[N];
pthread_t sleeper[N/2];
pthread_t waker[N/2];
struct {
	volatile unsigned v;
} __aligned(COHERENCY_UNIT) ticker[N/2];

static void
lock(unsigned i)
{
	int error;

	error = pthread_mutex_lock(&mutex[i]);
	if (error)
		errc(1, error, "pthread_mutex_lock");
}

static void
unlock(unsigned i)
{
	int error;

	error = pthread_mutex_unlock(&mutex[i]);
	if (error)
		errc(1, error, "pthread_mutex_unlock");
}

static void
wait(unsigned i)
{
	int error;

	error = pthread_cond_wait(&cond[i], &mutex[i]);
	if (error)
		errc(1, error, "pthread_cond_wait");
}

static void
wake_one(unsigned i)
{
	int error;

	error = pthread_cond_signal(&cond[i]);
	if (error)
		errc(1, error, "pthread_cond_signal");
}

static void __unused
wake_all(unsigned i)
{
	int error;

	error = pthread_cond_broadcast(&cond[i]);
	if (error)
		errc(1, error, "pthread_cond_broadcast");
}

static void *
start_sleeper(void *cookie)
{
	unsigned t = (unsigned)(uintptr_t)cookie;
	unsigned i;

	for (i = 0;; i++, i %= N) {
		lock(i);
		wait(i);
		unlock(i);
		ticker[t].v++;
	}
	__unreachable();
}

static void *
start_waker(void *cookie)
{
	unsigned i;

	(void)cookie;

	for (i = 0;; i++, i %= N) {
		lock(i);
		wake_one(i);
		unlock(i);
	}
	__unreachable();
}

int
main(void)
{
	uint64_t c = 0;
	unsigned tickercache[N/2] = {0};
	unsigned i, tmp;
	int error;

	for (i = 0; i < N; i++) {
		error = pthread_mutex_init(&mutex[i], NULL);
		if (error)
			errc(1, error, "pthread_mutex_init");
		error = pthread_cond_init(&cond[i], NULL);
		if (error)
			errc(1, error, "pthread_cond_init");
	}

	for (i = 0; i < N/2; i++) {
		error = pthread_create(&sleeper[i], NULL, &start_sleeper,
		    (void *)(uintptr_t)i);
		if (error)
			errc(1, error ,"pthread_create sleeper");
		error = pthread_create(&waker[i], NULL, &start_waker,
		    NULL);
		if (error)
			errc(1, error ,"pthread_create waker");
	}

	setlinebuf(stdout);
	for (;;) {
		sleep(1);
		c = 0;
		for (i = 0; i < N/2; i++) {
			if ((tmp = ticker[i].v) != tickercache[i]) {
				c += (tmp - tickercache[i]);
				tickercache[i] = tmp;
			} else {
				printf("thread %u wedged\n", i);
				raise(SIGABRT);
			}
		}
		printf("%"PRIu64" wakeups\n", c);
	}
	return 0;
}