summaryrefslogtreecommitdiffstats
path: root/js/src/builtin/AtomicsObject.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/builtin/AtomicsObject.cpp')
-rw-r--r--js/src/builtin/AtomicsObject.cpp1152
1 files changed, 1152 insertions, 0 deletions
diff --git a/js/src/builtin/AtomicsObject.cpp b/js/src/builtin/AtomicsObject.cpp
new file mode 100644
index 000000000..08777fd51
--- /dev/null
+++ b/js/src/builtin/AtomicsObject.cpp
@@ -0,0 +1,1152 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * 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/. */
+
+/*
+ * JS Atomics pseudo-module.
+ *
+ * See "Spec: JavaScript Shared Memory, Atomics, and Locks" for the
+ * full specification.
+ *
+ * In addition to what is specified there, we throw an Error object if
+ * the futex API hooks have not been installed on the runtime.
+ * Essentially that is an implementation error at a higher level.
+ *
+ *
+ * Note on the current implementation of atomic operations.
+ *
+ * The Mozilla atomics are not sufficient to implement these APIs
+ * because we need to support 8-bit, 16-bit, and 32-bit data: the
+ * Mozilla atomics only support 32-bit data.
+ *
+ * At the moment we include mozilla/Atomics.h, which will define
+ * MOZ_HAVE_CXX11_ATOMICS and include <atomic> if we have C++11
+ * atomics.
+ *
+ * If MOZ_HAVE_CXX11_ATOMICS is set we'll use C++11 atomics.
+ *
+ * Otherwise, if the compiler has them we'll fall back on gcc/Clang
+ * intrinsics.
+ *
+ * Otherwise, if we're on VC++2012, we'll use C++11 atomics even if
+ * MOZ_HAVE_CXX11_ATOMICS is not defined. The compiler has the
+ * atomics but they are disabled in Mozilla due to a performance bug.
+ * That performance bug does not affect the Atomics code. See
+ * mozilla/Atomics.h for further comments on that bug.
+ *
+ * Otherwise, if we're on VC++2010 or VC++2008, we'll emulate the
+ * gcc/Clang intrinsics with simple code below using the VC++
+ * intrinsics, like the VC++2012 solution this is a stopgap since
+ * we're about to start using VC++2013 anyway.
+ *
+ * If none of those options are available then the build must disable
+ * shared memory, or compilation will fail with a predictable error.
+ */
+
+#include "builtin/AtomicsObject.h"
+
+#include "mozilla/Atomics.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Unused.h"
+
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "jsnum.h"
+
+#include "jit/AtomicOperations.h"
+#include "jit/InlinableNatives.h"
+#include "js/Class.h"
+#include "vm/GlobalObject.h"
+#include "vm/Time.h"
+#include "vm/TypedArrayObject.h"
+#include "wasm/WasmInstance.h"
+
+#include "jsobjinlines.h"
+
+using namespace js;
+
+const Class AtomicsObject::class_ = {
+ "Atomics",
+ JSCLASS_HAS_CACHED_PROTO(JSProto_Atomics)
+};
+
+static bool
+ReportBadArrayType(JSContext* cx)
+{
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ATOMICS_BAD_ARRAY);
+ return false;
+}
+
+static bool
+ReportOutOfRange(JSContext* cx)
+{
+ // Use JSMSG_BAD_INDEX here even if it is generic, since that is
+ // the message used by ToIntegerIndex for its initial range
+ // checking.
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX);
+ return false;
+}
+
+static bool
+ReportCannotWait(JSContext* cx)
+{
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ATOMICS_WAIT_NOT_ALLOWED);
+ return false;
+}
+
+static bool
+GetSharedTypedArray(JSContext* cx, HandleValue v,
+ MutableHandle<TypedArrayObject*> viewp)
+{
+ if (!v.isObject())
+ return ReportBadArrayType(cx);
+ if (!v.toObject().is<TypedArrayObject>())
+ return ReportBadArrayType(cx);
+ viewp.set(&v.toObject().as<TypedArrayObject>());
+ if (!viewp->isSharedMemory())
+ return ReportBadArrayType(cx);
+ return true;
+}
+
+static bool
+GetTypedArrayIndex(JSContext* cx, HandleValue v, Handle<TypedArrayObject*> view, uint32_t* offset)
+{
+ uint64_t index;
+ if (!js::ToIntegerIndex(cx, v, &index))
+ return false;
+ if (index >= view->length())
+ return ReportOutOfRange(cx);
+ *offset = uint32_t(index);
+ return true;
+}
+
+static int32_t
+CompareExchange(Scalar::Type viewType, int32_t oldCandidate, int32_t newCandidate,
+ SharedMem<void*> viewData, uint32_t offset, bool* badArrayType = nullptr)
+{
+ switch (viewType) {
+ case Scalar::Int8: {
+ int8_t oldval = (int8_t)oldCandidate;
+ int8_t newval = (int8_t)newCandidate;
+ oldval = jit::AtomicOperations::compareExchangeSeqCst(viewData.cast<int8_t*>() + offset,
+ oldval, newval);
+ return oldval;
+ }
+ case Scalar::Uint8: {
+ uint8_t oldval = (uint8_t)oldCandidate;
+ uint8_t newval = (uint8_t)newCandidate;
+ oldval = jit::AtomicOperations::compareExchangeSeqCst(viewData.cast<uint8_t*>() + offset,
+ oldval, newval);
+ return oldval;
+ }
+ case Scalar::Int16: {
+ int16_t oldval = (int16_t)oldCandidate;
+ int16_t newval = (int16_t)newCandidate;
+ oldval = jit::AtomicOperations::compareExchangeSeqCst(viewData.cast<int16_t*>() + offset,
+ oldval, newval);
+ return oldval;
+ }
+ case Scalar::Uint16: {
+ uint16_t oldval = (uint16_t)oldCandidate;
+ uint16_t newval = (uint16_t)newCandidate;
+ oldval = jit::AtomicOperations::compareExchangeSeqCst(viewData.cast<uint16_t*>() + offset,
+ oldval, newval);
+ return oldval;
+ }
+ case Scalar::Int32: {
+ int32_t oldval = oldCandidate;
+ int32_t newval = newCandidate;
+ oldval = jit::AtomicOperations::compareExchangeSeqCst(viewData.cast<int32_t*>() + offset,
+ oldval, newval);
+ return oldval;
+ }
+ case Scalar::Uint32: {
+ uint32_t oldval = (uint32_t)oldCandidate;
+ uint32_t newval = (uint32_t)newCandidate;
+ oldval = jit::AtomicOperations::compareExchangeSeqCst(viewData.cast<uint32_t*>() + offset,
+ oldval, newval);
+ return (int32_t)oldval;
+ }
+ default:
+ if (badArrayType)
+ *badArrayType = true;
+ return 0;
+ }
+}
+
+bool
+js::atomics_compareExchange(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ HandleValue objv = args.get(0);
+ HandleValue idxv = args.get(1);
+ HandleValue oldv = args.get(2);
+ HandleValue newv = args.get(3);
+ MutableHandleValue r = args.rval();
+
+ Rooted<TypedArrayObject*> view(cx, nullptr);
+ if (!GetSharedTypedArray(cx, objv, &view))
+ return false;
+ uint32_t offset;
+ if (!GetTypedArrayIndex(cx, idxv, view, &offset))
+ return false;
+ int32_t oldCandidate;
+ if (!ToInt32(cx, oldv, &oldCandidate))
+ return false;
+ int32_t newCandidate;
+ if (!ToInt32(cx, newv, &newCandidate))
+ return false;
+
+ bool badType = false;
+ int32_t result = CompareExchange(view->type(), oldCandidate, newCandidate,
+ view->viewDataShared(), offset, &badType);
+
+ if (badType)
+ return ReportBadArrayType(cx);
+
+ if (view->type() == Scalar::Uint32)
+ r.setNumber((double)(uint32_t)result);
+ else
+ r.setInt32(result);
+ return true;
+}
+
+bool
+js::atomics_load(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ HandleValue objv = args.get(0);
+ HandleValue idxv = args.get(1);
+ MutableHandleValue r = args.rval();
+
+ Rooted<TypedArrayObject*> view(cx, nullptr);
+ if (!GetSharedTypedArray(cx, objv, &view))
+ return false;
+ uint32_t offset;
+ if (!GetTypedArrayIndex(cx, idxv, view, &offset))
+ return false;
+
+ SharedMem<void*> viewData = view->viewDataShared();
+ switch (view->type()) {
+ case Scalar::Uint8: {
+ uint8_t v = jit::AtomicOperations::loadSeqCst(viewData.cast<uint8_t*>() + offset);
+ r.setInt32(v);
+ return true;
+ }
+ case Scalar::Int8: {
+ int8_t v = jit::AtomicOperations::loadSeqCst(viewData.cast<uint8_t*>() + offset);
+ r.setInt32(v);
+ return true;
+ }
+ case Scalar::Int16: {
+ int16_t v = jit::AtomicOperations::loadSeqCst(viewData.cast<int16_t*>() + offset);
+ r.setInt32(v);
+ return true;
+ }
+ case Scalar::Uint16: {
+ uint16_t v = jit::AtomicOperations::loadSeqCst(viewData.cast<uint16_t*>() + offset);
+ r.setInt32(v);
+ return true;
+ }
+ case Scalar::Int32: {
+ int32_t v = jit::AtomicOperations::loadSeqCst(viewData.cast<int32_t*>() + offset);
+ r.setInt32(v);
+ return true;
+ }
+ case Scalar::Uint32: {
+ uint32_t v = jit::AtomicOperations::loadSeqCst(viewData.cast<uint32_t*>() + offset);
+ r.setNumber(v);
+ return true;
+ }
+ default:
+ return ReportBadArrayType(cx);
+ }
+}
+
+enum XchgStoreOp {
+ DoExchange,
+ DoStore
+};
+
+template<XchgStoreOp op>
+static int32_t
+ExchangeOrStore(Scalar::Type viewType, int32_t numberValue, SharedMem<void*> viewData,
+ uint32_t offset, bool* badArrayType = nullptr)
+{
+#define INT_OP(ptr, value) \
+ JS_BEGIN_MACRO \
+ if (op == DoStore) \
+ jit::AtomicOperations::storeSeqCst(ptr, value); \
+ else \
+ value = jit::AtomicOperations::exchangeSeqCst(ptr, value); \
+ JS_END_MACRO
+
+ switch (viewType) {
+ case Scalar::Int8: {
+ int8_t value = (int8_t)numberValue;
+ INT_OP(viewData.cast<int8_t*>() + offset, value);
+ return value;
+ }
+ case Scalar::Uint8: {
+ uint8_t value = (uint8_t)numberValue;
+ INT_OP(viewData.cast<uint8_t*>() + offset, value);
+ return value;
+ }
+ case Scalar::Int16: {
+ int16_t value = (int16_t)numberValue;
+ INT_OP(viewData.cast<int16_t*>() + offset, value);
+ return value;
+ }
+ case Scalar::Uint16: {
+ uint16_t value = (uint16_t)numberValue;
+ INT_OP(viewData.cast<uint16_t*>() + offset, value);
+ return value;
+ }
+ case Scalar::Int32: {
+ int32_t value = numberValue;
+ INT_OP(viewData.cast<int32_t*>() + offset, value);
+ return value;
+ }
+ case Scalar::Uint32: {
+ uint32_t value = (uint32_t)numberValue;
+ INT_OP(viewData.cast<uint32_t*>() + offset, value);
+ return (int32_t)value;
+ }
+ default:
+ if (badArrayType)
+ *badArrayType = true;
+ return 0;
+ }
+#undef INT_OP
+}
+
+template<XchgStoreOp op>
+static bool
+ExchangeOrStore(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ HandleValue objv = args.get(0);
+ HandleValue idxv = args.get(1);
+ HandleValue valv = args.get(2);
+ MutableHandleValue r = args.rval();
+
+ Rooted<TypedArrayObject*> view(cx, nullptr);
+ if (!GetSharedTypedArray(cx, objv, &view))
+ return false;
+ uint32_t offset;
+ if (!GetTypedArrayIndex(cx, idxv, view, &offset))
+ return false;
+ double integerValue;
+ if (!ToInteger(cx, valv, &integerValue))
+ return false;
+
+ bool badType = false;
+ int32_t result = ExchangeOrStore<op>(view->type(), JS::ToInt32(integerValue),
+ view->viewDataShared(), offset, &badType);
+
+ if (badType)
+ return ReportBadArrayType(cx);
+
+ if (op == DoStore)
+ r.setNumber(integerValue);
+ else if (view->type() == Scalar::Uint32)
+ r.setNumber((double)(uint32_t)result);
+ else
+ r.setInt32(result);
+ return true;
+}
+
+bool
+js::atomics_store(JSContext* cx, unsigned argc, Value* vp)
+{
+ return ExchangeOrStore<DoStore>(cx, argc, vp);
+}
+
+bool
+js::atomics_exchange(JSContext* cx, unsigned argc, Value* vp)
+{
+ return ExchangeOrStore<DoExchange>(cx, argc, vp);
+}
+
+template<typename T>
+static bool
+AtomicsBinop(JSContext* cx, HandleValue objv, HandleValue idxv, HandleValue valv,
+ MutableHandleValue r)
+{
+ Rooted<TypedArrayObject*> view(cx, nullptr);
+ if (!GetSharedTypedArray(cx, objv, &view))
+ return false;
+ uint32_t offset;
+ if (!GetTypedArrayIndex(cx, idxv, view, &offset))
+ return false;
+ int32_t numberValue;
+ if (!ToInt32(cx, valv, &numberValue))
+ return false;
+
+ SharedMem<void*> viewData = view->viewDataShared();
+ switch (view->type()) {
+ case Scalar::Int8: {
+ int8_t v = (int8_t)numberValue;
+ r.setInt32(T::operate(viewData.cast<int8_t*>() + offset, v));
+ return true;
+ }
+ case Scalar::Uint8: {
+ uint8_t v = (uint8_t)numberValue;
+ r.setInt32(T::operate(viewData.cast<uint8_t*>() + offset, v));
+ return true;
+ }
+ case Scalar::Int16: {
+ int16_t v = (int16_t)numberValue;
+ r.setInt32(T::operate(viewData.cast<int16_t*>() + offset, v));
+ return true;
+ }
+ case Scalar::Uint16: {
+ uint16_t v = (uint16_t)numberValue;
+ r.setInt32(T::operate(viewData.cast<uint16_t*>() + offset, v));
+ return true;
+ }
+ case Scalar::Int32: {
+ int32_t v = numberValue;
+ r.setInt32(T::operate(viewData.cast<int32_t*>() + offset, v));
+ return true;
+ }
+ case Scalar::Uint32: {
+ uint32_t v = (uint32_t)numberValue;
+ r.setNumber((double)T::operate(viewData.cast<uint32_t*>() + offset, v));
+ return true;
+ }
+ default:
+ return ReportBadArrayType(cx);
+ }
+}
+
+#define INTEGRAL_TYPES_FOR_EACH(NAME) \
+ static int8_t operate(SharedMem<int8_t*> addr, int8_t v) { return NAME(addr, v); } \
+ static uint8_t operate(SharedMem<uint8_t*> addr, uint8_t v) { return NAME(addr, v); } \
+ static int16_t operate(SharedMem<int16_t*> addr, int16_t v) { return NAME(addr, v); } \
+ static uint16_t operate(SharedMem<uint16_t*> addr, uint16_t v) { return NAME(addr, v); } \
+ static int32_t operate(SharedMem<int32_t*> addr, int32_t v) { return NAME(addr, v); } \
+ static uint32_t operate(SharedMem<uint32_t*> addr, uint32_t v) { return NAME(addr, v); }
+
+class PerformAdd
+{
+public:
+ INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchAddSeqCst)
+ static int32_t perform(int32_t x, int32_t y) { return x + y; }
+};
+
+bool
+js::atomics_add(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return AtomicsBinop<PerformAdd>(cx, args.get(0), args.get(1), args.get(2), args.rval());
+}
+
+class PerformSub
+{
+public:
+ INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchSubSeqCst)
+ static int32_t perform(int32_t x, int32_t y) { return x - y; }
+};
+
+bool
+js::atomics_sub(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return AtomicsBinop<PerformSub>(cx, args.get(0), args.get(1), args.get(2), args.rval());
+}
+
+class PerformAnd
+{
+public:
+ INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchAndSeqCst)
+ static int32_t perform(int32_t x, int32_t y) { return x & y; }
+};
+
+bool
+js::atomics_and(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return AtomicsBinop<PerformAnd>(cx, args.get(0), args.get(1), args.get(2), args.rval());
+}
+
+class PerformOr
+{
+public:
+ INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchOrSeqCst)
+ static int32_t perform(int32_t x, int32_t y) { return x | y; }
+};
+
+bool
+js::atomics_or(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return AtomicsBinop<PerformOr>(cx, args.get(0), args.get(1), args.get(2), args.rval());
+}
+
+class PerformXor
+{
+public:
+ INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchXorSeqCst)
+ static int32_t perform(int32_t x, int32_t y) { return x ^ y; }
+};
+
+bool
+js::atomics_xor(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return AtomicsBinop<PerformXor>(cx, args.get(0), args.get(1), args.get(2), args.rval());
+}
+
+bool
+js::atomics_isLockFree(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ HandleValue v = args.get(0);
+ int32_t size;
+ if (v.isInt32()) {
+ size = v.toInt32();
+ } else {
+ double dsize;
+ if (!ToInteger(cx, v, &dsize))
+ return false;
+ if (!mozilla::NumberIsInt32(dsize, &size)) {
+ args.rval().setBoolean(false);
+ return true;
+ }
+ }
+ args.rval().setBoolean(jit::AtomicOperations::isLockfree(size));
+ return true;
+}
+
+// asm.js callouts for platforms that do not have non-word-sized
+// atomics where we don't want to inline the logic for the atomics.
+//
+// Memory will always be shared since the callouts are only called from
+// code that checks that the memory is shared.
+//
+// To test this, either run on eg Raspberry Pi Model 1, or invoke the ARM
+// simulator build with ARMHWCAP=vfp set. Do not set any other flags; other
+// vfp/neon flags force ARMv7 to be set.
+
+int32_t
+js::atomics_add_asm_callout(wasm::Instance* instance, int32_t vt, int32_t offset, int32_t value)
+{
+ SharedMem<void*> heap = instance->memoryBase().cast<void*>();
+ size_t heapLength = instance->memoryLength();
+
+ if (size_t(offset) >= heapLength)
+ return 0;
+
+ switch (Scalar::Type(vt)) {
+ case Scalar::Int8:
+ return PerformAdd::operate(heap.cast<int8_t*>() + offset, value);
+ case Scalar::Uint8:
+ return PerformAdd::operate(heap.cast<uint8_t*>() + offset, value);
+ case Scalar::Int16:
+ return PerformAdd::operate(heap.cast<int16_t*>() + (offset >> 1), value);
+ case Scalar::Uint16:
+ return PerformAdd::operate(heap.cast<uint16_t*>() + (offset >> 1), value);
+ default:
+ MOZ_CRASH("Invalid size");
+ }
+}
+
+int32_t
+js::atomics_sub_asm_callout(wasm::Instance* instance, int32_t vt, int32_t offset, int32_t value)
+{
+ SharedMem<void*> heap = instance->memoryBase().cast<void*>();
+ size_t heapLength = instance->memoryLength();
+
+ if (size_t(offset) >= heapLength)
+ return 0;
+
+ switch (Scalar::Type(vt)) {
+ case Scalar::Int8:
+ return PerformSub::operate(heap.cast<int8_t*>() + offset, value);
+ case Scalar::Uint8:
+ return PerformSub::operate(heap.cast<uint8_t*>() + offset, value);
+ case Scalar::Int16:
+ return PerformSub::operate(heap.cast<int16_t*>() + (offset >> 1), value);
+ case Scalar::Uint16:
+ return PerformSub::operate(heap.cast<uint16_t*>() + (offset >> 1), value);
+ default:
+ MOZ_CRASH("Invalid size");
+ }
+}
+
+int32_t
+js::atomics_and_asm_callout(wasm::Instance* instance, int32_t vt, int32_t offset, int32_t value)
+{
+ SharedMem<void*> heap = instance->memoryBase().cast<void*>();
+ size_t heapLength = instance->memoryLength();
+
+ if (size_t(offset) >= heapLength)
+ return 0;
+
+ switch (Scalar::Type(vt)) {
+ case Scalar::Int8:
+ return PerformAnd::operate(heap.cast<int8_t*>() + offset, value);
+ case Scalar::Uint8:
+ return PerformAnd::operate(heap.cast<uint8_t*>() + offset, value);
+ case Scalar::Int16:
+ return PerformAnd::operate(heap.cast<int16_t*>() + (offset >> 1), value);
+ case Scalar::Uint16:
+ return PerformAnd::operate(heap.cast<uint16_t*>() + (offset >> 1), value);
+ default:
+ MOZ_CRASH("Invalid size");
+ }
+}
+
+int32_t
+js::atomics_or_asm_callout(wasm::Instance* instance, int32_t vt, int32_t offset, int32_t value)
+{
+ SharedMem<void*> heap = instance->memoryBase().cast<void*>();
+ size_t heapLength = instance->memoryLength();
+
+ if (size_t(offset) >= heapLength)
+ return 0;
+
+ switch (Scalar::Type(vt)) {
+ case Scalar::Int8:
+ return PerformOr::operate(heap.cast<int8_t*>() + offset, value);
+ case Scalar::Uint8:
+ return PerformOr::operate(heap.cast<uint8_t*>() + offset, value);
+ case Scalar::Int16:
+ return PerformOr::operate(heap.cast<int16_t*>() + (offset >> 1), value);
+ case Scalar::Uint16:
+ return PerformOr::operate(heap.cast<uint16_t*>() + (offset >> 1), value);
+ default:
+ MOZ_CRASH("Invalid size");
+ }
+}
+
+int32_t
+js::atomics_xor_asm_callout(wasm::Instance* instance, int32_t vt, int32_t offset, int32_t value)
+{
+ SharedMem<void*> heap = instance->memoryBase().cast<void*>();
+ size_t heapLength = instance->memoryLength();
+
+ if (size_t(offset) >= heapLength)
+ return 0;
+
+ switch (Scalar::Type(vt)) {
+ case Scalar::Int8:
+ return PerformXor::operate(heap.cast<int8_t*>() + offset, value);
+ case Scalar::Uint8:
+ return PerformXor::operate(heap.cast<uint8_t*>() + offset, value);
+ case Scalar::Int16:
+ return PerformXor::operate(heap.cast<int16_t*>() + (offset >> 1), value);
+ case Scalar::Uint16:
+ return PerformXor::operate(heap.cast<uint16_t*>() + (offset >> 1), value);
+ default:
+ MOZ_CRASH("Invalid size");
+ }
+}
+
+int32_t
+js::atomics_xchg_asm_callout(wasm::Instance* instance, int32_t vt, int32_t offset, int32_t value)
+{
+ SharedMem<void*> heap = instance->memoryBase().cast<void*>();
+ size_t heapLength = instance->memoryLength();
+
+ if (size_t(offset) >= heapLength)
+ return 0;
+
+ switch (Scalar::Type(vt)) {
+ case Scalar::Int8:
+ return ExchangeOrStore<DoExchange>(Scalar::Int8, value, heap, offset);
+ case Scalar::Uint8:
+ return ExchangeOrStore<DoExchange>(Scalar::Uint8, value, heap, offset);
+ case Scalar::Int16:
+ return ExchangeOrStore<DoExchange>(Scalar::Int16, value, heap, offset>>1);
+ case Scalar::Uint16:
+ return ExchangeOrStore<DoExchange>(Scalar::Uint16, value, heap, offset>>1);
+ default:
+ MOZ_CRASH("Invalid size");
+ }
+}
+
+int32_t
+js::atomics_cmpxchg_asm_callout(wasm::Instance* instance, int32_t vt, int32_t offset, int32_t oldval, int32_t newval)
+{
+ SharedMem<void*> heap = instance->memoryBase().cast<void*>();
+ size_t heapLength = instance->memoryLength();
+
+ if (size_t(offset) >= heapLength)
+ return 0;
+
+ switch (Scalar::Type(vt)) {
+ case Scalar::Int8:
+ return CompareExchange(Scalar::Int8, oldval, newval, heap, offset);
+ case Scalar::Uint8:
+ return CompareExchange(Scalar::Uint8, oldval, newval, heap, offset);
+ case Scalar::Int16:
+ return CompareExchange(Scalar::Int16, oldval, newval, heap, offset>>1);
+ case Scalar::Uint16:
+ return CompareExchange(Scalar::Uint16, oldval, newval, heap, offset>>1);
+ default:
+ MOZ_CRASH("Invalid size");
+ }
+}
+
+namespace js {
+
+// Represents one waiting worker.
+//
+// The type is declared opaque in SharedArrayObject.h. Instances of
+// js::FutexWaiter are stack-allocated and linked onto a list across a
+// call to FutexRuntime::wait().
+//
+// The 'waiters' field of the SharedArrayRawBuffer points to the highest
+// priority waiter in the list, and lower priority nodes are linked through
+// the 'lower_pri' field. The 'back' field goes the other direction.
+// The list is circular, so the 'lower_pri' field of the lowest priority
+// node points to the first node in the list. The list has no dedicated
+// header node.
+
+class FutexWaiter
+{
+ public:
+ FutexWaiter(uint32_t offset, JSRuntime* rt)
+ : offset(offset),
+ rt(rt),
+ lower_pri(nullptr),
+ back(nullptr)
+ {
+ }
+
+ uint32_t offset; // int32 element index within the SharedArrayBuffer
+ JSRuntime* rt; // The runtime of the waiter
+ FutexWaiter* lower_pri; // Lower priority nodes in circular doubly-linked list of waiters
+ FutexWaiter* back; // Other direction
+};
+
+class AutoLockFutexAPI
+{
+ // We have to wrap this in a Maybe because of the way loading
+ // mozilla::Atomic pointers works.
+ mozilla::Maybe<js::UniqueLock<js::Mutex>> unique_;
+
+ public:
+ AutoLockFutexAPI() {
+ js::Mutex* lock = FutexRuntime::lock_;
+ unique_.emplace(*lock);
+ }
+
+ ~AutoLockFutexAPI() {
+ unique_.reset();
+ }
+
+ js::UniqueLock<js::Mutex>& unique() { return *unique_; }
+};
+
+} // namespace js
+
+bool
+js::atomics_wait(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ HandleValue objv = args.get(0);
+ HandleValue idxv = args.get(1);
+ HandleValue valv = args.get(2);
+ HandleValue timeoutv = args.get(3);
+ MutableHandleValue r = args.rval();
+
+ JSRuntime* rt = cx->runtime();
+
+ Rooted<TypedArrayObject*> view(cx, nullptr);
+ if (!GetSharedTypedArray(cx, objv, &view))
+ return false;
+ if (view->type() != Scalar::Int32)
+ return ReportBadArrayType(cx);
+ uint32_t offset;
+ if (!GetTypedArrayIndex(cx, idxv, view, &offset))
+ return false;
+ int32_t value;
+ if (!ToInt32(cx, valv, &value))
+ return false;
+ mozilla::Maybe<mozilla::TimeDuration> timeout;
+ if (!timeoutv.isUndefined()) {
+ double timeout_ms;
+ if (!ToNumber(cx, timeoutv, &timeout_ms))
+ return false;
+ if (!mozilla::IsNaN(timeout_ms)) {
+ if (timeout_ms < 0)
+ timeout = mozilla::Some(mozilla::TimeDuration::FromSeconds(0.0));
+ else if (!mozilla::IsInfinite(timeout_ms))
+ timeout = mozilla::Some(mozilla::TimeDuration::FromMilliseconds(timeout_ms));
+ }
+ }
+
+ if (!rt->fx.canWait())
+ return ReportCannotWait(cx);
+
+ // This lock also protects the "waiters" field on SharedArrayRawBuffer,
+ // and it provides the necessary memory fence.
+ AutoLockFutexAPI lock;
+
+ SharedMem<int32_t*>(addr) = view->viewDataShared().cast<int32_t*>() + offset;
+ if (jit::AtomicOperations::loadSafeWhenRacy(addr) != value) {
+ r.setString(cx->names().futexNotEqual);
+ return true;
+ }
+
+ Rooted<SharedArrayBufferObject*> sab(cx, view->bufferShared());
+ SharedArrayRawBuffer* sarb = sab->rawBufferObject();
+
+ FutexWaiter w(offset, rt);
+ if (FutexWaiter* waiters = sarb->waiters()) {
+ w.lower_pri = waiters;
+ w.back = waiters->back;
+ waiters->back->lower_pri = &w;
+ waiters->back = &w;
+ } else {
+ w.lower_pri = w.back = &w;
+ sarb->setWaiters(&w);
+ }
+
+ FutexRuntime::WaitResult result = FutexRuntime::FutexOK;
+ bool retval = rt->fx.wait(cx, lock.unique(), timeout, &result);
+ if (retval) {
+ switch (result) {
+ case FutexRuntime::FutexOK:
+ r.setString(cx->names().futexOK);
+ break;
+ case FutexRuntime::FutexTimedOut:
+ r.setString(cx->names().futexTimedOut);
+ break;
+ }
+ }
+
+ if (w.lower_pri == &w) {
+ sarb->setWaiters(nullptr);
+ } else {
+ w.lower_pri->back = w.back;
+ w.back->lower_pri = w.lower_pri;
+ if (sarb->waiters() == &w)
+ sarb->setWaiters(w.lower_pri);
+ }
+ return retval;
+}
+
+bool
+js::atomics_wake(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ HandleValue objv = args.get(0);
+ HandleValue idxv = args.get(1);
+ HandleValue countv = args.get(2);
+ MutableHandleValue r = args.rval();
+
+ Rooted<TypedArrayObject*> view(cx, nullptr);
+ if (!GetSharedTypedArray(cx, objv, &view))
+ return false;
+ if (view->type() != Scalar::Int32)
+ return ReportBadArrayType(cx);
+ uint32_t offset;
+ if (!GetTypedArrayIndex(cx, idxv, view, &offset))
+ return false;
+ double count;
+ if (countv.isUndefined()) {
+ count = mozilla::PositiveInfinity<double>();
+ } else {
+ if (!ToInteger(cx, countv, &count))
+ return false;
+ if (count < 0.0)
+ count = 0.0;
+ }
+
+ AutoLockFutexAPI lock;
+
+ Rooted<SharedArrayBufferObject*> sab(cx, view->bufferShared());
+ SharedArrayRawBuffer* sarb = sab->rawBufferObject();
+ int32_t woken = 0;
+
+ FutexWaiter* waiters = sarb->waiters();
+ if (waiters && count > 0) {
+ FutexWaiter* iter = waiters;
+ do {
+ FutexWaiter* c = iter;
+ iter = iter->lower_pri;
+ if (c->offset != offset || !c->rt->fx.isWaiting())
+ continue;
+ c->rt->fx.wake(FutexRuntime::WakeExplicit);
+ ++woken;
+ --count;
+ } while (count > 0 && iter != waiters);
+ }
+
+ r.setInt32(woken);
+ return true;
+}
+
+/* static */ bool
+js::FutexRuntime::initialize()
+{
+ MOZ_ASSERT(!lock_);
+ lock_ = js_new<js::Mutex>(mutexid::FutexRuntime);
+ return lock_ != nullptr;
+}
+
+/* static */ void
+js::FutexRuntime::destroy()
+{
+ if (lock_) {
+ js::Mutex* lock = lock_;
+ js_delete(lock);
+ lock_ = nullptr;
+ }
+}
+
+/* static */ void
+js::FutexRuntime::lock()
+{
+ // Load the atomic pointer.
+ js::Mutex* lock = lock_;
+
+ lock->lock();
+}
+
+/* static */ mozilla::Atomic<js::Mutex*> FutexRuntime::lock_;
+
+/* static */ void
+js::FutexRuntime::unlock()
+{
+ // Load the atomic pointer.
+ js::Mutex* lock = lock_;
+
+ lock->unlock();
+}
+
+js::FutexRuntime::FutexRuntime()
+ : cond_(nullptr),
+ state_(Idle),
+ canWait_(false)
+{
+}
+
+bool
+js::FutexRuntime::initInstance()
+{
+ MOZ_ASSERT(lock_);
+ cond_ = js_new<js::ConditionVariable>();
+ return cond_ != nullptr;
+}
+
+void
+js::FutexRuntime::destroyInstance()
+{
+ if (cond_)
+ js_delete(cond_);
+}
+
+bool
+js::FutexRuntime::isWaiting()
+{
+ // When a worker is awoken for an interrupt it goes into state
+ // WaitingNotifiedForInterrupt for a short time before it actually
+ // wakes up and goes into WaitingInterrupted. In those states the
+ // worker is still waiting, and if an explicit wake arrives the
+ // worker transitions to Woken. See further comments in
+ // FutexRuntime::wait().
+ return state_ == Waiting || state_ == WaitingInterrupted || state_ == WaitingNotifiedForInterrupt;
+}
+
+bool
+js::FutexRuntime::wait(JSContext* cx, js::UniqueLock<js::Mutex>& locked,
+ mozilla::Maybe<mozilla::TimeDuration>& timeout, WaitResult* result)
+{
+ MOZ_ASSERT(&cx->runtime()->fx == this);
+ MOZ_ASSERT(cx->runtime()->fx.canWait());
+ MOZ_ASSERT(state_ == Idle || state_ == WaitingInterrupted);
+
+ // Disallow waiting when a runtime is processing an interrupt.
+ // See explanation below.
+
+ if (state_ == WaitingInterrupted) {
+ UnlockGuard<Mutex> unlock(locked);
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ATOMICS_WAIT_NOT_ALLOWED);
+ return false;
+ }
+
+ const bool isTimed = timeout.isSome();
+
+ auto finalEnd = timeout.map([](mozilla::TimeDuration& timeout) {
+ return mozilla::TimeStamp::Now() + timeout;
+ });
+
+
+ // 4000s is about the longest timeout slice that is guaranteed to
+ // work cross-platform.
+ auto maxSlice = mozilla::TimeDuration::FromSeconds(4000.0);
+
+ bool retval = true;
+
+ for (;;) {
+ // If we are doing a timed wait, calculate the end time for this wait
+ // slice.
+ auto sliceEnd = finalEnd.map([&](mozilla::TimeStamp& finalEnd) {
+ auto sliceEnd = mozilla::TimeStamp::Now() + maxSlice;
+ if (finalEnd < sliceEnd)
+ sliceEnd = finalEnd;
+ return sliceEnd;
+ });
+
+ state_ = Waiting;
+
+ if (isTimed) {
+ mozilla::Unused << cond_->wait_until(locked, *sliceEnd);
+ } else {
+ cond_->wait(locked);
+ }
+
+ switch (state_) {
+ case FutexRuntime::Waiting:
+ // Timeout or spurious wakeup.
+ if (isTimed) {
+ auto now = mozilla::TimeStamp::Now();
+ if (now >= *finalEnd) {
+ *result = FutexTimedOut;
+ goto finished;
+ }
+ }
+ break;
+
+ case FutexRuntime::Woken:
+ *result = FutexOK;
+ goto finished;
+
+ case FutexRuntime::WaitingNotifiedForInterrupt:
+ // The interrupt handler may reenter the engine. In that case
+ // there are two complications:
+ //
+ // - The waiting thread is not actually waiting on the
+ // condition variable so we have to record that it
+ // should be woken when the interrupt handler returns.
+ // To that end, we flag the thread as interrupted around
+ // the interrupt and check state_ when the interrupt
+ // handler returns. A wake() call that reaches the
+ // runtime during the interrupt sets state_ to Woken.
+ //
+ // - It is in principle possible for wait() to be
+ // reentered on the same thread/runtime and waiting on the
+ // same location and to yet again be interrupted and enter
+ // the interrupt handler. In this case, it is important
+ // that when another agent wakes waiters, all waiters using
+ // the same runtime on the same location are woken in LIFO
+ // order; FIFO may be the required order, but FIFO would
+ // fail to wake up the innermost call. Interrupts are
+ // outside any spec anyway. Also, several such suspended
+ // waiters may be woken at a time.
+ //
+ // For the time being we disallow waiting from within code
+ // that runs from within an interrupt handler; this may
+ // occasionally (very rarely) be surprising but is
+ // expedient. Other solutions exist, see bug #1131943. The
+ // code that performs the check is above, at the head of
+ // this function.
+
+ state_ = WaitingInterrupted;
+ {
+ UnlockGuard<Mutex> unlock(locked);
+ retval = cx->runtime()->handleInterrupt(cx);
+ }
+ if (!retval)
+ goto finished;
+ if (state_ == Woken) {
+ *result = FutexOK;
+ goto finished;
+ }
+ break;
+
+ default:
+ MOZ_CRASH("Bad FutexState in wait()");
+ }
+ }
+finished:
+ state_ = Idle;
+ return retval;
+}
+
+void
+js::FutexRuntime::wake(WakeReason reason)
+{
+ MOZ_ASSERT(isWaiting());
+
+ if ((state_ == WaitingInterrupted || state_ == WaitingNotifiedForInterrupt) && reason == WakeExplicit) {
+ state_ = Woken;
+ return;
+ }
+ switch (reason) {
+ case WakeExplicit:
+ state_ = Woken;
+ break;
+ case WakeForJSInterrupt:
+ if (state_ == WaitingNotifiedForInterrupt)
+ return;
+ state_ = WaitingNotifiedForInterrupt;
+ break;
+ default:
+ MOZ_CRASH("bad WakeReason in FutexRuntime::wake()");
+ }
+ cond_->notify_all();
+}
+
+const JSFunctionSpec AtomicsMethods[] = {
+ JS_INLINABLE_FN("compareExchange", atomics_compareExchange, 4,0, AtomicsCompareExchange),
+ JS_INLINABLE_FN("load", atomics_load, 2,0, AtomicsLoad),
+ JS_INLINABLE_FN("store", atomics_store, 3,0, AtomicsStore),
+ JS_INLINABLE_FN("exchange", atomics_exchange, 3,0, AtomicsExchange),
+ JS_INLINABLE_FN("add", atomics_add, 3,0, AtomicsAdd),
+ JS_INLINABLE_FN("sub", atomics_sub, 3,0, AtomicsSub),
+ JS_INLINABLE_FN("and", atomics_and, 3,0, AtomicsAnd),
+ JS_INLINABLE_FN("or", atomics_or, 3,0, AtomicsOr),
+ JS_INLINABLE_FN("xor", atomics_xor, 3,0, AtomicsXor),
+ JS_INLINABLE_FN("isLockFree", atomics_isLockFree, 1,0, AtomicsIsLockFree),
+ JS_FN("wait", atomics_wait, 4,0),
+ JS_FN("wake", atomics_wake, 3,0),
+ JS_FS_END
+};
+
+JSObject*
+AtomicsObject::initClass(JSContext* cx, Handle<GlobalObject*> global)
+{
+ // Create Atomics Object.
+ RootedObject objProto(cx, global->getOrCreateObjectPrototype(cx));
+ if (!objProto)
+ return nullptr;
+ RootedObject Atomics(cx, NewObjectWithGivenProto(cx, &AtomicsObject::class_, objProto,
+ SingletonObject));
+ if (!Atomics)
+ return nullptr;
+
+ if (!JS_DefineFunctions(cx, Atomics, AtomicsMethods))
+ return nullptr;
+
+ RootedValue AtomicsValue(cx, ObjectValue(*Atomics));
+
+ // Everything is set up, install Atomics on the global object.
+ if (!DefineProperty(cx, global, cx->names().Atomics, AtomicsValue, nullptr, nullptr,
+ JSPROP_RESOLVING))
+ {
+ return nullptr;
+ }
+
+ global->setConstructor(JSProto_Atomics, AtomicsValue);
+ return Atomics;
+}
+
+JSObject*
+js::InitAtomicsClass(JSContext* cx, HandleObject obj)
+{
+ MOZ_ASSERT(obj->is<GlobalObject>());
+ Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
+ return AtomicsObject::initClass(cx, global);
+}
+
+#undef CXX11_ATOMICS
+#undef GNU_ATOMICS