/* go-semacquire.c -- implement runtime.Semacquire and runtime.Semrelease.

   Copyright 2009 The Go Authors. All rights reserved.
   Use of this source code is governed by a BSD-style
   license that can be found in the LICENSE file.  */

#include <stdint.h>

#include <pthread.h>

#include "go-assert.h"
#include "runtime.h"

/* We use a single global lock and condition variable.  This is
   painful, since it will cause unnecessary contention, but is hard to
   avoid in a portable manner.  On Linux we can use futexes, but they
   are unfortunately not exposed by libc and are thus also hard to use
   portably.  */

static pthread_mutex_t sem_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t sem_cond = PTHREAD_COND_INITIALIZER;

/* If the value in *ADDR is positive, and we are able to atomically
   decrement it, return true.  Otherwise do nothing and return
   false.  */

static _Bool
acquire (uint32 *addr)
{
  while (1)
    {
      uint32 val;

      val = *addr;
      if (val == 0)
	return 0;
      if (__sync_bool_compare_and_swap (addr, val, val - 1))
	return 1;
    }
}

/* Implement runtime.Semacquire.  ADDR points to a semaphore count.
   We have acquired the semaphore when we have decremented the count
   and it remains nonnegative.  */

void
semacquire (uint32 *addr)
{
  while (1)
    {
      int i;

      /* If the current count is positive, and we are able to atomically
	 decrement it, then we have acquired the semaphore.  */
      if (acquire (addr))
	return;

      /* Lock the mutex.  */
      i = pthread_mutex_lock (&sem_lock);
      __go_assert (i == 0);

      /* Check the count again with the mutex locked.  */
      if (acquire (addr))
	{
	  i = pthread_mutex_unlock (&sem_lock);
	  __go_assert (i == 0);
	  return;
	}

      /* The count is zero.  Even if a call to runtime.Semrelease
	 increments it to become positive, that call will try to
	 acquire the mutex and block, so we are sure to see the signal
	 of the condition variable.  */
      i = pthread_cond_wait (&sem_cond, &sem_lock);
      __go_assert (i == 0);

      /* Unlock the mutex and try again.  */
      i = pthread_mutex_unlock (&sem_lock);
      __go_assert (i == 0);
    }
}

/* Implement runtime.Semrelease.  ADDR points to a semaphore count.  We
   must atomically increment the count.  If the count becomes
   positive, we signal the condition variable to wake up another
   process.  */

void
semrelease (uint32 *addr)
{
  int32_t val;

  val = __sync_fetch_and_add (addr, 1);

  /* VAL is the old value.  It should never be negative.  If it is
     negative, that implies that Semacquire somehow decremented a zero
     value, or that the count has overflowed.  */
  __go_assert (val >= 0);

  /* If the old value was zero, then we have now released a count, and
     we signal the condition variable.  If the old value was positive,
     then nobody can be waiting.  We have to use
     pthread_cond_broadcast, not pthread_cond_signal, because
     otherwise there would be a race condition when the count is
     incremented twice before any locker manages to decrement it.  */
  if (val == 0)
    {
      int i;

      i = pthread_mutex_lock (&sem_lock);
      __go_assert (i == 0);

      i = pthread_cond_broadcast (&sem_cond);
      __go_assert (i == 0);

      i = pthread_mutex_unlock (&sem_lock);
      __go_assert (i == 0);
    }
}


#ifndef HAVE_SYNC_FETCH_AND_ADD_4

/* For targets which don't have the required sync support.  Really
   this should be provided by gcc itself.  FIXME.  */

static pthread_mutex_t sync_lock = PTHREAD_MUTEX_INITIALIZER;

uint32
__sync_fetch_and_add_4(uint32*, uint32)
  __attribute__((visibility("hidden")));

uint32
__sync_fetch_and_add_4(uint32* ptr, uint32 add)
{
  int i;
  uint32 ret;

  i = pthread_mutex_lock(&sync_lock);
  __go_assert(i == 0);

  ret = *ptr;
  *ptr += add;

  i = pthread_mutex_unlock(&sync_lock);
  __go_assert(i == 0);

  return ret;
}

#endif