084996f1d1
From-SVN: r169820
152 lines
3.8 KiB
C
152 lines
3.8 KiB
C
/* 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
|