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