Merge pull request #5714 from alexischr/update_bockbuild
[mono.git] / mono / utils / atomic.h
1 /**
2  * \file
3  * Atomic operations
4  *
5  * Author:
6  *      Dick Porter (dick@ximian.com)
7  *
8  * (C) 2002 Ximian, Inc.
9  * Copyright 2012 Xamarin Inc
10  * Licensed under the MIT license. See LICENSE file in the project root for full license information.
11  */
12
13 #ifndef _WAPI_ATOMIC_H_
14 #define _WAPI_ATOMIC_H_
15
16 #include "config.h"
17 #include <glib.h>
18 #include <mono/utils/mono-membar.h>
19
20 /*
21 The current Nexus 7 arm-v7a fails with:
22 F/MonoDroid( 1568): shared runtime initialization error: Cannot load library: reloc_library[1285]:    37 cannot locate '__sync_val_compare_and_swap_8'
23
24 Apple targets have historically being problematic, xcode 4.6 would miscompile the intrinsic.
25 */
26
27 /* On Windows, we always use the functions provided by the Windows API. */
28 #if defined(__WIN32__) || defined(_WIN32)
29
30 #ifndef WIN32_LEAN_AND_MEAN
31 #define WIN32_LEAN_AND_MEAN
32 #endif
33 #include <windows.h>
34
35 /* mingw is missing InterlockedCompareExchange64 () from winbase.h */
36 #if HAVE_DECL_INTERLOCKEDCOMPAREEXCHANGE64==0
37 static inline gint64 InterlockedCompareExchange64(volatile gint64 *dest, gint64 exch, gint64 comp)
38 {
39         return __sync_val_compare_and_swap (dest, comp, exch);
40 }
41 #endif
42
43 /* mingw is missing InterlockedExchange64 () from winbase.h */
44 #if HAVE_DECL_INTERLOCKEDEXCHANGE64==0
45 static inline gint64 InterlockedExchange64(volatile gint64 *val, gint64 new_val)
46 {
47         gint64 old_val;
48         do {
49                 old_val = *val;
50         } while (InterlockedCompareExchange64 (val, new_val, old_val) != old_val);
51         return old_val;
52 }
53 #endif
54
55 /* mingw is missing InterlockedIncrement64 () from winbase.h */
56 #if HAVE_DECL_INTERLOCKEDINCREMENT64==0
57 static inline gint64 InterlockedIncrement64(volatile gint64 *val)
58 {
59         return __sync_add_and_fetch (val, 1);
60 }
61 #endif
62
63 /* mingw is missing InterlockedDecrement64 () from winbase.h */
64 #if HAVE_DECL_INTERLOCKEDDECREMENT64==0
65 static inline gint64 InterlockedDecrement64(volatile gint64 *val)
66 {
67         return __sync_sub_and_fetch (val, 1);
68 }
69 #endif
70
71 /* mingw is missing InterlockedAdd () from winbase.h */
72 #if HAVE_DECL_INTERLOCKEDADD==0
73 static inline gint32 InterlockedAdd(volatile gint32 *dest, gint32 add)
74 {
75         return __sync_add_and_fetch (dest, add);
76 }
77 #endif
78
79 /* mingw is missing InterlockedAdd64 () from winbase.h */
80 #if HAVE_DECL_INTERLOCKEDADD64==0
81 static inline gint64 InterlockedAdd64(volatile gint64 *dest, gint64 add)
82 {
83         return __sync_add_and_fetch (dest, add);
84 }
85 #endif
86
87 #if defined(_MSC_VER) && !defined(InterlockedAdd)
88 /* MSVC before 2013 only defines InterlockedAdd* for the Itanium architecture */
89 static inline gint32 InterlockedAdd(volatile gint32 *dest, gint32 add)
90 {
91         return InterlockedExchangeAdd (dest, add) + add;
92 }
93 #endif
94
95 #if defined(_MSC_VER) && !defined(InterlockedAdd64)
96 #if defined(InterlockedExchangeAdd64)
97 /* This may be defined only on amd64 */
98 static inline gint64 InterlockedAdd64(volatile gint64 *dest, gint64 add)
99 {
100         return InterlockedExchangeAdd64 (dest, add) + add;
101 }
102 #else
103 static inline gint64 InterlockedAdd64(volatile gint64 *dest, gint64 add)
104 {
105         gint64 prev_value;
106
107         do {
108                 prev_value = *dest;
109         } while (prev_value != InterlockedCompareExchange64(dest, prev_value + add, prev_value));
110
111         return prev_value + add;
112 }
113 #endif
114 #endif
115
116 #ifdef HOST_WIN32
117 #define TO_INTERLOCKED_ARGP(ptr) ((volatile LONG*)(ptr))
118 #else
119 #define TO_INTERLOCKED_ARGP(ptr) (ptr)
120 #endif
121
122 /* And now for some dirty hacks... The Windows API doesn't
123  * provide any useful primitives for this (other than getting
124  * into architecture-specific madness), so use CAS. */
125
126 static inline gint32 InterlockedRead(volatile gint32 *src)
127 {
128         return InterlockedCompareExchange (TO_INTERLOCKED_ARGP (src), 0, 0);
129 }
130
131 static inline gint64 InterlockedRead64(volatile gint64 *src)
132 {
133         return InterlockedCompareExchange64 (src, 0, 0);
134 }
135
136 static inline gpointer InterlockedReadPointer(volatile gpointer *src)
137 {
138         return InterlockedCompareExchangePointer (src, NULL, NULL);
139 }
140
141 static inline void InterlockedWrite(volatile gint32 *dst, gint32 val)
142 {
143         InterlockedExchange (TO_INTERLOCKED_ARGP (dst), val);
144 }
145
146 static inline void InterlockedWrite64(volatile gint64 *dst, gint64 val)
147 {
148         InterlockedExchange64 (dst, val);
149 }
150
151 static inline void InterlockedWritePointer(volatile gpointer *dst, gpointer val)
152 {
153         InterlockedExchangePointer (dst, val);
154 }
155
156 /* We can't even use CAS for these, so write them out
157  * explicitly according to x86(_64) semantics... */
158
159 static inline gint8 InterlockedRead8(volatile gint8 *src)
160 {
161         return *src;
162 }
163
164 static inline gint16 InterlockedRead16(volatile gint16 *src)
165 {
166         return *src;
167 }
168
169 static inline void InterlockedWrite8(volatile gint8 *dst, gint8 val)
170 {
171         *dst = val;
172         mono_memory_barrier ();
173 }
174
175 static inline void InterlockedWrite16(volatile gint16 *dst, gint16 val)
176 {
177         *dst = val;
178         mono_memory_barrier ();
179 }
180
181 /* Prefer GCC atomic ops if the target supports it (see configure.ac). */
182 #elif defined(USE_GCC_ATOMIC_OPS)
183
184 /*
185  * As of this comment (August 2016), all current Clang versions get atomic
186  * intrinsics on ARM64 wrong. All GCC versions prior to 5.3.0 do, too. The bug
187  * is the same: The compiler developers thought that the acq + rel barriers
188  * that ARM64 load/store instructions can impose are sufficient to provide
189  * sequential consistency semantics. This is not the case:
190  *
191  *     http://lists.infradead.org/pipermail/linux-arm-kernel/2014-February/229588.html
192  *
193  * We work around this bug by inserting full barriers around each atomic
194  * intrinsic if we detect that we're built with a buggy compiler.
195  */
196
197 #if defined (HOST_ARM64) && (defined (__clang__) || MONO_GNUC_VERSION < 50300)
198 #define WRAP_ATOMIC_INTRINSIC(INTRIN) \
199         ({ \
200                 mono_memory_barrier (); \
201                 __typeof__ (INTRIN) atomic_ret__ = (INTRIN); \
202                 mono_memory_barrier (); \
203                 atomic_ret__; \
204         })
205
206 #define gcc_sync_val_compare_and_swap(a, b, c) WRAP_ATOMIC_INTRINSIC (__sync_val_compare_and_swap (a, b, c))
207 #define gcc_sync_add_and_fetch(a, b) WRAP_ATOMIC_INTRINSIC (__sync_add_and_fetch (a, b))
208 #define gcc_sync_sub_and_fetch(a, b) WRAP_ATOMIC_INTRINSIC (__sync_sub_and_fetch (a, b))
209 #define gcc_sync_fetch_and_add(a, b) WRAP_ATOMIC_INTRINSIC (__sync_fetch_and_add (a, b))
210 #else
211 #define gcc_sync_val_compare_and_swap(a, b, c) __sync_val_compare_and_swap (a, b, c)
212 #define gcc_sync_add_and_fetch(a, b) __sync_add_and_fetch (a, b)
213 #define gcc_sync_sub_and_fetch(a, b) __sync_sub_and_fetch (a, b)
214 #define gcc_sync_fetch_and_add(a, b) __sync_fetch_and_add (a, b)
215 #endif
216
217 static inline gint32 InterlockedCompareExchange(volatile gint32 *dest,
218                                                 gint32 exch, gint32 comp)
219 {
220         return gcc_sync_val_compare_and_swap (dest, comp, exch);
221 }
222
223 static inline gpointer InterlockedCompareExchangePointer(volatile gpointer *dest, gpointer exch, gpointer comp)
224 {
225         return gcc_sync_val_compare_and_swap (dest, comp, exch);
226 }
227
228 static inline gint32 InterlockedAdd(volatile gint32 *dest, gint32 add)
229 {
230         return gcc_sync_add_and_fetch (dest, add);
231 }
232
233 static inline gint32 InterlockedIncrement(volatile gint32 *val)
234 {
235         return gcc_sync_add_and_fetch (val, 1);
236 }
237
238 static inline gint32 InterlockedDecrement(volatile gint32 *val)
239 {
240         return gcc_sync_sub_and_fetch (val, 1);
241 }
242
243 static inline gint32 InterlockedExchange(volatile gint32 *val, gint32 new_val)
244 {
245         gint32 old_val;
246         do {
247                 old_val = *val;
248         } while (gcc_sync_val_compare_and_swap (val, old_val, new_val) != old_val);
249         return old_val;
250 }
251
252 static inline gpointer InterlockedExchangePointer(volatile gpointer *val,
253                                                   gpointer new_val)
254 {
255         gpointer old_val;
256         do {
257                 old_val = *val;
258         } while (gcc_sync_val_compare_and_swap (val, old_val, new_val) != old_val);
259         return old_val;
260 }
261
262 static inline gint32 InterlockedExchangeAdd(volatile gint32 *val, gint32 add)
263 {
264         return gcc_sync_fetch_and_add (val, add);
265 }
266
267 static inline gint8 InterlockedRead8(volatile gint8 *src)
268 {
269         /* Kind of a hack, but GCC doesn't give us anything better, and it's
270          * certainly not as bad as using a CAS loop. */
271         return gcc_sync_fetch_and_add (src, 0);
272 }
273
274 static inline gint16 InterlockedRead16(volatile gint16 *src)
275 {
276         return gcc_sync_fetch_and_add (src, 0);
277 }
278
279 static inline gint32 InterlockedRead(volatile gint32 *src)
280 {
281         return gcc_sync_fetch_and_add (src, 0);
282 }
283
284 static inline void InterlockedWrite8(volatile gint8 *dst, gint8 val)
285 {
286         /* Nothing useful from GCC at all, so fall back to CAS. */
287         gint8 old_val;
288         do {
289                 old_val = *dst;
290         } while (gcc_sync_val_compare_and_swap (dst, old_val, val) != old_val);
291 }
292
293 static inline void InterlockedWrite16(volatile gint16 *dst, gint16 val)
294 {
295         gint16 old_val;
296         do {
297                 old_val = *dst;
298         } while (gcc_sync_val_compare_and_swap (dst, old_val, val) != old_val);
299 }
300
301 static inline void InterlockedWrite(volatile gint32 *dst, gint32 val)
302 {
303         /* Nothing useful from GCC at all, so fall back to CAS. */
304         gint32 old_val;
305         do {
306                 old_val = *dst;
307         } while (gcc_sync_val_compare_and_swap (dst, old_val, val) != old_val);
308 }
309
310 #if defined (TARGET_OSX) || defined (__arm__) || (defined (__mips__) && !defined (__mips64)) || (defined (__powerpc__) && !defined (__powerpc64__)) || (defined (__sparc__) && !defined (__arch64__))
311 #define BROKEN_64BIT_ATOMICS_INTRINSIC 1
312 #endif
313
314 #if !defined (BROKEN_64BIT_ATOMICS_INTRINSIC)
315
316 static inline gint64 InterlockedCompareExchange64(volatile gint64 *dest, gint64 exch, gint64 comp)
317 {
318         return gcc_sync_val_compare_and_swap (dest, comp, exch);
319 }
320
321 static inline gint64 InterlockedAdd64(volatile gint64 *dest, gint64 add)
322 {
323         return gcc_sync_add_and_fetch (dest, add);
324 }
325
326 static inline gint64 InterlockedIncrement64(volatile gint64 *val)
327 {
328         return gcc_sync_add_and_fetch (val, 1);
329 }
330
331 static inline gint64 InterlockedDecrement64(volatile gint64 *val)
332 {
333         return gcc_sync_sub_and_fetch (val, 1);
334 }
335
336 static inline gint64 InterlockedExchangeAdd64(volatile gint64 *val, gint64 add)
337 {
338         return gcc_sync_fetch_and_add (val, add);
339 }
340
341 static inline gint64 InterlockedRead64(volatile gint64 *src)
342 {
343         /* Kind of a hack, but GCC doesn't give us anything better. */
344         return gcc_sync_fetch_and_add (src, 0);
345 }
346
347 #else
348
349 /* Implement 64-bit cmpxchg by hand or emulate it. */
350 extern gint64 InterlockedCompareExchange64(volatile gint64 *dest, gint64 exch, gint64 comp);
351
352 /* Implement all other 64-bit atomics in terms of a specialized CAS
353  * in this case, since chances are that the other 64-bit atomic
354  * intrinsics are broken too.
355  */
356
357 static inline gint64 InterlockedExchangeAdd64(volatile gint64 *dest, gint64 add)
358 {
359         gint64 old_val;
360         do {
361                 old_val = *dest;
362         } while (InterlockedCompareExchange64 (dest, old_val + add, old_val) != old_val);
363         return old_val;
364 }
365
366 static inline gint64 InterlockedIncrement64(volatile gint64 *val)
367 {
368         gint64 get, set;
369         do {
370                 get = *val;
371                 set = get + 1;
372         } while (InterlockedCompareExchange64 (val, set, get) != get);
373         return set;
374 }
375
376 static inline gint64 InterlockedDecrement64(volatile gint64 *val)
377 {
378         gint64 get, set;
379         do {
380                 get = *val;
381                 set = get - 1;
382         } while (InterlockedCompareExchange64 (val, set, get) != get);
383         return set;
384 }
385
386 static inline gint64 InterlockedAdd64(volatile gint64 *dest, gint64 add)
387 {
388         gint64 get, set;
389         do {
390                 get = *dest;
391                 set = get + add;
392         } while (InterlockedCompareExchange64 (dest, set, get) != get);
393         return set;
394 }
395
396 static inline gint64 InterlockedRead64(volatile gint64 *src)
397 {
398         return InterlockedCompareExchange64 (src, 0, 0);
399 }
400
401 #endif
402
403 static inline gpointer InterlockedReadPointer(volatile gpointer *src)
404 {
405         return InterlockedCompareExchangePointer (src, NULL, NULL);
406 }
407
408 static inline void InterlockedWritePointer(volatile gpointer *dst, gpointer val)
409 {
410         InterlockedExchangePointer (dst, val);
411 }
412
413 /* We always implement this in terms of a 64-bit cmpxchg since
414  * GCC doesn't have an intrisic to model it anyway. */
415 static inline gint64 InterlockedExchange64(volatile gint64 *val, gint64 new_val)
416 {
417         gint64 old_val;
418         do {
419                 old_val = *val;
420         } while (InterlockedCompareExchange64 (val, new_val, old_val) != old_val);
421         return old_val;
422 }
423
424 static inline void InterlockedWrite64(volatile gint64 *dst, gint64 val)
425 {
426         /* Nothing useful from GCC at all, so fall back to CAS. */
427         InterlockedExchange64 (dst, val);
428 }
429
430 #else
431
432 #define WAPI_NO_ATOMIC_ASM
433
434 extern gint32 InterlockedCompareExchange(volatile gint32 *dest, gint32 exch, gint32 comp);
435 extern gint64 InterlockedCompareExchange64(volatile gint64 *dest, gint64 exch, gint64 comp);
436 extern gpointer InterlockedCompareExchangePointer(volatile gpointer *dest, gpointer exch, gpointer comp);
437 extern gint32 InterlockedAdd(volatile gint32 *dest, gint32 add);
438 extern gint64 InterlockedAdd64(volatile gint64 *dest, gint64 add);
439 extern gint32 InterlockedIncrement(volatile gint32 *dest);
440 extern gint64 InterlockedIncrement64(volatile gint64 *dest);
441 extern gint32 InterlockedDecrement(volatile gint32 *dest);
442 extern gint64 InterlockedDecrement64(volatile gint64 *dest);
443 extern gint32 InterlockedExchange(volatile gint32 *dest, gint32 exch);
444 extern gint64 InterlockedExchange64(volatile gint64 *dest, gint64 exch);
445 extern gpointer InterlockedExchangePointer(volatile gpointer *dest, gpointer exch);
446 extern gint32 InterlockedExchangeAdd(volatile gint32 *dest, gint32 add);
447 extern gint64 InterlockedExchangeAdd64(volatile gint64 *dest, gint64 add);
448 extern gint8 InterlockedRead8(volatile gint8 *src);
449 extern gint16 InterlockedRead16(volatile gint16 *src);
450 extern gint32 InterlockedRead(volatile gint32 *src);
451 extern gint64 InterlockedRead64(volatile gint64 *src);
452 extern gpointer InterlockedReadPointer(volatile gpointer *src);
453 extern void InterlockedWrite8(volatile gint8 *dst, gint8 val);
454 extern void InterlockedWrite16(volatile gint16 *dst, gint16 val);
455 extern void InterlockedWrite(volatile gint32 *dst, gint32 val);
456 extern void InterlockedWrite64(volatile gint64 *dst, gint64 val);
457 extern void InterlockedWritePointer(volatile gpointer *dst, gpointer val);
458
459 #endif
460
461 #if SIZEOF_VOID_P == 4
462 #define InterlockedAddP(p,add) InterlockedAdd ((volatile gint32*)p, (gint32)add)
463 #else
464 #define InterlockedAddP(p,add) InterlockedAdd64 ((volatile gint64*)p, (gint64)add)
465 #endif
466
467 /* The following functions cannot be found on any platform, and thus they can be declared without further existence checks */
468
469 static inline void
470 InterlockedWriteBool (volatile gboolean *dest, gboolean val)
471 {
472         /* both, gboolean and gint32, are int32_t; the purpose of these casts is to make things explicit */
473         InterlockedWrite ((volatile gint32 *)dest, (gint32)val);
474 }
475
476 #endif /* _WAPI_ATOMIC_H_ */