1 | /* |
2 | Simple DirectMedia Layer |
3 | Copyright (C) 1997-2021 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(__WIN32__) || defined(__WINRT__) |
24 | #include "../core/windows/SDL_windows.h" |
25 | #endif |
26 | |
27 | #include "SDL_atomic.h" |
28 | #include "SDL_mutex.h" |
29 | #include "SDL_timer.h" |
30 | |
31 | #if !defined(HAVE_GCC_ATOMICS) && defined(__SOLARIS__) |
32 | #include <atomic.h> |
33 | #endif |
34 | |
35 | #if !defined(HAVE_GCC_ATOMICS) && defined(__RISCOS__) |
36 | #include <unixlib/local.h> |
37 | #endif |
38 | |
39 | #if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64)) |
40 | #include <xmmintrin.h> |
41 | #endif |
42 | |
43 | #if defined(__WATCOMC__) && defined(__386__) |
44 | SDL_COMPILE_TIME_ASSERT(locksize, 4==sizeof(SDL_SpinLock)); |
45 | extern _inline int _SDL_xchg_watcom(volatile int *a, int v); |
46 | #pragma aux _SDL_xchg_watcom = \ |
47 | "lock xchg [ecx], eax" \ |
48 | parm [ecx] [eax] \ |
49 | value [eax] \ |
50 | modify exact [eax]; |
51 | #endif /* __WATCOMC__ && __386__ */ |
52 | |
53 | /* This function is where all the magic happens... */ |
54 | SDL_bool |
55 | SDL_AtomicTryLock(SDL_SpinLock *lock) |
56 | { |
57 | #if SDL_ATOMIC_DISABLED |
58 | /* Terrible terrible damage */ |
59 | static SDL_mutex *_spinlock_mutex; |
60 | |
61 | if (!_spinlock_mutex) { |
62 | /* Race condition on first lock... */ |
63 | _spinlock_mutex = SDL_CreateMutex(); |
64 | } |
65 | SDL_LockMutex(_spinlock_mutex); |
66 | if (*lock == 0) { |
67 | *lock = 1; |
68 | SDL_UnlockMutex(_spinlock_mutex); |
69 | return SDL_TRUE; |
70 | } else { |
71 | SDL_UnlockMutex(_spinlock_mutex); |
72 | return SDL_FALSE; |
73 | } |
74 | |
75 | #elif defined(_MSC_VER) && (defined(_M_ARM) || defined(_M_ARM64)) |
76 | return (_InterlockedExchange_acq(lock, 1) == 0); |
77 | |
78 | #elif defined(_MSC_VER) |
79 | SDL_COMPILE_TIME_ASSERT(locksize, sizeof(*lock) == sizeof(long)); |
80 | return (InterlockedExchange((long*)lock, 1) == 0); |
81 | |
82 | #elif defined(__WATCOMC__) && defined(__386__) |
83 | return _SDL_xchg_watcom(lock, 1) == 0; |
84 | |
85 | #elif HAVE_GCC_ATOMICS || HAVE_GCC_SYNC_LOCK_TEST_AND_SET |
86 | return (__sync_lock_test_and_set(lock, 1) == 0); |
87 | |
88 | #elif defined(__GNUC__) && defined(__arm__) && \ |
89 | (defined(__ARM_ARCH_3__) || defined(__ARM_ARCH_3M__) || \ |
90 | defined(__ARM_ARCH_4__) || defined(__ARM_ARCH_4T__) || \ |
91 | defined(__ARM_ARCH_5__) || defined(__ARM_ARCH_5TE__) || \ |
92 | defined(__ARM_ARCH_5TEJ__)) |
93 | int result; |
94 | |
95 | #if defined(__RISCOS__) |
96 | if (__cpucap_have_rex()) { |
97 | __asm__ __volatile__ ( |
98 | "ldrex %0, [%2]\nteq %0, #0\nstrexeq %0, %1, [%2]" |
99 | : "=&r" (result) : "r" (1), "r" (lock) : "cc" , "memory" ); |
100 | return (result == 0); |
101 | } |
102 | #endif |
103 | |
104 | __asm__ __volatile__ ( |
105 | "swp %0, %1, [%2]\n" |
106 | : "=&r,&r" (result) : "r,0" (1), "r,r" (lock) : "memory" ); |
107 | return (result == 0); |
108 | |
109 | #elif defined(__GNUC__) && defined(__arm__) |
110 | int result; |
111 | __asm__ __volatile__ ( |
112 | "ldrex %0, [%2]\nteq %0, #0\nstrexeq %0, %1, [%2]" |
113 | : "=&r" (result) : "r" (1), "r" (lock) : "cc" , "memory" ); |
114 | return (result == 0); |
115 | |
116 | #elif defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) |
117 | int result; |
118 | __asm__ __volatile__( |
119 | "lock ; xchgl %0, (%1)\n" |
120 | : "=r" (result) : "r" (lock), "0" (1) : "cc" , "memory" ); |
121 | return (result == 0); |
122 | |
123 | #elif defined(__MACOSX__) || defined(__IPHONEOS__) |
124 | /* Maybe used for PowerPC, but the Intel asm or gcc atomics are favored. */ |
125 | return OSAtomicCompareAndSwap32Barrier(0, 1, lock); |
126 | |
127 | #elif defined(__SOLARIS__) && defined(_LP64) |
128 | /* Used for Solaris with non-gcc compilers. */ |
129 | return (SDL_bool) ((int) atomic_cas_64((volatile uint64_t*)lock, 0, 1) == 0); |
130 | |
131 | #elif defined(__SOLARIS__) && !defined(_LP64) |
132 | /* Used for Solaris with non-gcc compilers. */ |
133 | return (SDL_bool) ((int) atomic_cas_32((volatile uint32_t*)lock, 0, 1) == 0); |
134 | |
135 | #else |
136 | #error Please implement for your platform. |
137 | return SDL_FALSE; |
138 | #endif |
139 | } |
140 | |
141 | /* "REP NOP" is PAUSE, coded for tools that don't know it by that name. */ |
142 | #if (defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) |
143 | #define PAUSE_INSTRUCTION() __asm__ __volatile__("pause\n") /* Some assemblers can't do REP NOP, so go with PAUSE. */ |
144 | #elif (defined(__arm__) && __ARM_ARCH__ >= 7) || defined(__aarch64__) |
145 | #define PAUSE_INSTRUCTION() __asm__ __volatile__("yield" ::: "memory") |
146 | #elif (defined(__powerpc__) || defined(__powerpc64__)) |
147 | #define PAUSE_INSTRUCTION() __asm__ __volatile__("or 27,27,27"); |
148 | #elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64)) |
149 | #define PAUSE_INSTRUCTION() _mm_pause() /* this is actually "rep nop" and not a SIMD instruction. No inline asm in MSVC x86-64! */ |
150 | #elif defined(_MSC_VER) && (defined(_M_ARM) || defined(_M_ARM64)) |
151 | #define PAUSE_INSTRUCTION() __yield() |
152 | #elif defined(__WATCOMC__) && defined(__386__) |
153 | /* watcom assembler rejects PAUSE if CPU < i686, and it refuses REP NOP as an invalid combination. Hardcode the bytes. */ |
154 | extern _inline void PAUSE_INSTRUCTION(void); |
155 | #pragma aux PAUSE_INSTRUCTION = "db 0f3h,90h" |
156 | #else |
157 | #define PAUSE_INSTRUCTION() |
158 | #endif |
159 | |
160 | void |
161 | SDL_AtomicLock(SDL_SpinLock *lock) |
162 | { |
163 | int iterations = 0; |
164 | /* FIXME: Should we have an eventual timeout? */ |
165 | while (!SDL_AtomicTryLock(lock)) { |
166 | if (iterations < 32) { |
167 | iterations++; |
168 | PAUSE_INSTRUCTION(); |
169 | } else { |
170 | /* !!! FIXME: this doesn't definitely give up the current timeslice, it does different things on various platforms. */ |
171 | SDL_Delay(0); |
172 | } |
173 | } |
174 | } |
175 | |
176 | void |
177 | SDL_AtomicUnlock(SDL_SpinLock *lock) |
178 | { |
179 | #if defined(_MSC_VER) && (defined(_M_ARM) || defined(_M_ARM64)) |
180 | _InterlockedExchange_rel(lock, 0); |
181 | #elif defined(_MSC_VER) |
182 | _ReadWriteBarrier(); |
183 | *lock = 0; |
184 | |
185 | #elif defined(__WATCOMC__) && defined(__386__) |
186 | SDL_CompilerBarrier (); |
187 | *lock = 0; |
188 | |
189 | #elif HAVE_GCC_ATOMICS || HAVE_GCC_SYNC_LOCK_TEST_AND_SET |
190 | __sync_lock_release(lock); |
191 | |
192 | #elif defined(__SOLARIS__) |
193 | /* Used for Solaris when not using gcc. */ |
194 | *lock = 0; |
195 | membar_producer(); |
196 | |
197 | #else |
198 | *lock = 0; |
199 | #endif |
200 | } |
201 | |
202 | /* vi: set ts=4 sw=4 expandtab: */ |
203 | |