summaryrefslogtreecommitdiffstats
path: root/js/src/jit/AtomicOperations.h
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jit/AtomicOperations.h')
-rw-r--r--js/src/jit/AtomicOperations.h352
1 files changed, 352 insertions, 0 deletions
diff --git a/js/src/jit/AtomicOperations.h b/js/src/jit/AtomicOperations.h
new file mode 100644
index 0000000000..8ad2839b36
--- /dev/null
+++ b/js/src/jit/AtomicOperations.h
@@ -0,0 +1,352 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef jit_AtomicOperations_h
+#define jit_AtomicOperations_h
+
+#include "mozilla/Types.h"
+
+#include <string.h>
+
+#include "jit/AtomicOperationsGenerated.h"
+#include "vm/SharedMem.h"
+
+namespace js {
+namespace jit {
+
+/*
+ * [SMDOC] Atomic Operations
+ *
+ * The atomic operations layer defines types and functions for
+ * JIT-compatible atomic operation.
+ *
+ * The fundamental constraints on the functions are:
+ *
+ * - That their realization here MUST be compatible with code the JIT
+ * generates for its Atomics operations, so that an atomic access
+ * from the interpreter or runtime - from any C++ code - really is
+ * atomic relative to a concurrent, compatible atomic access from
+ * jitted code. That is, these primitives expose JIT-compatible
+ * atomicity functionality to C++.
+ *
+ * - That accesses may race without creating C++ undefined behavior:
+ * atomic accesses (marked "SeqCst") may race with non-atomic
+ * accesses (marked "SafeWhenRacy"); overlapping but non-matching,
+ * and hence incompatible, atomic accesses may race; and non-atomic
+ * accesses may race. The effects of races need not be predictable,
+ * so garbage can be produced by a read or written by a write, but
+ * the effects must be benign: the program must continue to run, and
+ * only the memory in the union of addresses named in the racing
+ * accesses may be affected.
+ *
+ * The compatibility constraint means that if the JIT makes dynamic
+ * decisions about how to implement atomic operations then
+ * corresponding dynamic decisions MUST be made in the implementations
+ * of the functions below.
+ *
+ * The safe-for-races constraint means that by and large, it is hard
+ * to implement these primitives in C++. See "Implementation notes"
+ * below.
+ *
+ * The "SeqCst" suffix on operations means "sequentially consistent"
+ * and means such a function's operation must have "sequentially
+ * consistent" memory ordering. See mfbt/Atomics.h for an explanation
+ * of this memory ordering.
+ *
+ * Note that a "SafeWhenRacy" access does not provide the atomicity of
+ * a "relaxed atomic" access: it can read or write garbage if there's
+ * a race.
+ *
+ *
+ * Implementation notes.
+ *
+ * It's not a requirement that these functions be inlined; performance
+ * is not a great concern. On some platforms these functions may call
+ * functions that use inline assembly. See GenerateAtomicOperations.py.
+ *
+ * In principle these functions will not be written in C++, thus
+ * making races defined behavior if all racy accesses from C++ go via
+ * these functions. (Jitted code will always be safe for races and
+ * provides the same guarantees as these functions.)
+ *
+ * The appropriate implementations will be platform-specific and
+ * there are some obvious implementation strategies to choose
+ * from, sometimes a combination is appropriate:
+ *
+ * - generating the code at run-time with the JIT;
+ * - hand-written assembler (maybe inline); or
+ * - using special compiler intrinsics or directives.
+ *
+ * Trusting the compiler not to generate code that blows up on a
+ * race definitely won't work in the presence of TSan, or even of
+ * optimizing compilers in seemingly-"innocuous" conditions. (See
+ * https://www.usenix.org/legacy/event/hotpar11/tech/final_files/Boehm.pdf
+ * for details.)
+ */
+class AtomicOperations {
+ // The following functions are defined for T = int8_t, uint8_t,
+ // int16_t, uint16_t, int32_t, uint32_t, int64_t, and uint64_t.
+
+ // Atomically read *addr.
+ template <typename T>
+ static inline T loadSeqCst(T* addr);
+
+ // Atomically store val in *addr.
+ template <typename T>
+ static inline void storeSeqCst(T* addr, T val);
+
+ // Atomically store val in *addr and return the old value of *addr.
+ template <typename T>
+ static inline T exchangeSeqCst(T* addr, T val);
+
+ // Atomically check that *addr contains oldval and if so replace it
+ // with newval, in any case returning the old contents of *addr.
+ template <typename T>
+ static inline T compareExchangeSeqCst(T* addr, T oldval, T newval);
+
+ // Atomically add, subtract, bitwise-AND, bitwise-OR, or bitwise-XOR
+ // val into *addr and return the old value of *addr.
+ template <typename T>
+ static inline T fetchAddSeqCst(T* addr, T val);
+
+ template <typename T>
+ static inline T fetchSubSeqCst(T* addr, T val);
+
+ template <typename T>
+ static inline T fetchAndSeqCst(T* addr, T val);
+
+ template <typename T>
+ static inline T fetchOrSeqCst(T* addr, T val);
+
+ template <typename T>
+ static inline T fetchXorSeqCst(T* addr, T val);
+
+ // The SafeWhenRacy functions are to be used when C++ code has to access
+ // memory without synchronization and can't guarantee that there won't be a
+ // race on the access. But they are access-atomic for integer data so long
+ // as any racing writes are of the same size and to the same address.
+
+ // Defined for all the integral types as well as for float32 and float64,
+ // but not access-atomic for floats, nor for int64 and uint64 on 32-bit
+ // platforms.
+ template <typename T>
+ static inline T loadSafeWhenRacy(T* addr);
+
+ // Defined for all the integral types as well as for float32 and float64,
+ // but not access-atomic for floats, nor for int64 and uint64 on 32-bit
+ // platforms.
+ template <typename T>
+ static inline void storeSafeWhenRacy(T* addr, T val);
+
+ // Replacement for memcpy(). No access-atomicity guarantees.
+ static inline void memcpySafeWhenRacy(void* dest, const void* src,
+ size_t nbytes);
+
+ // Replacement for memmove(). No access-atomicity guarantees.
+ static inline void memmoveSafeWhenRacy(void* dest, const void* src,
+ size_t nbytes);
+
+ public:
+ // Test lock-freedom for any int32 value. This implements the
+ // Atomics::isLockFree() operation in the ECMAScript Shared Memory and
+ // Atomics specification, as follows:
+ //
+ // 4-byte accesses are always lock free (in the spec).
+ // 1-, 2-, and 8-byte accesses are always lock free (in SpiderMonkey).
+ //
+ // There is no lock-freedom for JS for any other values on any platform.
+ static constexpr inline bool isLockfreeJS(int32_t n);
+
+ // If the return value is true then the templated functions below are
+ // supported for int64_t and uint64_t. If the return value is false then
+ // those functions will MOZ_CRASH. The value of this call does not change
+ // during execution.
+ static inline bool hasAtomic8();
+
+ // If the return value is true then hasAtomic8() is true and the atomic
+ // operations are indeed lock-free. The value of this call does not change
+ // during execution.
+ static inline bool isLockfree8();
+
+ // Execute a full memory barrier (LoadLoad+LoadStore+StoreLoad+StoreStore).
+ static inline void fenceSeqCst();
+
+ // All clients should use the APIs that take SharedMem pointers.
+ // See above for semantics and acceptable types.
+
+ template <typename T>
+ static T loadSeqCst(SharedMem<T*> addr) {
+ return loadSeqCst(addr.unwrap());
+ }
+
+ template <typename T>
+ static void storeSeqCst(SharedMem<T*> addr, T val) {
+ return storeSeqCst(addr.unwrap(), val);
+ }
+
+ template <typename T>
+ static T exchangeSeqCst(SharedMem<T*> addr, T val) {
+ return exchangeSeqCst(addr.unwrap(), val);
+ }
+
+ template <typename T>
+ static T compareExchangeSeqCst(SharedMem<T*> addr, T oldval, T newval) {
+ return compareExchangeSeqCst(addr.unwrap(), oldval, newval);
+ }
+
+ template <typename T>
+ static T fetchAddSeqCst(SharedMem<T*> addr, T val) {
+ return fetchAddSeqCst(addr.unwrap(), val);
+ }
+
+ template <typename T>
+ static T fetchSubSeqCst(SharedMem<T*> addr, T val) {
+ return fetchSubSeqCst(addr.unwrap(), val);
+ }
+
+ template <typename T>
+ static T fetchAndSeqCst(SharedMem<T*> addr, T val) {
+ return fetchAndSeqCst(addr.unwrap(), val);
+ }
+
+ template <typename T>
+ static T fetchOrSeqCst(SharedMem<T*> addr, T val) {
+ return fetchOrSeqCst(addr.unwrap(), val);
+ }
+
+ template <typename T>
+ static T fetchXorSeqCst(SharedMem<T*> addr, T val) {
+ return fetchXorSeqCst(addr.unwrap(), val);
+ }
+
+ template <typename T>
+ static T loadSafeWhenRacy(SharedMem<T*> addr) {
+ return loadSafeWhenRacy(addr.unwrap());
+ }
+
+ template <typename T>
+ static void storeSafeWhenRacy(SharedMem<T*> addr, T val) {
+ return storeSafeWhenRacy(addr.unwrap(), val);
+ }
+
+ template <typename T>
+ static void memcpySafeWhenRacy(SharedMem<T*> dest, SharedMem<T*> src,
+ size_t nbytes) {
+ memcpySafeWhenRacy(dest.template cast<void*>().unwrap(),
+ src.template cast<void*>().unwrap(), nbytes);
+ }
+
+ template <typename T>
+ static void memcpySafeWhenRacy(SharedMem<T*> dest, T* src, size_t nbytes) {
+ memcpySafeWhenRacy(dest.template cast<void*>().unwrap(),
+ static_cast<void*>(src), nbytes);
+ }
+
+ template <typename T>
+ static void memcpySafeWhenRacy(T* dest, SharedMem<T*> src, size_t nbytes) {
+ memcpySafeWhenRacy(static_cast<void*>(dest),
+ src.template cast<void*>().unwrap(), nbytes);
+ }
+
+ template <typename T>
+ static void memmoveSafeWhenRacy(SharedMem<T*> dest, SharedMem<T*> src,
+ size_t nbytes) {
+ memmoveSafeWhenRacy(dest.template cast<void*>().unwrap(),
+ src.template cast<void*>().unwrap(), nbytes);
+ }
+
+ static void memsetSafeWhenRacy(SharedMem<uint8_t*> dest, int value,
+ size_t nbytes) {
+ uint8_t buf[1024];
+ size_t iterations = nbytes / sizeof(buf);
+ size_t tail = nbytes % sizeof(buf);
+ size_t offs = 0;
+ if (iterations > 0) {
+ memset(buf, value, sizeof(buf));
+ while (iterations--) {
+ memcpySafeWhenRacy(dest + offs, SharedMem<uint8_t*>::unshared(buf),
+ sizeof(buf));
+ offs += sizeof(buf);
+ }
+ } else {
+ memset(buf, value, tail);
+ }
+ memcpySafeWhenRacy(dest + offs, SharedMem<uint8_t*>::unshared(buf), tail);
+ }
+
+ template <typename T>
+ static void podCopySafeWhenRacy(SharedMem<T*> dest, SharedMem<T*> src,
+ size_t nelem) {
+ memcpySafeWhenRacy(dest, src, nelem * sizeof(T));
+ }
+
+ template <typename T>
+ static void podMoveSafeWhenRacy(SharedMem<T*> dest, SharedMem<T*> src,
+ size_t nelem) {
+ memmoveSafeWhenRacy(dest, src, nelem * sizeof(T));
+ }
+};
+
+constexpr inline bool AtomicOperations::isLockfreeJS(int32_t size) {
+ // Keep this in sync with atomicIsLockFreeJS() in jit/MacroAssembler.cpp.
+
+ switch (size) {
+ case 1:
+ return true;
+ case 2:
+ return true;
+ case 4:
+ // The spec requires Atomics.isLockFree(4) to return true.
+ return true;
+ case 8:
+ return true;
+ default:
+ return false;
+ }
+}
+
+} // namespace jit
+} // namespace js
+
+// As explained above, our atomic operations are not portable even in principle,
+// so we must include platform+compiler specific definitions here.
+//
+// x86, x64, arm, and arm64 are maintained by Mozilla. All other platform
+// setups are by platform maintainers' request and are not maintained by
+// Mozilla.
+//
+// If you are using a platform+compiler combination that causes an error below
+// (and if the problem isn't just that the compiler uses a different name for a
+// known architecture), you have basically three options:
+//
+// - find an already-supported compiler for the platform and use that instead
+//
+// - write your own support code for the platform+compiler and create a new
+// case below
+//
+// - include jit/shared/AtomicOperations-feeling-lucky.h in a case for the
+// platform below, if you have a gcc-compatible compiler and truly feel
+// lucky. You may have to add a little code to that file, too.
+//
+// Simulators are confusing. These atomic primitives must be compatible with
+// the code that the JIT emits, but of course for an ARM simulator running on
+// x86 the primitives here will be for x86, not for ARM, while the JIT emits ARM
+// code. Our ARM simulator solves that the easy way: by using these primitives
+// to implement its atomic operations. For other simulators there may need to
+// be special cases below to provide simulator-compatible primitives, for
+// example, for our ARM64 simulator the primitives could in principle
+// participate in the memory exclusivity monitors implemented by the simulator.
+// Such a solution is likely to be difficult.
+
+#ifdef JS_HAVE_GENERATED_ATOMIC_OPS
+# include "jit/shared/AtomicOperations-shared-jit.h"
+#elif defined(JS_SIMULATOR_MIPS32) || defined(__mips__)
+# include "jit/mips-shared/AtomicOperations-mips-shared.h"
+#else
+# include "jit/shared/AtomicOperations-feeling-lucky.h"
+#endif
+
+#endif // jit_AtomicOperations_h