1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: set ts=8 sts=4 et sw=4 tw=99:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #ifndef jit_AtomicOperations_h
8 #define jit_AtomicOperations_h
9
10 #include "mozilla/Types.h"
11
12 #include "vm/SharedMem.h"
13
14 namespace js {
15 namespace jit {
16
17 class RegionLock;
18
19 /*
20 * The atomic operations layer defines types and functions for
21 * JIT-compatible atomic operation.
22 *
23 * The fundamental constraints on the functions are:
24 *
25 * - That their realization here MUST be compatible with code the JIT
26 * generates for its Atomics operations, so that an atomic access
27 * from the interpreter or runtime - from any C++ code - really is
28 * atomic relative to a concurrent, compatible atomic access from
29 * jitted code. That is, these primitives expose JIT-compatible
30 * atomicity functionality to C++.
31 *
32 * - That accesses may race without creating C++ undefined behavior:
33 * atomic accesses (marked "SeqCst") may race with non-atomic
34 * accesses (marked "SafeWhenRacy"); overlapping but non-matching,
35 * and hence incompatible, atomic accesses may race; and non-atomic
36 * accesses may race. The effects of races need not be predictable,
37 * so garbage can be produced by a read or written by a write, but
38 * the effects must be benign: the program must continue to run, and
39 * only the memory in the union of addresses named in the racing
40 * accesses may be affected.
41 *
42 * The compatibility constraint means that if the JIT makes dynamic
43 * decisions about how to implement atomic operations then
44 * corresponding dynamic decisions MUST be made in the implementations
45 * of the functions below.
46 *
47 * The safe-for-races constraint means that by and large, it is hard
48 * to implement these primitives in C++. See "Implementation notes"
49 * below.
50 *
51 * The "SeqCst" suffix on operations means "sequentially consistent"
52 * and means such a function's operation must have "sequentially
53 * consistent" memory ordering. See mfbt/Atomics.h for an explanation
54 * of this memory ordering.
55 *
56 * Note that a "SafeWhenRacy" access does not provide the atomicity of
57 * a "relaxed atomic" access: it can read or write garbage if there's
58 * a race.
59 *
60 *
61 * Implementation notes.
62 *
63 * It's not a requirement that these functions be inlined; performance
64 * is not a great concern. On some platforms these functions may call
65 * out to code that's generated at run time.
66 *
67 * In principle these functions will not be written in C++, thus
68 * making races defined behavior if all racy accesses from C++ go via
69 * these functions. (Jitted code will always be safe for races and
70 * provides the same guarantees as these functions.)
71 *
72 * The appropriate implementations will be platform-specific and
73 * there are some obvious implementation strategies to choose
74 * from, sometimes a combination is appropriate:
75 *
76 * - generating the code at run-time with the JIT;
77 * - hand-written assembler (maybe inline); or
78 * - using special compiler intrinsics or directives.
79 *
80 * Trusting the compiler not to generate code that blows up on a
81 * race definitely won't work in the presence of TSan, or even of
82 * optimizing compilers in seemingly-"innocuous" conditions. (See
83 * https://www.usenix.org/legacy/event/hotpar11/tech/final_files/Boehm.pdf
84 * for details.)
85 */
86 class AtomicOperations
87 {
88 friend class RegionLock;
89
90 private:
91 // The following functions are defined for T = int8_t, uint8_t,
92 // int16_t, uint16_t, int32_t, uint32_t, int64_t, and uint64_t.
93
94 // Atomically read *addr.
95 template<typename T>
96 static inline T loadSeqCst(T* addr);
97
98 // Atomically store val in *addr.
99 template<typename T>
100 static inline void storeSeqCst(T* addr, T val);
101
102 // Atomically store val in *addr and return the old value of *addr.
103 template<typename T>
104 static inline T exchangeSeqCst(T* addr, T val);
105
106 // Atomically check that *addr contains oldval and if so replace it
107 // with newval, in any case returning the old contents of *addr.
108 template<typename T>
109 static inline T compareExchangeSeqCst(T* addr, T oldval, T newval);
110
111 // The following functions are defined for T = int8_t, uint8_t,
112 // int16_t, uint16_t, int32_t, uint32_t only.
113
114 // Atomically add, subtract, bitwise-AND, bitwise-OR, or bitwise-XOR
115 // val into *addr and return the old value of *addr.
116 template<typename T>
117 static inline T fetchAddSeqCst(T* addr, T val);
118
119 template<typename T>
120 static inline T fetchSubSeqCst(T* addr, T val);
121
122 template<typename T>
123 static inline T fetchAndSeqCst(T* addr, T val);
124
125 template<typename T>
126 static inline T fetchOrSeqCst(T* addr, T val);
127
128 template<typename T>
129 static inline T fetchXorSeqCst(T* addr, T val);
130
131 // The SafeWhenRacy functions are to be used when C++ code has to access
132 // memory without synchronization and can't guarantee that there
133 // won't be a race on the access.
134
135 // Defined for all the integral types as well as for float32 and float64.
136 template<typename T>
137 static inline T loadSafeWhenRacy(T* addr);
138
139 // Defined for all the integral types as well as for float32 and float64.
140 template<typename T>
141 static inline void storeSafeWhenRacy(T* addr, T val);
142
143 // Replacement for memcpy().
144 static inline void memcpySafeWhenRacy(void* dest, const void* src, size_t nbytes);
145
146 // Replacement for memmove().
147 static inline void memmoveSafeWhenRacy(void* dest, const void* src, size_t nbytes);
148
149 public:
150 // Test lock-freedom for any integer value.
151 //
152 // This implements a platform-independent pattern, as follows:
153 //
154 // 1, 2, and 4 bytes are always lock free, lock-freedom for 8
155 // bytes is determined by the platform's isLockfree8(), and there
156 // is no lock-freedom for any other values on any platform.
157 static inline bool isLockfree(int32_t n);
158
159 // If the return value is true then a call to the 64-bit (8-byte)
160 // routines below will work, otherwise those functions will assert in
161 // debug builds and may crash in release build. (See the code in
162 // ../arm for an example.) The value of this call does not change
163 // during execution.
164 static inline bool isLockfree8();
165
166 // Execute a full memory barrier (LoadLoad+LoadStore+StoreLoad+StoreStore).
167 static inline void fenceSeqCst();
168
169 // All clients should use the APIs that take SharedMem pointers.
170 // See above for semantics and acceptable types.
171
172 template<typename T>
loadSeqCst(SharedMem<T * > addr)173 static T loadSeqCst(SharedMem<T*> addr) {
174 return loadSeqCst(addr.unwrap());
175 }
176
177 template<typename T>
storeSeqCst(SharedMem<T * > addr,T val)178 static void storeSeqCst(SharedMem<T*> addr, T val) {
179 return storeSeqCst(addr.unwrap(), val);
180 }
181
182 template<typename T>
exchangeSeqCst(SharedMem<T * > addr,T val)183 static T exchangeSeqCst(SharedMem<T*> addr, T val) {
184 return exchangeSeqCst(addr.unwrap(), val);
185 }
186
187 template<typename T>
compareExchangeSeqCst(SharedMem<T * > addr,T oldval,T newval)188 static T compareExchangeSeqCst(SharedMem<T*> addr, T oldval, T newval) {
189 return compareExchangeSeqCst(addr.unwrap(), oldval, newval);
190 }
191
192 template<typename T>
fetchAddSeqCst(SharedMem<T * > addr,T val)193 static T fetchAddSeqCst(SharedMem<T*> addr, T val) {
194 return fetchAddSeqCst(addr.unwrap(), val);
195 }
196
197 template<typename T>
fetchSubSeqCst(SharedMem<T * > addr,T val)198 static T fetchSubSeqCst(SharedMem<T*> addr, T val) {
199 return fetchSubSeqCst(addr.unwrap(), val);
200 }
201
202 template<typename T>
fetchAndSeqCst(SharedMem<T * > addr,T val)203 static T fetchAndSeqCst(SharedMem<T*> addr, T val) {
204 return fetchAndSeqCst(addr.unwrap(), val);
205 }
206
207 template<typename T>
fetchOrSeqCst(SharedMem<T * > addr,T val)208 static T fetchOrSeqCst(SharedMem<T*> addr, T val) {
209 return fetchOrSeqCst(addr.unwrap(), val);
210 }
211
212 template<typename T>
fetchXorSeqCst(SharedMem<T * > addr,T val)213 static T fetchXorSeqCst(SharedMem<T*> addr, T val) {
214 return fetchXorSeqCst(addr.unwrap(), val);
215 }
216
217 template<typename T>
loadSafeWhenRacy(SharedMem<T * > addr)218 static T loadSafeWhenRacy(SharedMem<T*> addr) {
219 return loadSafeWhenRacy(addr.unwrap());
220 }
221
222 template<typename T>
storeSafeWhenRacy(SharedMem<T * > addr,T val)223 static void storeSafeWhenRacy(SharedMem<T*> addr, T val) {
224 return storeSafeWhenRacy(addr.unwrap(), val);
225 }
226
227 template<typename T>
memcpySafeWhenRacy(SharedMem<T> dest,SharedMem<T> src,size_t nbytes)228 static void memcpySafeWhenRacy(SharedMem<T> dest, SharedMem<T> src, size_t nbytes) {
229 memcpySafeWhenRacy(static_cast<void*>(dest.unwrap()), static_cast<void*>(src.unwrap()), nbytes);
230 }
231
232 template<typename T>
memcpySafeWhenRacy(SharedMem<T> dest,T src,size_t nbytes)233 static void memcpySafeWhenRacy(SharedMem<T> dest, T src, size_t nbytes) {
234 memcpySafeWhenRacy(static_cast<void*>(dest.unwrap()), static_cast<void*>(src), nbytes);
235 }
236
237 template<typename T>
memcpySafeWhenRacy(T dest,SharedMem<T> src,size_t nbytes)238 static void memcpySafeWhenRacy(T dest, SharedMem<T> src, size_t nbytes) {
239 memcpySafeWhenRacy(static_cast<void*>(dest), static_cast<void*>(src.unwrap()), nbytes);
240 }
241
242 template<typename T>
memmoveSafeWhenRacy(SharedMem<T> dest,SharedMem<T> src,size_t nbytes)243 static void memmoveSafeWhenRacy(SharedMem<T> dest, SharedMem<T> src, size_t nbytes) {
244 memmoveSafeWhenRacy(static_cast<void*>(dest.unwrap()), static_cast<void*>(src.unwrap()), nbytes);
245 }
246 };
247
248 /* A data type representing a lock on some region of a
249 * SharedArrayRawBuffer's memory, to be used only when the hardware
250 * does not provide necessary atomicity (eg, float64 access on ARMv6
251 * and some ARMv7 systems).
252 */
253 class RegionLock
254 {
255 public:
RegionLock()256 RegionLock() : spinlock(0) {}
257
258 /* Addr is the address to be locked, nbytes the number of bytes we
259 * need to lock. The lock that is taken may cover a larger range
260 * of bytes.
261 */
262 template<size_t nbytes>
263 void acquire(void* addr);
264
265 /* Addr is the address to be unlocked, nbytes the number of bytes
266 * we need to unlock. The lock must be held by the calling thread,
267 * at the given address and for the number of bytes.
268 */
269 template<size_t nbytes>
270 void release(void* addr);
271
272 private:
273 /* For now, a simple spinlock that covers the entire buffer. */
274 uint32_t spinlock;
275 };
276
277 inline bool
isLockfree(int32_t size)278 AtomicOperations::isLockfree(int32_t size)
279 {
280 // Keep this in sync with visitAtomicIsLockFree() in jit/CodeGenerator.cpp.
281
282 switch (size) {
283 case 1:
284 case 2:
285 case 4:
286 return true;
287 case 8:
288 return AtomicOperations::isLockfree8();
289 default:
290 return false;
291 }
292 }
293
294 } // namespace jit
295 } // namespace js
296
297 #if defined(JS_CODEGEN_ARM)
298 # include "jit/arm/AtomicOperations-arm.h"
299 #elif defined(JS_CODEGEN_ARM64)
300 # include "jit/arm64/AtomicOperations-arm64.h"
301 #elif defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
302 # include "jit/mips-shared/AtomicOperations-mips-shared.h"
303 #elif defined(__ppc64__) || defined(__PPC64_) \
304 || defined(__ppc64le__) || defined(__PPC64LE__) \
305 || defined(__ppc__) || defined(__PPC__)
306 # include "jit/none/AtomicOperations-ppc.h"
307 #elif defined(JS_CODEGEN_NONE)
308 # include "jit/none/AtomicOperations-none.h"
309 #elif defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
310 # include "jit/x86-shared/AtomicOperations-x86-shared.h"
311 #else
312 # error "Atomic operations must be defined for this platform"
313 #endif
314
315 #endif // jit_AtomicOperations_h
316