Skip to content

Commit c48aa81

Browse files
authored
[mono] Second try at using C11 atomics (#91808)
* For each platform decide if we will use C11 standard atomics, Win32 API atomics, GCC atomic intrinsics or emulated atomics. To use C11 atomics we generally want them to be lock-free for the primitive types we care about (that is, `ATOMIC_LONG_LONG_LOCK_FREE == 2` not `1` and similarly for other macros) because otherwise we cannot be sure if the atomic might use a global lock in which case we could have thread suspend problems if both the GC and the rest of the runtime use an atomic at the same time. * On win32, use the win32 atomics until MSVC atomics support is not experimental, or we update our build to pass `/experimental:c11atomics`; and also until we build our C++ code with C++23 or later (otherwise MSVC will complain about including stdatomic.h) * If the header gets included while we're generating offsets using `offsets-tool.py`, pretend we're using emulated atomics. On some Linux configurations, the libclang that we use ends up picking up the platform `atomic.h` header, not the clang one, and then errors out on their underlying implementation. * Replace the use of `bool` in some macros in Mono - it will expand to `_Bool` in C11 and mess things up. Use `boolean` instead Fixes #91779
1 parent e2c319a commit c48aa81

File tree

5 files changed

+291
-14
lines changed

5 files changed

+291
-14
lines changed

src/mono/mono/metadata/icall-signatures.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
// mono_icall_sig_void_int32
5858
// mono_icall_sig_void_object
5959
// mono_icall_sig_void_ptr
60-
// mono_icall_sig_bool_ptr_ptrref
60+
// mono_icall_sig_boolean_ptr_ptrref
6161
// mono_icall_sig_double_double_double
6262
// mono_icall_sig_float_float_float
6363
// mono_icall_sig_int_obj_ptr
@@ -94,7 +94,7 @@
9494
// mono_icall_sig_void_ptr_ptr
9595
// mono_icall_sig_void_ptr_ptrref
9696
// mono_icall_sig_void_uint32_ptrref
97-
// mono_icall_sig_bool_ptr_int32_ptrref
97+
// mono_icall_sig_boolean_ptr_int32_ptrref
9898
// mono_icall_sig_int32_int32_ptr_ptrref
9999
// mono_icall_sig_int32_ptr_int32_ptr
100100
// mono_icall_sig_int32_ptr_int32_ptrref
@@ -182,7 +182,7 @@ ICALL_SIG (2, (void, int)) \
182182
ICALL_SIG (2, (void, int32)) \
183183
ICALL_SIG (2, (void, object)) \
184184
ICALL_SIG (2, (void, ptr)) \
185-
ICALL_SIG (3, (bool, ptr, ptrref)) \
185+
ICALL_SIG (3, (boolean, ptr, ptrref)) \
186186
ICALL_SIG (3, (double, double, double)) \
187187
ICALL_SIG (3, (float, float, float)) \
188188
ICALL_SIG (3, (int, obj, ptr)) \
@@ -222,7 +222,7 @@ ICALL_SIG (3, (void, ptr, object)) \
222222
ICALL_SIG (3, (void, ptr, ptr)) \
223223
ICALL_SIG (3, (void, ptr, ptrref)) \
224224
ICALL_SIG (3, (void, uint32, ptrref)) \
225-
ICALL_SIG (4, (bool, ptr, int32, ptrref)) \
225+
ICALL_SIG (4, (boolean, ptr, int32, ptrref)) \
226226
ICALL_SIG (4, (int32, int32, ptr, ptrref)) \
227227
ICALL_SIG (4, (int32, ptr, int32, ptr)) \
228228
ICALL_SIG (4, (int32, ptr, int32, ptrref)) \

src/mono/mono/metadata/icall.c

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7186,8 +7186,7 @@ mono_lookup_icall_symbol (MonoMethod *m)
71867186
//
71877187
// mono_create_icall_signatures depends on this order. Handle with care.
71887188
typedef enum ICallSigType {
7189-
ICALL_SIG_TYPE_bool = 0x00,
7190-
ICALL_SIG_TYPE_boolean = ICALL_SIG_TYPE_bool,
7189+
ICALL_SIG_TYPE_boolean = 0x00,
71917190
ICALL_SIG_TYPE_double = 0x01,
71927191
ICALL_SIG_TYPE_float = 0x02,
71937192
ICALL_SIG_TYPE_int = 0x03,
@@ -7265,7 +7264,7 @@ mono_create_icall_signatures (void)
72657264
typedef gsize G_MAY_ALIAS gsize_a;
72667265

72677266
MonoType * const lookup [ ] = {
7268-
m_class_get_byval_arg (mono_defaults.boolean_class), // ICALL_SIG_TYPE_bool
7267+
m_class_get_byval_arg (mono_defaults.boolean_class), // ICALL_SIG_TYPE_boolean
72697268
m_class_get_byval_arg (mono_defaults.double_class), // ICALL_SIG_TYPE_double
72707269
m_class_get_byval_arg (mono_defaults.single_class), // ICALL_SIG_TYPE_float
72717270
m_class_get_byval_arg (mono_defaults.int32_class), // ICALL_SIG_TYPE_int

src/mono/mono/mini/aot-compiler.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14612,8 +14612,8 @@ add_preinit_got_slots (MonoAotCompile *acfg)
1461214612

1461314613
#ifndef MONO_ARCH_HAVE_INTERP_ENTRY_TRAMPOLINE
1461414614
static MonoMethodSignature * const * const interp_in_static_sigs [] = {
14615-
&mono_icall_sig_bool_ptr_int32_ptrref,
14616-
&mono_icall_sig_bool_ptr_ptrref,
14615+
&mono_icall_sig_boolean_ptr_int32_ptrref,
14616+
&mono_icall_sig_boolean_ptr_ptrref,
1461714617
&mono_icall_sig_int32_int32_ptrref,
1461814618
&mono_icall_sig_int32_int32_ptr_ptrref,
1461914619
&mono_icall_sig_int32_ptr_int32_ptr,

src/mono/mono/tools/offsets-tool/offsets-tool.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,9 @@ def require_emscipten_path (args):
9494
self.target_args += ["-target", args.abi]
9595
else:
9696
require_emscipten_path (args)
97-
self.sys_includes = [args.emscripten_path + "/system/include", args.emscripten_path + "/system/include/libc", args.emscripten_path + "/system/lib/libc/musl/arch/emscripten", args.emscripten_path + "/system/lib/libc/musl/include", args.emscripten_path + "/system/lib/libc/musl/arch/generic"]
97+
clang_path = os.path.dirname(args.libclang)
98+
self.sys_includes = [args.emscripten_path + "/system/include", args.emscripten_path + "/system/include/libc", args.emscripten_path + "/system/lib/libc/musl/arch/emscripten", args.emscripten_path + "/system/lib/libc/musl/include", args.emscripten_path + "/system/lib/libc/musl/arch/generic",
99+
clang_path + "/../lib/clang/16/include"]
98100
self.target = Target ("TARGET_WASM", None, [])
99101
self.target_args += ["-target", args.abi]
100102

src/mono/mono/utils/atomic.h

Lines changed: 280 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,281 @@ F/MonoDroid( 1568): shared runtime initialization error: Cannot load library: re
2525
Apple targets have historically being problematic, xcode 4.6 would miscompile the intrinsic.
2626
*/
2727

28-
#if defined(HOST_WIN32)
28+
/* For each platform, decide what atomic implementation to use.
29+
*
30+
* Generally, we can enable C11 atomics if the header is available and if all the primitive types we
31+
* care about (int, long, void*, long long) are lock-free.
32+
*
33+
* Note that we generally don't want the compiler's locking implementation because it may take a
34+
* global lock, in which case if the atomic is used by both the GC implementation and runtime
35+
* internals we may have deadlocks during GC suspend.
36+
*
37+
* It might be possible to use some Mono specific implementation for specific types (e.g. long long)
38+
* on some platforms if the standard atomics for some type are not lock-free (for example: long
39+
* long). We might be able to use a GC-aware lock, for example.
40+
*
41+
*/
42+
#undef MONO_USE_C11_ATOMIC
43+
#undef MONO_USE_WIN32_ATOMIC
44+
#undef MONO_USE_GCC_ATOMIC
45+
#undef MONO_USE_EMULATED_ATOMIC
46+
47+
#if defined(MONO_GENERATING_OFFSETS)
48+
/*
49+
* Hack: for the offsets tool, define MONO_USE_EMULATED_ATOMIC since it doesn't actually need to see
50+
* the impementation, and the stdatomic ones cause problems on some Linux configurations where
51+
* libclang sees the platform header, not the clang one.
52+
*/
53+
# define MONO_USE_EMULATED_ATOMIC 1
54+
#elif defined(_MSC_VER) || defined(HOST_WIN32)
55+
/*
56+
* we need two things to switch to C11 atomics on Windows:
57+
*
58+
* 1. MSVC atomics support is not experimental, or we pass /experimental:c11atomics
59+
*
60+
* 2. We build our C++ code with C++23 or later (otherwise MSVC will complain about including
61+
* stdatomic.h)
62+
*
63+
*/
64+
# define MONO_USE_WIN32_ATOMIC 1
65+
#elif defined(HOST_IOS) || defined(HOST_OSX) || defined(HOST_WATCHOS) || defined(HOST_TVOS)
66+
# define MONO_USE_C11_ATOMIC 1
67+
#elif defined(HOST_ANDROID)
68+
/* on Android-x86 ATOMIC_LONG_LONG_LOCK_FREE == 1, not 2 like we want. */
69+
/* on Andriod-x64 ATOMIC_LONG_LOCK_FREE == 1, not 2 */
70+
/* on Android-armv7 ATOMIC_INT_LOCK_FREE == 1, not 2 */
71+
# if defined(HOST_ARM64)
72+
# define MONO_USE_C11_ATOMIC 1
73+
# elif defined(USE_GCC_ATOMIC_OPS)
74+
# define MONO_USE_GCC_ATOMIC 1
75+
# else
76+
# define MONO_USE_EMULATED_ATOMIC 1
77+
# endif
78+
#elif defined(HOST_LINUX)
79+
/* FIXME: probably need arch checks */
80+
# define MONO_USE_C11_ATOMIC 1
81+
#elif defined(HOST_WASI) || defined(HOST_BROWSER)
82+
# define MONO_USE_C11_ATOMIC 1
83+
#elif defined(USE_GCC_ATOMIC_OPS)
84+
/* Prefer GCC atomic ops if the target supports it (see configure.ac). */
85+
# define MONO_USE_GCC_ATOMIC 1
86+
#else
87+
# define MONO_USE_EMULATED_ATOMIC 1
88+
#endif
89+
90+
#if defined(MONO_USE_C11_ATOMIC)
91+
92+
#include<stdatomic.h>
93+
94+
static inline gint32
95+
mono_atomic_cas_i32 (volatile gint32 *dest, gint32 exch, gint32 comp)
96+
{
97+
g_static_assert (sizeof (atomic_int) == sizeof (*dest) && ATOMIC_INT_LOCK_FREE == 2);
98+
(void)atomic_compare_exchange_strong ((volatile atomic_int *)dest, &comp, exch);
99+
return comp;
100+
}
101+
102+
static inline gint64
103+
mono_atomic_cas_i64 (volatile gint64 *dest, gint64 exch, gint64 comp)
104+
{
105+
#if SIZEOF_LONG == 8
106+
g_static_assert (sizeof (atomic_long) == sizeof (*dest) && ATOMIC_LONG_LOCK_FREE == 2);
107+
(void)atomic_compare_exchange_strong ((volatile atomic_long *)dest, (long*)&comp, exch);
108+
return comp;
109+
#elif SIZEOF_LONG_LONG == 8
110+
g_static_assert (sizeof (atomic_llong) == sizeof (*dest) && ATOMIC_LLONG_LOCK_FREE == 2);
111+
(void)atomic_compare_exchange_strong ((volatile atomic_llong *)dest, (long long*)&comp, exch);
112+
return comp;
113+
#else
114+
#error gint64 not same size atomic_llong or atomic_long, don't define MONO_USE_STDATOMIC
115+
#endif
116+
}
117+
118+
static inline gpointer
119+
mono_atomic_cas_ptr (volatile gpointer *dest, gpointer exch, gpointer comp)
120+
{
121+
g_static_assert(ATOMIC_POINTER_LOCK_FREE == 2);
122+
(void)atomic_compare_exchange_strong ((volatile _Atomic(gpointer) *)dest, &comp, exch);
123+
return comp;
124+
}
125+
126+
static inline gint32
127+
mono_atomic_fetch_add_i32 (volatile gint32 *dest, gint32 add);
128+
static inline gint64
129+
mono_atomic_fetch_add_i64 (volatile gint64 *dest, gint64 add);
130+
131+
static inline gint32
132+
mono_atomic_add_i32 (volatile gint32 *dest, gint32 add)
133+
{
134+
// mono_atomic_add_ is supposed to return the value that is stored.
135+
// the atomic_add intrinsic returns the previous value instead.
136+
// so we return prev+add which should be the new value
137+
return mono_atomic_fetch_add_i32 (dest, add) + add;
138+
}
139+
140+
static inline gint64
141+
mono_atomic_add_i64 (volatile gint64 *dest, gint64 add)
142+
{
143+
return mono_atomic_fetch_add_i64 (dest, add) + add;
144+
}
145+
146+
static inline gint32
147+
mono_atomic_inc_i32 (volatile gint32 *dest)
148+
{
149+
return mono_atomic_add_i32 (dest, 1);
150+
}
151+
152+
static inline gint64
153+
mono_atomic_inc_i64 (volatile gint64 *dest)
154+
{
155+
return mono_atomic_add_i64 (dest, 1);
156+
}
157+
158+
static inline gint32
159+
mono_atomic_dec_i32 (volatile gint32 *dest)
160+
{
161+
return mono_atomic_add_i32 (dest, -1);
162+
}
163+
164+
static inline gint64
165+
mono_atomic_dec_i64 (volatile gint64 *dest)
166+
{
167+
return mono_atomic_add_i64 (dest, -1);
168+
}
169+
170+
static inline gint32
171+
mono_atomic_xchg_i32 (volatile gint32 *dest, gint32 exch)
172+
{
173+
g_static_assert (sizeof (atomic_int) == sizeof (*dest) && ATOMIC_INT_LOCK_FREE == 2);
174+
return atomic_exchange ((volatile atomic_int *)dest, exch);
175+
}
176+
177+
static inline gint64
178+
mono_atomic_xchg_i64 (volatile gint64 *dest, gint64 exch)
179+
{
180+
#if SIZEOF_LONG == 8
181+
g_static_assert (sizeof (atomic_long) == sizeof (*dest) && ATOMIC_LONG_LOCK_FREE == 2);
182+
return atomic_exchange ((volatile atomic_long *)dest, exch);
183+
#elif SIZEOF_LONG_LONG == 8
184+
g_static_assert (sizeof (atomic_llong) == sizeof (*dest) && ATOMIC_LLONG_LOCK_FREE == 2);
185+
return atomic_exchange ((volatile atomic_llong *)dest, exch);
186+
#else
187+
#error gint64 not same size atomic_llong or atomic_long, don't define MONO_USE_STDATOMIC
188+
#endif
189+
}
190+
191+
static inline gpointer
192+
mono_atomic_xchg_ptr (volatile gpointer *dest, gpointer exch)
193+
{
194+
g_static_assert (ATOMIC_POINTER_LOCK_FREE == 2);
195+
return atomic_exchange ((volatile _Atomic(gpointer) *)dest, exch);
196+
}
197+
198+
static inline gint32
199+
mono_atomic_fetch_add_i32 (volatile gint32 *dest, gint32 add)
200+
{
201+
g_static_assert (sizeof (atomic_int) == sizeof (*dest) && ATOMIC_INT_LOCK_FREE == 2);
202+
return atomic_fetch_add ((volatile atomic_int *)dest, add);
203+
}
204+
205+
static inline gint64
206+
mono_atomic_fetch_add_i64 (volatile gint64 *dest, gint64 add)
207+
{
208+
#if SIZEOF_LONG == 8
209+
g_static_assert (sizeof (atomic_long) == sizeof (*dest) && ATOMIC_LONG_LOCK_FREE == 2);
210+
return atomic_fetch_add ((volatile atomic_long *)dest, add);
211+
#elif SIZEOF_LONG_LONG == 8
212+
g_static_assert (sizeof (atomic_llong) == sizeof (*dest) && ATOMIC_LLONG_LOCK_FREE == 2);
213+
return atomic_fetch_add ((volatile atomic_llong *)dest, add);
214+
#else
215+
#error gint64 not same size atomic_llong or atomic_long, don't define MONO_USE_STDATOMIC
216+
#endif
217+
}
218+
219+
static inline gint8
220+
mono_atomic_load_i8 (volatile gint8 *src)
221+
{
222+
g_static_assert (sizeof (atomic_char) == sizeof (*src) && ATOMIC_CHAR_LOCK_FREE == 2);
223+
return atomic_load ((volatile atomic_char *)src);
224+
}
225+
226+
static inline gint16
227+
mono_atomic_load_i16 (volatile gint16 *src)
228+
{
229+
g_static_assert (sizeof (atomic_short) == sizeof (*src) && ATOMIC_SHORT_LOCK_FREE == 2);
230+
return atomic_load ((volatile atomic_short *)src);
231+
}
232+
233+
static inline gint32 mono_atomic_load_i32 (volatile gint32 *src)
234+
{
235+
g_static_assert (sizeof (atomic_int) == sizeof (*src) && ATOMIC_INT_LOCK_FREE == 2);
236+
return atomic_load ((volatile atomic_int *)src);
237+
}
238+
239+
static inline gint64
240+
mono_atomic_load_i64 (volatile gint64 *src)
241+
{
242+
#if SIZEOF_LONG == 8
243+
g_static_assert (sizeof (atomic_long) == sizeof (*src) && ATOMIC_LONG_LOCK_FREE == 2);
244+
return atomic_load ((volatile atomic_long *)src);
245+
#elif SIZEOF_LONG_LONG == 8
246+
g_static_assert (sizeof (atomic_llong) == sizeof (*src) && ATOMIC_LLONG_LOCK_FREE == 2);
247+
return atomic_load ((volatile atomic_llong *)src);
248+
#else
249+
#error gint64 not same size atomic_llong or atomic_long, don't define MONO_USE_STDATOMIC
250+
#endif
251+
}
252+
253+
static inline gpointer
254+
mono_atomic_load_ptr (volatile gpointer *src)
255+
{
256+
g_static_assert (ATOMIC_POINTER_LOCK_FREE == 2);
257+
return atomic_load ((volatile _Atomic(gpointer) *)src);
258+
}
259+
260+
static inline void
261+
mono_atomic_store_i8 (volatile gint8 *dst, gint8 val)
262+
{
263+
g_static_assert (sizeof (atomic_char) == sizeof (*dst) && ATOMIC_CHAR_LOCK_FREE == 2);
264+
atomic_store ((volatile atomic_char *)dst, val);
265+
}
266+
267+
static inline void
268+
mono_atomic_store_i16 (volatile gint16 *dst, gint16 val)
269+
{
270+
g_static_assert (sizeof (atomic_short) == sizeof (*dst) && ATOMIC_SHORT_LOCK_FREE == 2);
271+
atomic_store ((volatile atomic_short *)dst, val);
272+
}
273+
274+
static inline void
275+
mono_atomic_store_i32 (volatile gint32 *dst, gint32 val)
276+
{
277+
g_static_assert (sizeof (atomic_int) == sizeof (*dst) && ATOMIC_INT_LOCK_FREE == 2);
278+
atomic_store ((atomic_int *)dst, val);
279+
}
280+
281+
static inline void
282+
mono_atomic_store_i64 (volatile gint64 *dst, gint64 val)
283+
{
284+
#if SIZEOF_LONG == 8
285+
g_static_assert (sizeof (atomic_long) == sizeof (*dst) && ATOMIC_LONG_LOCK_FREE == 2);
286+
atomic_store ((volatile atomic_long *)dst, val);
287+
#elif SIZEOF_LONG_LONG == 8
288+
g_static_assert (sizeof (atomic_llong) == sizeof (*dst) && ATOMIC_LLONG_LOCK_FREE == 2);
289+
atomic_store ((volatile atomic_llong *)dst, val);
290+
#else
291+
#error gint64 not same size atomic_llong or atomic_long, don't define MONO_USE_STDATOMIC
292+
#endif
293+
}
294+
295+
static inline void
296+
mono_atomic_store_ptr (volatile gpointer *dst, gpointer val)
297+
{
298+
g_static_assert (ATOMIC_POINTER_LOCK_FREE == 2);
299+
atomic_store ((volatile _Atomic(gpointer) *)dst, val);
300+
}
301+
302+
#elif defined(MONO_USE_WIN32_ATOMIC)
29303

30304
#ifndef WIN32_LEAN_AND_MEAN
31305
#define WIN32_LEAN_AND_MEAN
@@ -204,8 +478,8 @@ mono_atomic_store_ptr (volatile gpointer *dst, gpointer val)
204478
InterlockedExchangePointer ((PVOID volatile *)dst, (PVOID)val);
205479
}
206480

207-
/* Prefer GCC atomic ops if the target supports it (see configure.ac). */
208-
#elif defined(USE_GCC_ATOMIC_OPS)
481+
482+
#elif defined(MONO_USE_GCC_ATOMIC)
209483

210484
/*
211485
* As of this comment (August 2016), all current Clang versions get atomic
@@ -453,7 +727,7 @@ static inline void mono_atomic_store_i64(volatile gint64 *dst, gint64 val)
453727
mono_atomic_xchg_i64 (dst, val);
454728
}
455729

456-
#else
730+
#elif defined(MONO_USE_EMULATED_ATOMIC)
457731

458732
#define WAPI_NO_ATOMIC_ASM
459733

@@ -482,6 +756,8 @@ extern void mono_atomic_store_i32(volatile gint32 *dst, gint32 val);
482756
extern void mono_atomic_store_i64(volatile gint64 *dst, gint64 val);
483757
extern void mono_atomic_store_ptr(volatile gpointer *dst, gpointer val);
484758

759+
#else
760+
#error one of MONO_USE_C11_ATOMIC, MONO_USE_WIN32_ATOMIC, MONO_USE_GCC_ATOMIC or MONO_USE_EMULATED_ATOMIC must be defined
485761
#endif
486762

487763
#if SIZEOF_VOID_P == 4

0 commit comments

Comments
 (0)