blob: 24c6a7388f6a2ed792e74773242299dd99e57003 [file] [log] [blame]
/* 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