/* 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 #include #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