summaryrefslogtreecommitdiff
path: root/libgo/runtime/go-semacquire.c
blob: 24c6a7388f6a2ed792e74773242299dd99e57003 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
/* 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