1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#if defined(_MSC_VER) && (_MSC_VER >= 1900)
24#include <intrin.h>
25#define HAVE_MSC_ATOMICS 1
26#endif
27
28#ifdef SDL_PLATFORM_MACOS // !!! FIXME: should we favor gcc atomics?
29#include <libkern/OSAtomic.h>
30#endif
31
32#if !defined(HAVE_GCC_ATOMICS) && defined(SDL_PLATFORM_SOLARIS)
33#include <atomic.h>
34#endif
35
36// The __atomic_load_n() intrinsic showed up in different times for different compilers.
37#ifdef __clang__
38#if __has_builtin(__atomic_load_n) || defined(HAVE_GCC_ATOMICS)
39/* !!! FIXME: this advertises as available in the NDK but uses an external symbol we don't have.
40 It might be in a later NDK or we might need an extra library? --ryan. */
41#ifndef SDL_PLATFORM_ANDROID
42#define HAVE_ATOMIC_LOAD_N 1
43#endif
44#endif
45#elif defined(__GNUC__)
46#if (__GNUC__ >= 5)
47#define HAVE_ATOMIC_LOAD_N 1
48#endif
49#endif
50
51/* *INDENT-OFF* */ // clang-format off
52#if defined(__WATCOMC__) && defined(__386__)
53SDL_COMPILE_TIME_ASSERT(intsize, 4==sizeof(int));
54#define HAVE_WATCOM_ATOMICS
55extern __inline int _SDL_xchg_watcom(volatile int *a, int v);
56#pragma aux _SDL_xchg_watcom = \
57 "lock xchg [ecx], eax" \
58 parm [ecx] [eax] \
59 value [eax] \
60 modify exact [eax];
61
62extern __inline unsigned char _SDL_cmpxchg_watcom(volatile int *a, int newval, int oldval);
63#pragma aux _SDL_cmpxchg_watcom = \
64 "lock cmpxchg [edx], ecx" \
65 "setz al" \
66 parm [edx] [ecx] [eax] \
67 value [al] \
68 modify exact [eax];
69
70extern __inline int _SDL_xadd_watcom(volatile int *a, int v);
71#pragma aux _SDL_xadd_watcom = \
72 "lock xadd [ecx], eax" \
73 parm [ecx] [eax] \
74 value [eax] \
75 modify exact [eax];
76
77#endif // __WATCOMC__ && __386__
78/* *INDENT-ON* */ // clang-format on
79
80/*
81 If any of the operations are not provided then we must emulate some
82 of them. That means we need a nice implementation of spin locks
83 that avoids the "one big lock" problem. We use a vector of spin
84 locks and pick which one to use based on the address of the operand
85 of the function.
86
87 To generate the index of the lock we first shift by 3 bits to get
88 rid on the zero bits that result from 32 and 64 bit alignment of
89 data. We then mask off all but 5 bits and use those 5 bits as an
90 index into the table.
91
92 Picking the lock this way insures that accesses to the same data at
93 the same time will go to the same lock. OTOH, accesses to different
94 data have only a 1/32 chance of hitting the same lock. That should
95 pretty much eliminate the chances of several atomic operations on
96 different data from waiting on the same "big lock". If it isn't
97 then the table of locks can be expanded to a new size so long as
98 the new size is a power of two.
99
100 Contributed by Bob Pendleton, bob@pendleton.com
101*/
102
103#if !defined(HAVE_MSC_ATOMICS) && !defined(HAVE_GCC_ATOMICS) && !defined(SDL_PLATFORM_MACOS) && !defined(SDL_PLATFORM_SOLARIS) && !defined(HAVE_WATCOM_ATOMICS)
104#define EMULATE_CAS 1
105#endif
106
107#ifdef EMULATE_CAS
108static SDL_SpinLock locks[32];
109
110static SDL_INLINE void enterLock(void *a)
111{
112 uintptr_t index = ((((uintptr_t)a) >> 3) & 0x1f);
113
114 SDL_LockSpinlock(&locks[index]);
115}
116
117static SDL_INLINE void leaveLock(void *a)
118{
119 uintptr_t index = ((((uintptr_t)a) >> 3) & 0x1f);
120
121 SDL_UnlockSpinlock(&locks[index]);
122}
123#endif
124
125bool SDL_CompareAndSwapAtomicInt(SDL_AtomicInt *a, int oldval, int newval)
126{
127#ifdef HAVE_MSC_ATOMICS
128 SDL_COMPILE_TIME_ASSERT(atomic_cas, sizeof(long) == sizeof(a->value));
129 return _InterlockedCompareExchange((long *)&a->value, (long)newval, (long)oldval) == (long)oldval;
130#elif defined(HAVE_WATCOM_ATOMICS)
131 return _SDL_cmpxchg_watcom((volatile int *)&a->value, newval, oldval);
132#elif defined(HAVE_GCC_ATOMICS)
133 return __sync_bool_compare_and_swap(&a->value, oldval, newval);
134#elif defined(SDL_PLATFORM_MACOS) // this is deprecated in 10.12 sdk; favor gcc atomics.
135 return OSAtomicCompareAndSwap32Barrier(oldval, newval, &a->value);
136#elif defined(SDL_PLATFORM_SOLARIS)
137 SDL_COMPILE_TIME_ASSERT(atomic_cas, sizeof(uint_t) == sizeof(a->value));
138 return ((int)atomic_cas_uint((volatile uint_t *)&a->value, (uint_t)oldval, (uint_t)newval) == oldval);
139#elif defined(EMULATE_CAS)
140 bool result = false;
141
142 enterLock(a);
143 if (a->value == oldval) {
144 a->value = newval;
145 result = true;
146 }
147 leaveLock(a);
148
149 return result;
150#else
151#error Please define your platform.
152#endif
153}
154
155bool SDL_CompareAndSwapAtomicU32(SDL_AtomicU32 *a, Uint32 oldval, Uint32 newval)
156{
157#ifdef HAVE_MSC_ATOMICS
158 SDL_COMPILE_TIME_ASSERT(atomic_cas, sizeof(long) == sizeof(a->value));
159 return _InterlockedCompareExchange((long *)&a->value, (long)newval, (long)oldval) == (long)oldval;
160#elif defined(HAVE_WATCOM_ATOMICS)
161 SDL_COMPILE_TIME_ASSERT(atomic_cas, sizeof(int) == sizeof(a->value));
162 return _SDL_cmpxchg_watcom((volatile int *)&a->value, (int)newval, (int)oldval);
163#elif defined(HAVE_GCC_ATOMICS)
164 return __sync_bool_compare_and_swap(&a->value, oldval, newval);
165#elif defined(SDL_PLATFORM_MACOS) // this is deprecated in 10.12 sdk; favor gcc atomics.
166 return OSAtomicCompareAndSwap32Barrier((int32_t)oldval, (int32_t)newval, (int32_t*)&a->value);
167#elif defined(SDL_PLATFORM_SOLARIS)
168 SDL_COMPILE_TIME_ASSERT(atomic_cas, sizeof(uint_t) == sizeof(a->value));
169 return ((Uint32)atomic_cas_uint((volatile uint_t *)&a->value, (uint_t)oldval, (uint_t)newval) == oldval);
170#elif defined(EMULATE_CAS)
171 bool result = false;
172
173 enterLock(a);
174 if (a->value == oldval) {
175 a->value = newval;
176 result = true;
177 }
178 leaveLock(a);
179
180 return result;
181#else
182#error Please define your platform.
183#endif
184}
185
186bool SDL_CompareAndSwapAtomicPointer(void **a, void *oldval, void *newval)
187{
188#ifdef HAVE_MSC_ATOMICS
189 return _InterlockedCompareExchangePointer(a, newval, oldval) == oldval;
190#elif defined(HAVE_WATCOM_ATOMICS)
191 return _SDL_cmpxchg_watcom((int *)a, (long)newval, (long)oldval);
192#elif defined(HAVE_GCC_ATOMICS)
193 return __sync_bool_compare_and_swap(a, oldval, newval);
194#elif defined(SDL_PLATFORM_MACOS) && defined(__LP64__) // this is deprecated in 10.12 sdk; favor gcc atomics.
195 return OSAtomicCompareAndSwap64Barrier((int64_t)oldval, (int64_t)newval, (int64_t *)a);
196#elif defined(SDL_PLATFORM_MACOS) && !defined(__LP64__) // this is deprecated in 10.12 sdk; favor gcc atomics.
197 return OSAtomicCompareAndSwap32Barrier((int32_t)oldval, (int32_t)newval, (int32_t *)a);
198#elif defined(SDL_PLATFORM_SOLARIS)
199 return (atomic_cas_ptr(a, oldval, newval) == oldval);
200#elif defined(EMULATE_CAS)
201 bool result = false;
202
203 enterLock(a);
204 if (*a == oldval) {
205 *a = newval;
206 result = true;
207 }
208 leaveLock(a);
209
210 return result;
211#else
212#error Please define your platform.
213#endif
214}
215
216int SDL_SetAtomicInt(SDL_AtomicInt *a, int v)
217{
218#ifdef HAVE_MSC_ATOMICS
219 SDL_COMPILE_TIME_ASSERT(atomic_set, sizeof(long) == sizeof(a->value));
220 return _InterlockedExchange((long *)&a->value, v);
221#elif defined(HAVE_WATCOM_ATOMICS)
222 return _SDL_xchg_watcom(&a->value, v);
223#elif defined(HAVE_GCC_ATOMICS)
224 return __sync_lock_test_and_set(&a->value, v);
225#elif defined(SDL_PLATFORM_SOLARIS)
226 SDL_COMPILE_TIME_ASSERT(atomic_set, sizeof(uint_t) == sizeof(a->value));
227 return (int)atomic_swap_uint((volatile uint_t *)&a->value, v);
228#else
229 int value;
230 do {
231 value = a->value;
232 } while (!SDL_CompareAndSwapAtomicInt(a, value, v));
233 return value;
234#endif
235}
236
237Uint32 SDL_SetAtomicU32(SDL_AtomicU32 *a, Uint32 v)
238{
239#ifdef HAVE_MSC_ATOMICS
240 SDL_COMPILE_TIME_ASSERT(atomic_set, sizeof(long) == sizeof(a->value));
241 return _InterlockedExchange((long *)&a->value, v);
242#elif defined(HAVE_WATCOM_ATOMICS)
243 return _SDL_xchg_watcom(&a->value, v);
244#elif defined(HAVE_GCC_ATOMICS)
245 return __sync_lock_test_and_set(&a->value, v);
246#elif defined(SDL_PLATFORM_SOLARIS)
247 SDL_COMPILE_TIME_ASSERT(atomic_set, sizeof(uint_t) == sizeof(a->value));
248 return (Uint32)atomic_swap_uint((volatile uint_t *)&a->value, v);
249#else
250 Uint32 value;
251 do {
252 value = a->value;
253 } while (!SDL_CompareAndSwapAtomicU32(a, value, v));
254 return value;
255#endif
256}
257
258void *SDL_SetAtomicPointer(void **a, void *v)
259{
260#ifdef HAVE_MSC_ATOMICS
261 return _InterlockedExchangePointer(a, v);
262#elif defined(HAVE_WATCOM_ATOMICS)
263 return (void *)_SDL_xchg_watcom((int *)a, (long)v);
264#elif defined(HAVE_GCC_ATOMICS)
265 return __sync_lock_test_and_set(a, v);
266#elif defined(SDL_PLATFORM_SOLARIS)
267 return atomic_swap_ptr(a, v);
268#else
269 void *value;
270 do {
271 value = *a;
272 } while (!SDL_CompareAndSwapAtomicPointer(a, value, v));
273 return value;
274#endif
275}
276
277int SDL_AddAtomicInt(SDL_AtomicInt *a, int v)
278{
279#ifdef HAVE_MSC_ATOMICS
280 SDL_COMPILE_TIME_ASSERT(atomic_add, sizeof(long) == sizeof(a->value));
281 return _InterlockedExchangeAdd((long *)&a->value, v);
282#elif defined(HAVE_WATCOM_ATOMICS)
283 SDL_COMPILE_TIME_ASSERT(atomic_add, sizeof(int) == sizeof(a->value));
284 return _SDL_xadd_watcom((volatile int *)&a->value, v);
285#elif defined(HAVE_GCC_ATOMICS)
286 return __sync_fetch_and_add(&a->value, v);
287#elif defined(SDL_PLATFORM_SOLARIS)
288 int pv = a->value;
289 membar_consumer();
290 atomic_add_int((volatile uint_t *)&a->value, v);
291 return pv;
292#else
293 int value;
294 do {
295 value = a->value;
296 } while (!SDL_CompareAndSwapAtomicInt(a, value, (value + v)));
297 return value;
298#endif
299}
300
301int SDL_GetAtomicInt(SDL_AtomicInt *a)
302{
303#ifdef HAVE_ATOMIC_LOAD_N
304 return __atomic_load_n(&a->value, __ATOMIC_SEQ_CST);
305#elif defined(HAVE_MSC_ATOMICS)
306 SDL_COMPILE_TIME_ASSERT(atomic_get, sizeof(long) == sizeof(a->value));
307 return _InterlockedOr((long *)&a->value, 0);
308#elif defined(HAVE_WATCOM_ATOMICS)
309 return _SDL_xadd_watcom(&a->value, 0);
310#elif defined(HAVE_GCC_ATOMICS)
311 return __sync_or_and_fetch(&a->value, 0);
312#elif defined(SDL_PLATFORM_MACOS) // this is deprecated in 10.12 sdk; favor gcc atomics.
313 return sizeof(a->value) == sizeof(uint32_t) ? OSAtomicOr32Barrier(0, (volatile uint32_t *)&a->value) : OSAtomicAdd64Barrier(0, (volatile int64_t *)&a->value);
314#elif defined(SDL_PLATFORM_SOLARIS)
315 return atomic_or_uint_nv((volatile uint_t *)&a->value, 0);
316#else
317 int value;
318 do {
319 value = a->value;
320 } while (!SDL_CompareAndSwapAtomicInt(a, value, value));
321 return value;
322#endif
323}
324
325Uint32 SDL_GetAtomicU32(SDL_AtomicU32 *a)
326{
327#ifdef HAVE_ATOMIC_LOAD_N
328 return __atomic_load_n(&a->value, __ATOMIC_SEQ_CST);
329#elif defined(HAVE_MSC_ATOMICS)
330 SDL_COMPILE_TIME_ASSERT(atomic_get, sizeof(long) == sizeof(a->value));
331 return (Uint32)_InterlockedOr((long *)&a->value, 0);
332#elif defined(HAVE_WATCOM_ATOMICS)
333 SDL_COMPILE_TIME_ASSERT(atomic_get, sizeof(int) == sizeof(a->value));
334 return (Uint32)_SDL_xadd_watcom((volatile int *)&a->value, 0);
335#elif defined(HAVE_GCC_ATOMICS)
336 return __sync_or_and_fetch(&a->value, 0);
337#elif defined(SDL_PLATFORM_MACOS) // this is deprecated in 10.12 sdk; favor gcc atomics.
338 return OSAtomicOr32Barrier(0, (volatile uint32_t *)&a->value);
339#elif defined(SDL_PLATFORM_SOLARIS)
340 SDL_COMPILE_TIME_ASSERT(atomic_get, sizeof(uint_t) == sizeof(a->value));
341 return (Uint32)atomic_or_uint_nv((volatile uint_t *)&a->value, 0);
342#else
343 Uint32 value;
344 do {
345 value = a->value;
346 } while (!SDL_CompareAndSwapAtomicU32(a, value, value));
347 return value;
348#endif
349}
350
351void *SDL_GetAtomicPointer(void **a)
352{
353#ifdef HAVE_ATOMIC_LOAD_N
354 return __atomic_load_n(a, __ATOMIC_SEQ_CST);
355#elif defined(HAVE_MSC_ATOMICS)
356 return _InterlockedCompareExchangePointer(a, NULL, NULL);
357#elif defined(HAVE_GCC_ATOMICS)
358 return __sync_val_compare_and_swap(a, (void *)0, (void *)0);
359#elif defined(SDL_PLATFORM_SOLARIS)
360 return atomic_cas_ptr(a, (void *)0, (void *)0);
361#else
362 void *value;
363 do {
364 value = *a;
365 } while (!SDL_CompareAndSwapAtomicPointer(a, value, value));
366 return value;
367#endif
368}
369
370#ifdef SDL_MEMORY_BARRIER_USES_FUNCTION
371#error This file should be built in arm mode so the mcr instruction is available for memory barriers
372#endif
373
374void SDL_MemoryBarrierReleaseFunction(void)
375{
376 SDL_MemoryBarrierRelease();
377}
378
379void SDL_MemoryBarrierAcquireFunction(void)
380{
381 SDL_MemoryBarrierAcquire();
382}
383