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