v / thirdparty / libatomic_ops / atomic_ops.c
300 lines · 254 sloc · 8.57 KB · f53b5d737fd636890520101e736ad29e20d3c322
Raw
1/*
2 * Copyright (c) 2003-2011 Hewlett-Packard Development Company, L.P.
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 * SOFTWARE.
21 */
22
23/*
24 * Initialized data and out-of-line functions to support atomic_ops.h
25 * go here. Currently this is needed only for pthread-based atomics
26 * emulation, or for compare-and-swap emulation.
27 * Pthreads emulation isn't useful on a native Windows platform, and
28 * cas emulation is not needed. Thus we skip this on Windows.
29 */
30
31#if defined(HAVE_CONFIG_H)
32# include "config.h"
33#endif
34
35#if (defined(__hexagon__) || defined(__native_client__)) \
36 && !defined(AO_USE_NO_SIGNALS) && !defined(AO_USE_NANOSLEEP)
37 /* Hexagon QuRT does not have sigprocmask (but Hexagon does not need */
38 /* emulation, so it is OK not to bother about signals blocking). */
39 /* Since NaCl is not recognized by configure yet, we do it here. */
40# define AO_USE_NO_SIGNALS
41# define AO_USE_NANOSLEEP
42#endif
43
44#if defined(AO_USE_WIN32_PTHREADS) && !defined(AO_USE_NO_SIGNALS)
45# define AO_USE_NO_SIGNALS
46#endif
47
48#if (defined(__CYGWIN__) || defined(__GLIBC__) || defined(__GNU__) \
49 || defined(__linux__)) \
50 && !defined(AO_USE_NO_SIGNALS) && !defined(_GNU_SOURCE)
51# define _GNU_SOURCE 1
52#endif
53
54#ifndef AO_BUILD
55# define AO_BUILD
56#endif
57
58#undef AO_REQUIRE_CAS
59#include "atomic_ops.h" /* Without cas emulation! */
60
61#ifdef __cplusplus
62 extern "C" {
63#endif
64
65AO_API void AO_pause(int); /* defined below */
66
67#ifdef __cplusplus
68 } /* extern "C" */
69#endif
70
71#if !defined(_MSC_VER) && !defined(__MINGW32__) && !defined(__BORLANDC__) \
72 || defined(AO_USE_NO_SIGNALS)
73
74#ifndef AO_NO_PTHREADS
75# include <pthread.h>
76#endif
77
78#ifndef AO_USE_NO_SIGNALS
79# include <signal.h>
80#endif
81
82#ifdef AO_USE_NANOSLEEP
83 /* This requires _POSIX_TIMERS feature. */
84# include <sys/time.h>
85# include <time.h>
86#elif defined(AO_USE_WIN32_PTHREADS)
87# include <windows.h> /* for Sleep() */
88#elif defined(_HPUX_SOURCE)
89# include <sys/time.h>
90#else
91# include <sys/select.h>
92#endif
93
94#ifndef AO_HAVE_double_t
95# include "atomic_ops/sysdeps/standard_ao_double_t.h"
96#endif
97
98#ifdef __cplusplus
99 extern "C" {
100#endif
101
102AO_API AO_t AO_fetch_compare_and_swap_emulation(volatile AO_t *addr,
103 AO_t old_val, AO_t new_val);
104
105AO_API int
106AO_compare_double_and_swap_double_emulation(volatile AO_double_t *addr,
107 AO_t old_val1, AO_t old_val2,
108 AO_t new_val1, AO_t new_val2);
109
110AO_API void AO_store_full_emulation(volatile AO_t *addr, AO_t val);
111
112/* Lock for pthreads-based implementation. */
113#ifndef AO_NO_PTHREADS
114 AO_API pthread_mutex_t AO_pt_lock;
115#endif
116
117#ifdef __cplusplus
118 } /* extern "C" */
119#endif
120
121#ifndef AO_NO_PTHREADS
122 pthread_mutex_t AO_pt_lock = PTHREAD_MUTEX_INITIALIZER;
123#endif
124
125/*
126 * Out of line compare-and-swap emulation based on test and set.
127 *
128 * We use a small table of locks for different compare_and_swap locations.
129 * Before we update perform a compare-and-swap, we grab the corresponding
130 * lock. Different locations may hash to the same lock, but since we
131 * never acquire more than one lock at a time, this can't deadlock.
132 * We explicitly disable signals while we perform this operation.
133 *
134 * TODO: Probably also support emulation based on Lamport
135 * locks, since we may not have test_and_set either.
136 */
137#define AO_HASH_SIZE 16
138
139#define AO_HASH(x) ((unsigned)((AO_uintptr_t)(x) >> 12) & (AO_HASH_SIZE-1))
140
141static AO_TS_t AO_locks[AO_HASH_SIZE] = {
142 AO_TS_INITIALIZER, AO_TS_INITIALIZER, AO_TS_INITIALIZER, AO_TS_INITIALIZER,
143 AO_TS_INITIALIZER, AO_TS_INITIALIZER, AO_TS_INITIALIZER, AO_TS_INITIALIZER,
144 AO_TS_INITIALIZER, AO_TS_INITIALIZER, AO_TS_INITIALIZER, AO_TS_INITIALIZER,
145 AO_TS_INITIALIZER, AO_TS_INITIALIZER, AO_TS_INITIALIZER, AO_TS_INITIALIZER,
146};
147
148static void lock_ool(volatile AO_TS_t *l)
149{
150 int i = 0;
151
152 while (AO_test_and_set_acquire(l) == AO_TS_SET)
153 AO_pause(++i);
154}
155
156AO_INLINE void lock(volatile AO_TS_t *l)
157{
158 if (AO_EXPECT_FALSE(AO_test_and_set_acquire(l) == AO_TS_SET))
159 lock_ool(l);
160}
161
162AO_INLINE void unlock(volatile AO_TS_t *l)
163{
164 AO_CLEAR(l);
165}
166
167#ifndef AO_USE_NO_SIGNALS
168 static sigset_t all_sigs;
169 static volatile AO_t initialized = 0;
170 static volatile AO_TS_t init_lock = AO_TS_INITIALIZER;
171
172 AO_INLINE void block_all_signals(sigset_t *old_sigs_ptr)
173 {
174 if (AO_EXPECT_FALSE(!AO_load_acquire(&initialized)))
175 {
176 lock(&init_lock);
177 if (!initialized)
178 sigfillset(&all_sigs);
179 unlock(&init_lock);
180 AO_store_release(&initialized, 1);
181 }
182 sigprocmask(SIG_BLOCK, &all_sigs, old_sigs_ptr);
183 /* Neither sigprocmask nor pthread_sigmask is 100% */
184 /* guaranteed to work here. Sigprocmask is not */
185 /* guaranteed be thread safe, and pthread_sigmask */
186 /* is not async-signal-safe. Under linuxthreads, */
187 /* sigprocmask may block some pthreads-internal */
188 /* signals. So long as we do that for short periods, */
189 /* we should be OK. */
190 }
191#endif /* !AO_USE_NO_SIGNALS */
192
193AO_API AO_t AO_fetch_compare_and_swap_emulation(volatile AO_t *addr,
194 AO_t old_val, AO_t new_val)
195{
196 AO_TS_t *my_lock = AO_locks + AO_HASH(addr);
197 AO_t fetched_val;
198
199# ifndef AO_USE_NO_SIGNALS
200 sigset_t old_sigs;
201 block_all_signals(&old_sigs);
202# endif
203 lock(my_lock);
204 fetched_val = *addr;
205 if (fetched_val == old_val)
206 *addr = new_val;
207 unlock(my_lock);
208# ifndef AO_USE_NO_SIGNALS
209 sigprocmask(SIG_SETMASK, &old_sigs, NULL);
210# endif
211 return fetched_val;
212}
213
214AO_API int
215AO_compare_double_and_swap_double_emulation(volatile AO_double_t *addr,
216 AO_t old_val1, AO_t old_val2,
217 AO_t new_val1, AO_t new_val2)
218{
219 AO_TS_t *my_lock = AO_locks + AO_HASH(addr);
220 int result;
221
222# ifndef AO_USE_NO_SIGNALS
223 sigset_t old_sigs;
224 block_all_signals(&old_sigs);
225# endif
226 lock(my_lock);
227 if (addr -> AO_val1 == old_val1 && addr -> AO_val2 == old_val2)
228 {
229 addr -> AO_val1 = new_val1;
230 addr -> AO_val2 = new_val2;
231 result = 1;
232 }
233 else
234 result = 0;
235 unlock(my_lock);
236# ifndef AO_USE_NO_SIGNALS
237 sigprocmask(SIG_SETMASK, &old_sigs, NULL);
238# endif
239 return result;
240}
241
242AO_API void AO_store_full_emulation(volatile AO_t *addr, AO_t val)
243{
244 AO_TS_t *my_lock = AO_locks + AO_HASH(addr);
245 lock(my_lock);
246 *addr = val;
247 unlock(my_lock);
248}
249
250#else /* Non-posix platform */
251
252# include <windows.h>
253
254# define AO_USE_WIN32_PTHREADS
255 /* define to use Sleep() */
256
257 extern int AO_non_posix_implementation_is_entirely_in_headers;
258
259#endif
260
261static volatile AO_t spin_dummy = 0;
262
263/* Spin for 2**n units. */
264static void AO_spin(int n)
265{
266 AO_t j = AO_load(&spin_dummy);
267 int i = 2 << n;
268
269 while (i-- > 0)
270 j += j << 2;
271 /* Given spin_dummy is initialized to 0, j is 0 after the loop. */
272 AO_store(&spin_dummy, j);
273}
274
275AO_API void AO_pause(int n)
276{
277 if (n < 12) {
278 AO_spin(n);
279 } else {
280# ifdef AO_USE_NANOSLEEP
281 struct timespec ts;
282 ts.tv_sec = 0;
283 ts.tv_nsec = n > 28 ? 100000L * 1000 : 1L << (n - 2);
284 nanosleep(&ts, 0);
285# elif defined(AO_USE_WIN32_PTHREADS)
286 Sleep(n > 28 ? 100 /* millis */
287 : n < 22 ? 1 : (DWORD)1 << (n - 22));
288# else
289 struct timeval tv;
290 /* Short async-signal-safe sleep. */
291 int usec = n > 28 ? 100000 : 1 << (n - 12);
292 /* Use an intermediate variable (of int type) to avoid */
293 /* "shift followed by widening conversion" warning. */
294
295 tv.tv_sec = 0;
296 tv.tv_usec = usec;
297 (void)select(0, 0, 0, 0, &tv);
298# endif
299 }
300}
301