Bug 1377576 - Preparatory refactoring and extensions for wasm atomics. r=bbouvier
authorLars T Hansen <lhansen@mozilla.com>
Wed, 05 Jul 2017 12:22:13 -0700
changeset 437649 6d224bf532b536566913df59c6bbb84ad497d23c
parent 437648 de907393db020ec66d981c400b855cdc30ecfc38
child 437650 443ff6e4b7f1981efbc327057d8c988c0b063ca8
push id117
push userfmarier@mozilla.com
push dateTue, 28 Nov 2017 20:17:16 +0000
reviewersbbouvier
bugs1377576
milestone59.0a1
Bug 1377576 - Preparatory refactoring and extensions for wasm atomics. r=bbouvier
js/src/builtin/AtomicsObject.cpp
js/src/builtin/AtomicsObject.h
js/src/jit/IonTypes.h
js/src/jit/arm/Simulator-arm.cpp
js/src/js.msg
js/src/wasm/WasmBuiltins.cpp
js/src/wasm/WasmFrameIter.cpp
js/src/wasm/WasmInstance.cpp
js/src/wasm/WasmInstance.h
js/src/wasm/WasmTypes.h
--- a/js/src/builtin/AtomicsObject.cpp
+++ b/js/src/builtin/AtomicsObject.cpp
@@ -43,16 +43,17 @@
  *
  * 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/CheckedInt.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/ScopeExit.h"
 #include "mozilla/Unused.h"
 
 #include "jsapi.h"
 #include "jsfriendapi.h"
 #include "jsnum.h"
@@ -86,23 +87,16 @@ ReportOutOfRange(JSContext* cx)
 {
     // Use JSMSG_BAD_INDEX here, it is what ToIndex uses for some cases that it
     // reports directly.
     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>());
@@ -727,16 +721,77 @@ class AutoLockFutexAPI
         unique_.reset();
     }
 
     js::UniqueLock<js::Mutex>& unique() { return *unique_; }
 };
 
 } // namespace js
 
+template<typename T>
+static FutexThread::WaitResult
+AtomicsWait(JSContext* cx, SharedArrayRawBuffer* sarb, uint32_t byteOffset, T value,
+            const mozilla::Maybe<mozilla::TimeDuration>& timeout)
+{
+    // Validation and other guards should ensure that this does not happen.
+    MOZ_ASSERT(sarb, "wait is only applicable to shared memory");
+
+    if (!cx->fx.canWait()) {
+        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ATOMICS_WAIT_NOT_ALLOWED);
+        return FutexThread::WaitResult::Error;
+    }
+
+    SharedMem<T*> addr = sarb->dataPointerShared().cast<T*>() + (byteOffset / sizeof(T));
+
+    // This lock also protects the "waiters" field on SharedArrayRawBuffer,
+    // and it provides the necessary memory fence.
+    AutoLockFutexAPI lock;
+
+    if (jit::AtomicOperations::loadSafeWhenRacy(addr) != value)
+        return FutexThread::WaitResult::NotEqual;
+
+    FutexWaiter w(byteOffset, cx);
+    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);
+    }
+
+    FutexThread::WaitResult retval = cx->fx.wait(cx, lock.unique(), timeout);
+
+    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;
+}
+
+FutexThread::WaitResult
+js::atomics_wait_impl(JSContext* cx, SharedArrayRawBuffer* sarb, uint32_t byteOffset,
+                      int32_t value, const mozilla::Maybe<mozilla::TimeDuration>& timeout)
+{
+    return AtomicsWait(cx, sarb, byteOffset, value, timeout);
+}
+
+FutexThread::WaitResult
+js::atomics_wait_impl(JSContext* cx, SharedArrayRawBuffer* sarb, uint32_t byteOffset,
+                      int64_t value, const mozilla::Maybe<mozilla::TimeDuration>& timeout)
+{
+    return AtomicsWait(cx, sarb, byteOffset, value, timeout);
+}
+
 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);
@@ -761,65 +816,72 @@ js::atomics_wait(JSContext* cx, unsigned
         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 (!cx->fx.canWait())
-        return ReportCannotWait(cx);
+    Rooted<SharedArrayBufferObject*> sab(cx, view->bufferShared());
+    // The computation will not overflow because range checks have been
+    // performed.
+    uint32_t byteOffset = offset * sizeof(int32_t) +
+                          (view->viewDataShared().cast<uint8_t*>().unwrap(/* arithmetic */) -
+                           sab->dataPointerShared().unwrap(/* arithmetic */));
 
-    // This lock also protects the "waiters" field on SharedArrayRawBuffer,
-    // and it provides the necessary memory fence.
+    switch (atomics_wait_impl(cx, sab->rawBufferObject(), byteOffset, value, timeout)) {
+      case FutexThread::WaitResult::NotEqual:
+        r.setString(cx->names().futexNotEqual);
+        return true;
+      case FutexThread::WaitResult::OK:
+        r.setString(cx->names().futexOK);
+        return true;
+      case FutexThread::WaitResult::TimedOut:
+        r.setString(cx->names().futexTimedOut);
+        return true;
+      case FutexThread::WaitResult::Error:
+        return false;
+      default:
+        MOZ_CRASH("Should not happen");
+    }
+}
+
+int64_t
+js::atomics_wake_impl(SharedArrayRawBuffer* sarb, uint32_t byteOffset, int64_t count)
+{
+    // Validation should ensure this does not happen.
+    MOZ_ASSERT(sarb, "wake is only applicable to shared memory");
+
     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;
+    int64_t woken = 0;
+
+    FutexWaiter* waiters = sarb->waiters();
+    if (waiters && count) {
+        FutexWaiter* iter = waiters;
+        do {
+            FutexWaiter* c = iter;
+            iter = iter->lower_pri;
+            if (c->offset != byteOffset || !c->cx->fx.isWaiting())
+                continue;
+            c->cx->fx.wake(FutexThread::WakeExplicit);
+            // Overflow will be a problem only in two cases:
+            // (1) 128-bit systems with substantially more than 2^64 bytes of
+            //     memory per process, and a very lightweight
+            //     Atomics.waitAsync().  Obviously a future problem.
+            // (2) Bugs.
+            MOZ_RELEASE_ASSERT(woken < INT64_MAX);
+            ++woken;
+            if (count > 0)
+                --count;
+        } while (count && iter != waiters);
     }
 
-    Rooted<SharedArrayBufferObject*> sab(cx, view->bufferShared());
-    SharedArrayRawBuffer* sarb = sab->rawBufferObject();
-
-    FutexWaiter w(offset, cx);
-    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);
-    }
-
-    FutexThread::WaitResult result = FutexThread::FutexOK;
-    bool retval = cx->fx.wait(cx, lock.unique(), timeout, &result);
-    if (retval) {
-        switch (result) {
-          case FutexThread::FutexOK:
-            r.setString(cx->names().futexOK);
-            break;
-          case FutexThread::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;
+    return woken;
 }
 
 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);
@@ -829,47 +891,37 @@ js::atomics_wake(JSContext* cx, unsigned
     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;
+    int64_t count;
     if (countv.isUndefined()) {
-        count = mozilla::PositiveInfinity<double>();
+        count = -1;
     } else {
-        if (!ToInteger(cx, countv, &count))
+        double dcount;
+        if (!ToInteger(cx, countv, &dcount))
             return false;
-        if (count < 0.0)
-            count = 0.0;
+        if (dcount < 0.0)
+            dcount = 0.0;
+        count = dcount > INT64_MAX ? -1 : int64_t(dcount);
     }
 
-    AutoLockFutexAPI lock;
-
     Rooted<SharedArrayBufferObject*> sab(cx, view->bufferShared());
-    SharedArrayRawBuffer* sarb = sab->rawBufferObject();
-    int32_t woken = 0;
+    // The computation will not overflow because range checks have been
+    // performed.
+    uint32_t byteOffset = offset * sizeof(int32_t) +
+                          (view->viewDataShared().cast<uint8_t*>().unwrap(/* arithmetic */) -
+                           sab->dataPointerShared().unwrap(/* arithmetic */));
 
-    FutexWaiter* waiters = sarb->waiters();
-    if (waiters && count > 0) {
-        FutexWaiter* iter = waiters;
-        do {
-            FutexWaiter* c = iter;
-            iter = iter->lower_pri;
-            if (c->offset != offset || !c->cx->fx.isWaiting())
-                continue;
-            c->cx->fx.wake(FutexThread::WakeExplicit);
-            ++woken;
-            --count;
-        } while (count > 0 && iter != waiters);
-    }
+    r.setNumber(double(atomics_wake_impl(sab->rawBufferObject(), byteOffset, count)));
 
-    r.setInt32(woken);
     return true;
 }
 
 /* static */ bool
 js::FutexThread::initialize()
 {
     MOZ_ASSERT(!lock_);
     lock_ = js_new<js::Mutex>(mutexid::FutexThread);
@@ -935,41 +987,41 @@ js::FutexThread::isWaiting()
     // 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
     // FutexThread::wait().
     return state_ == Waiting || state_ == WaitingInterrupted || state_ == WaitingNotifiedForInterrupt;
 }
 
-bool
+FutexThread::WaitResult
 js::FutexThread::wait(JSContext* cx, js::UniqueLock<js::Mutex>& locked,
-                       mozilla::Maybe<mozilla::TimeDuration>& timeout, WaitResult* result)
+                      const mozilla::Maybe<mozilla::TimeDuration>& timeout)
 {
     MOZ_ASSERT(&cx->fx == this);
     MOZ_ASSERT(cx->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;
+        return WaitResult::Error;
     }
 
     // Go back to Idle after returning.
     auto onFinish = mozilla::MakeScopeExit([&] {
         state_ = Idle;
     });
 
     const bool isTimed = timeout.isSome();
 
-    auto finalEnd = timeout.map([](mozilla::TimeDuration& timeout) {
+    auto finalEnd = timeout.map([](const 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);
 
@@ -991,26 +1043,23 @@ js::FutexThread::wait(JSContext* cx, js:
             cond_->wait(locked);
         }
 
         switch (state_) {
           case FutexThread::Waiting:
             // Timeout or spurious wakeup.
             if (isTimed) {
                 auto now = mozilla::TimeStamp::Now();
-                if (now >= *finalEnd) {
-                    *result = FutexTimedOut;
-                    return true;
-                }
+                if (now >= *finalEnd)
+                    return WaitResult::TimedOut;
             }
             break;
 
           case FutexThread::Woken:
-            *result = FutexOK;
-            return true;
+            return WaitResult::OK;
 
           case FutexThread::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.
@@ -1036,22 +1085,20 @@ js::FutexThread::wait(JSContext* cx, js:
             //   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);
                 if (!cx->handleInterrupt())
-                    return false;
+                    return WaitResult::Error;
             }
-            if (state_ == Woken) {
-                *result = FutexOK;
-                return true;
-            }
+            if (state_ == Woken)
+                return WaitResult::OK;
             break;
 
           default:
             MOZ_CRASH("Bad FutexState in wait()");
         }
     }
 }
 
--- a/js/src/builtin/AtomicsObject.h
+++ b/js/src/builtin/AtomicsObject.h
@@ -64,35 +64,37 @@ public:
     void destroyInstance();
 
     // Parameters to wake().
     enum WakeReason {
         WakeExplicit,           // Being asked to wake up by another thread
         WakeForJSInterrupt      // Interrupt requested
     };
 
-    // Result code from wait().
-    enum WaitResult {
-        FutexOK,
-        FutexTimedOut
+    // Result codes from wait() and atomics_wait_impl().
+    enum class WaitResult {
+        Error,                  // Error has been reported, just propagate error signal
+        NotEqual,               // Did not wait because the values differed
+        OK,                     // Waited and was woken
+        TimedOut                // Waited and timed out
     };
 
     // Block the calling thread and wait.
     //
     // The futex lock must be held around this call.
     //
     // The timeout is the number of milliseconds, with fractional
     // times allowed; specify mozilla::Nothing() for an indefinite
     // wait.
     //
     // wait() will not wake up spuriously.  It will return true and
     // set *result to a return code appropriate for
     // Atomics.wait() on success, and return false on error.
-    MOZ_MUST_USE bool wait(JSContext* cx, js::UniqueLock<js::Mutex>& locked,
-                           mozilla::Maybe<mozilla::TimeDuration>& timeout, WaitResult* result);
+    MOZ_MUST_USE WaitResult wait(JSContext* cx, js::UniqueLock<js::Mutex>& locked,
+                                 const mozilla::Maybe<mozilla::TimeDuration>& timeout);
 
     // Wake the thread this is associated with.
     //
     // The futex lock must be held around this call.  (The sleeping
     // thread will not wake up until the caller of Atomics.wake()
     // releases the lock.)
     //
     // If the thread is not waiting then this method does nothing.
@@ -146,11 +148,28 @@ public:
 
     // A flag that controls whether waiting is allowed.
     ThreadLocalData<bool> canWait_;
 };
 
 JSObject*
 InitAtomicsClass(JSContext* cx, HandleObject obj);
 
+// Go to sleep if the int32_t value at the given address equals `value`.
+MOZ_MUST_USE FutexThread::WaitResult
+atomics_wait_impl(JSContext* cx, SharedArrayRawBuffer* sarb, uint32_t byteOffset, int32_t value,
+                  const mozilla::Maybe<mozilla::TimeDuration>& timeout);
+
+// Go to sleep if the int64_t value at the given address equals `value`.
+MOZ_MUST_USE FutexThread::WaitResult
+atomics_wait_impl(JSContext* cx, SharedArrayRawBuffer* sarb, uint32_t byteOffset, int64_t value,
+                  const mozilla::Maybe<mozilla::TimeDuration>& timeout);
+
+// Wake some waiters on the given address.  If `count` is negative then wake
+// all.  The return value is nonnegative and is the number of waiters woken.  If
+// the number of waiters woken exceeds INT64_MAX then this never returns.  If
+// `count` is nonnegative then the return value is never greater than `count`.
+MOZ_MUST_USE int64_t
+atomics_wake_impl(SharedArrayRawBuffer* sarb, uint32_t byteOffset, int64_t count);
+
 }  /* namespace js */
 
 #endif /* builtin_AtomicsObject_h */
--- a/js/src/jit/IonTypes.h
+++ b/js/src/jit/IonTypes.h
@@ -846,17 +846,29 @@ enum ABIFunctionType
        (ArgType_General << (ArgType_Shift * 2)) |
        (ArgType_Double  << (ArgType_Shift * 3)),
 
     // int f(int, double, int, int)
     Args_Int_IntDoubleIntInt = Args_General0 |
         (ArgType_General << (ArgType_Shift * 1)) |
         (ArgType_General << (ArgType_Shift * 2)) |
         (ArgType_Double  << (ArgType_Shift * 3)) |
-        (ArgType_General << (ArgType_Shift * 4))
+        (ArgType_General << (ArgType_Shift * 4)),
+
+    Args_Int_GeneralGeneralGeneralInt64 = Args_General0 |
+        (ArgType_General << (ArgType_Shift * 1)) |
+        (ArgType_General << (ArgType_Shift * 2)) |
+        (ArgType_General << (ArgType_Shift * 3)) |
+        (ArgType_Int64 << (ArgType_Shift * 4)),
+
+    Args_Int_GeneralGeneralInt64Int64 = Args_General0 |
+        (ArgType_General << (ArgType_Shift * 1)) |
+        (ArgType_General << (ArgType_Shift * 2)) |
+        (ArgType_Int64 << (ArgType_Shift * 3)) |
+        (ArgType_Int64 << (ArgType_Shift * 4))
 };
 
 enum class BarrierKind : uint32_t {
     // No barrier is needed.
     NoBarrier,
 
     // The barrier only has to check the value's type tag is in the TypeSet.
     // Specific object types don't have to be checked.
--- a/js/src/jit/arm/Simulator-arm.cpp
+++ b/js/src/jit/arm/Simulator-arm.cpp
@@ -2490,16 +2490,20 @@ typedef int64_t (*Prototype_General4)(in
 typedef int64_t (*Prototype_General5)(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3,
                                       int32_t arg4);
 typedef int64_t (*Prototype_General6)(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3,
                                       int32_t arg4, int32_t arg5);
 typedef int64_t (*Prototype_General7)(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3,
                                       int32_t arg4, int32_t arg5, int32_t arg6);
 typedef int64_t (*Prototype_General8)(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3,
                                       int32_t arg4, int32_t arg5, int32_t arg6, int32_t arg7);
+typedef int64_t (*Prototype_GeneralGeneralGeneralInt64)(int32_t arg0, int32_t arg1, int32_t arg2,
+                                                        int64_t arg3);
+typedef int64_t (*Prototype_GeneralGeneralInt64Int64)(int32_t arg0, int32_t arg1, int64_t arg2,
+                                                      int64_t arg3);
 
 typedef double (*Prototype_Double_None)();
 typedef double (*Prototype_Double_Double)(double arg0);
 typedef double (*Prototype_Double_Int)(int32_t arg0);
 typedef double (*Prototype_Double_IntInt)(int32_t arg0, int32_t arg1);
 typedef int32_t (*Prototype_Int_Double)(double arg0);
 typedef int64_t (*Prototype_Int64_Double)(double arg0);
 typedef int32_t (*Prototype_Int_DoubleIntInt)(double arg0, int32_t arg1, int32_t arg2);
@@ -2541,16 +2545,23 @@ Simulator::scratchVolatileRegisters(bool
         uint64_t scratch_value_d = 0x5a5a5a5a5a5a5a5aLU ^ uint64_t(icount_) ^ (uint64_t(icount_) << 30);
         for (uint32_t i = d0; i < d8; i++)
             set_d_register(i, &scratch_value_d);
         for (uint32_t i = d16; i < FloatRegisters::TotalPhys; i++)
             set_d_register(i, &scratch_value_d);
     }
 }
 
+static int64_t
+MakeInt64(int32_t first, int32_t second)
+{
+    // Little-endian order.
+    return ((int64_t)second << 32) | (uint32_t)first;
+}
+
 // Software interrupt instructions are used by the simulator to call into C++.
 void
 Simulator::softwareInterrupt(SimInstruction* instr)
 {
     int svc = instr->svcValue();
     switch (svc) {
       case kCallRtRedirected: {
         Redirection* redirection = Redirection::FromSwiInstruction(instr);
@@ -2641,16 +2652,33 @@ Simulator::softwareInterrupt(SimInstruct
             Prototype_General8 target = reinterpret_cast<Prototype_General8>(external);
             int32_t arg6 = stack_pointer[2];
             int32_t arg7 = stack_pointer[3];
             int64_t result = target(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7);
             scratchVolatileRegisters(/* scratchFloat = true */);
             setCallResult(result);
             break;
           }
+          case Args_Int_GeneralGeneralGeneralInt64: {
+            Prototype_GeneralGeneralGeneralInt64 target =
+                reinterpret_cast<Prototype_GeneralGeneralGeneralInt64>(external);
+            // The int64 arg is not split across register and stack
+            int64_t result = target(arg0, arg1, arg2, MakeInt64(arg4, arg5));
+            scratchVolatileRegisters(/* scratchFloat = true */);
+            setCallResult(result);
+            break;
+          }
+          case Args_Int_GeneralGeneralInt64Int64: {
+            Prototype_GeneralGeneralInt64Int64 target =
+                reinterpret_cast<Prototype_GeneralGeneralInt64Int64>(external);
+            int64_t result = target(arg0, arg1, MakeInt64(arg2, arg3), MakeInt64(arg4, arg5));
+            scratchVolatileRegisters(/* scratchFloat = true */);
+            setCallResult(result);
+            break;
+          }
           case Args_Int64_Double: {
             double dval0, dval1;
             int32_t ival;
             getFpArgs(&dval0, &dval1, &ival);
             Prototype_Int64_Double target = reinterpret_cast<Prototype_Int64_Double>(external);
             int64_t result = target(dval0);
             scratchVolatileRegisters(/* scratchFloat = true */);
             setCallResult(result);
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -370,16 +370,17 @@ MSG_DEF(JSMSG_WASM_NO_SHMEM_LINK,      0
 MSG_DEF(JSMSG_WASM_IND_CALL_TO_NULL,   0, JSEXN_WASMRUNTIMEERROR, "indirect call to null")
 MSG_DEF(JSMSG_WASM_IND_CALL_BAD_SIG,   0, JSEXN_WASMRUNTIMEERROR, "indirect call signature mismatch")
 MSG_DEF(JSMSG_WASM_UNREACHABLE,        0, JSEXN_WASMRUNTIMEERROR, "unreachable executed")
 MSG_DEF(JSMSG_WASM_INTEGER_OVERFLOW,   0, JSEXN_WASMRUNTIMEERROR, "integer overflow")
 MSG_DEF(JSMSG_WASM_INVALID_CONVERSION, 0, JSEXN_WASMRUNTIMEERROR, "invalid conversion to integer")
 MSG_DEF(JSMSG_WASM_INT_DIVIDE_BY_ZERO, 0, JSEXN_WASMRUNTIMEERROR, "integer divide by zero")
 MSG_DEF(JSMSG_WASM_OUT_OF_BOUNDS,      0, JSEXN_WASMRUNTIMEERROR, "index out of bounds")
 MSG_DEF(JSMSG_WASM_UNALIGNED_ACCESS,   0, JSEXN_WASMRUNTIMEERROR, "unaligned memory access")
+MSG_DEF(JSMSG_WASM_WAKE_OVERFLOW,      0, JSEXN_WASMRUNTIMEERROR, "too many woken agents")
 MSG_DEF(JSMSG_WASM_BAD_UINT32,         2, JSEXN_RANGEERR,    "bad {0} {1}")
 MSG_DEF(JSMSG_WASM_BAD_GROW,           1, JSEXN_RANGEERR,    "failed to grow {0}")
 MSG_DEF(JSMSG_WASM_BAD_BUF_ARG,        0, JSEXN_TYPEERR,     "first argument must be an ArrayBuffer or typed array object")
 MSG_DEF(JSMSG_WASM_BAD_MOD_ARG,        0, JSEXN_TYPEERR,     "first argument must be a WebAssembly.Module")
 MSG_DEF(JSMSG_WASM_BAD_BUF_MOD_ARG,    0, JSEXN_TYPEERR,     "first argument must be a WebAssembly.Module, ArrayBuffer or typed array object")
 MSG_DEF(JSMSG_WASM_BAD_DESC_ARG,       1, JSEXN_TYPEERR,     "first argument must be a {0} descriptor")
 MSG_DEF(JSMSG_WASM_BAD_ELEMENT,        0, JSEXN_TYPEERR,     "\"element\" property of table descriptor must be \"anyfunc\"")
 MSG_DEF(JSMSG_WASM_BAD_IMPORT_ARG,     0, JSEXN_TYPEERR,     "second argument must be an object")
--- a/js/src/wasm/WasmBuiltins.cpp
+++ b/js/src/wasm/WasmBuiltins.cpp
@@ -262,19 +262,25 @@ WasmReportTrap(int32_t trapIndex)
         errorNumber = JSMSG_WASM_IND_CALL_BAD_SIG;
         break;
       case Trap::ImpreciseSimdConversion:
         errorNumber = JSMSG_SIMD_FAILED_CONVERSION;
         break;
       case Trap::OutOfBounds:
         errorNumber = JSMSG_WASM_OUT_OF_BOUNDS;
         break;
+      case Trap::UnalignedAccess:
+        errorNumber = JSMSG_WASM_UNALIGNED_ACCESS;
+        break;
       case Trap::StackOverflow:
         errorNumber = JSMSG_OVER_RECURSED;
         break;
+      case Trap::ThrowReported:
+        // Error was already reported under another name.
+        return;
       default:
         MOZ_CRASH("unexpected trap");
     }
 
     JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, errorNumber);
 }
 
 static void
@@ -580,16 +586,25 @@ AddressOf(SymbolicAddress imm, ABIFuncti
         *abiType = Args_Double_DoubleDouble;
         return FuncCast(ecmaAtan2, *abiType);
       case SymbolicAddress::GrowMemory:
         *abiType = Args_General2;
         return FuncCast(Instance::growMemory_i32, *abiType);
       case SymbolicAddress::CurrentMemory:
         *abiType = Args_General1;
         return FuncCast(Instance::currentMemory_i32, *abiType);
+      case SymbolicAddress::WaitI32:
+        *abiType = Args_Int_GeneralGeneralGeneralInt64;
+        return FuncCast(Instance::wait_i32, *abiType);
+      case SymbolicAddress::WaitI64:
+        *abiType = Args_Int_GeneralGeneralInt64Int64;
+        return FuncCast(Instance::wait_i64, *abiType);
+      case SymbolicAddress::Wake:
+        *abiType = Args_General2;
+        return FuncCast(Instance::wake, *abiType);
       case SymbolicAddress::Limit:
         break;
     }
 
     MOZ_CRASH("Bad SymbolicAddress");
 }
 
 bool
@@ -649,16 +664,19 @@ wasm::NeedsBuiltinThunk(SymbolicAddress 
       case SymbolicAddress::NearbyIntD:
       case SymbolicAddress::NearbyIntF:
       case SymbolicAddress::ExpD:
       case SymbolicAddress::LogD:
       case SymbolicAddress::PowD:
       case SymbolicAddress::ATan2D:
       case SymbolicAddress::GrowMemory:
       case SymbolicAddress::CurrentMemory:
+      case SymbolicAddress::WaitI32:
+      case SymbolicAddress::WaitI64:
+      case SymbolicAddress::Wake:
         return true;
       case SymbolicAddress::Limit:
         break;
     }
 
     MOZ_CRASH("unexpected symbolic address");
 }
 
--- a/js/src/wasm/WasmFrameIter.cpp
+++ b/js/src/wasm/WasmFrameIter.cpp
@@ -1034,16 +1034,22 @@ ThunkedNativeToDescription(SymbolicAddre
       case SymbolicAddress::PowD:
         return "call to asm.js native f64 Math.pow";
       case SymbolicAddress::ATan2D:
         return "call to asm.js native f64 Math.atan2";
       case SymbolicAddress::GrowMemory:
         return "call to native grow_memory (in wasm)";
       case SymbolicAddress::CurrentMemory:
         return "call to native current_memory (in wasm)";
+      case SymbolicAddress::WaitI32:
+        return "call to native i32.wait (in wasm)";
+      case SymbolicAddress::WaitI64:
+        return "call to native i64.wait (in wasm)";
+      case SymbolicAddress::Wake:
+        return "call to native wake (in wasm)";
       case SymbolicAddress::Limit:
         break;
     }
     return "?";
 }
 
 const char*
 ProfilingFrameIterator::label() const
--- a/js/src/wasm/WasmInstance.cpp
+++ b/js/src/wasm/WasmInstance.cpp
@@ -13,16 +13,17 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #include "wasm/WasmInstance.h"
 
+#include "jit/AtomicOperations.h"
 #include "jit/BaselineJIT.h"
 #include "jit/InlinableNatives.h"
 #include "jit/JitCommon.h"
 #include "wasm/WasmBuiltins.h"
 #include "wasm/WasmModule.h"
 
 #include "jsobjinlines.h"
 
@@ -304,16 +305,86 @@ Instance::growMemory_i32(Instance* insta
 /* static */ uint32_t
 Instance::currentMemory_i32(Instance* instance)
 {
     uint32_t byteLength = instance->memory()->volatileMemoryLength();
     MOZ_ASSERT(byteLength % wasm::PageSize == 0);
     return byteLength / wasm::PageSize;
 }
 
+template<typename T>
+static int32_t
+PerformWait(Instance* instance, uint32_t byteOffset, T value, int64_t timeout_ns)
+{
+    JSContext* cx = TlsContext.get();
+
+    if (byteOffset & (sizeof(T) - 1)) {
+        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_UNALIGNED_ACCESS);
+        return -1;
+    }
+
+    if (byteOffset + sizeof(T) > instance->memory()->volatileMemoryLength()) {
+        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_OUT_OF_BOUNDS);
+        return -1;
+    }
+
+    mozilla::Maybe<mozilla::TimeDuration> timeout;
+    if (timeout_ns >= 0)
+        timeout = mozilla::Some(mozilla::TimeDuration::FromMicroseconds(timeout_ns / 1000));
+
+    switch (atomics_wait_impl(cx, instance->sharedMemoryBuffer(), byteOffset, value, timeout)) {
+      case FutexThread::WaitResult::OK:       return 0;
+      case FutexThread::WaitResult::NotEqual: return 1;
+      case FutexThread::WaitResult::TimedOut: return 2;
+      case FutexThread::WaitResult::Error:    return -1;
+      default: MOZ_CRASH();
+    }
+}
+
+/* static */ int32_t
+Instance::wait_i32(Instance* instance, uint32_t byteOffset, int32_t value, int64_t timeout_ns)
+{
+    return PerformWait<int32_t>(instance, byteOffset, value, timeout_ns);
+}
+
+/* static */ int32_t
+Instance::wait_i64(Instance* instance, uint32_t byteOffset, int64_t value, int64_t timeout_ns)
+{
+    return PerformWait<int64_t>(instance, byteOffset, value, timeout_ns);
+}
+
+/* static */ int32_t
+Instance::wake(Instance* instance, uint32_t byteOffset, int32_t count)
+{
+    JSContext* cx = TlsContext.get();
+
+    // The alignment guard is not in the wasm spec as of 2017-11-02, but is
+    // considered likely to appear, as 4-byte alignment is required for WAKE by
+    // the spec's validation algorithm.
+
+    if (byteOffset & 3) {
+        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_UNALIGNED_ACCESS);
+        return -1;
+    }
+
+    if (byteOffset >= instance->memory()->volatileMemoryLength()) {
+        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_OUT_OF_BOUNDS);
+        return -1;
+    }
+
+    int64_t woken = atomics_wake_impl(instance->sharedMemoryBuffer(), byteOffset, int64_t(count));
+
+    if (woken > INT32_MAX) {
+        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_WAKE_OVERFLOW);
+        return -1;
+    }
+
+    return int32_t(woken);
+}
+
 Instance::Instance(JSContext* cx,
                    Handle<WasmInstanceObject*> object,
                    SharedCode code,
                    UniqueDebugState debug,
                    UniqueGlobalSegment globals,
                    HandleWasmMemoryObject memory,
                    SharedTableVector&& tables,
                    Handle<FunctionVector> funcImports,
--- a/js/src/wasm/WasmInstance.h
+++ b/js/src/wasm/WasmInstance.h
@@ -153,21 +153,16 @@ class Instance
     // Initially, calls to imports in wasm code call out through the generic
     // callImport method. If the imported callee gets JIT compiled and the types
     // match up, callImport will patch the code to instead call through a thunk
     // directly into the JIT code. If the JIT code is released, the Instance must
     // be notified so it can go back to the generic callImport.
 
     void deoptimizeImportExit(uint32_t funcImportIndex);
 
-    // Called by simulators to check whether accessing 'numBytes' starting at
-    // 'addr' would trigger a fault and be safely handled by signal handlers.
-
-    bool memoryAccessWouldFault(uint8_t* addr, unsigned numBytes);
-
     // Called by Wasm(Memory|Table)Object when a moving resize occurs:
 
     void onMovingGrowMemory(uint8_t* prevMemoryBase);
     void onMovingGrowTable();
 
     // Debug support:
 
     bool debugEnabled() const { return metadata().debugEnabled; }
@@ -187,16 +182,19 @@ class Instance
   public:
     // Functions to be called directly from wasm code.
     static int32_t callImport_void(Instance*, int32_t, int32_t, uint64_t*);
     static int32_t callImport_i32(Instance*, int32_t, int32_t, uint64_t*);
     static int32_t callImport_i64(Instance*, int32_t, int32_t, uint64_t*);
     static int32_t callImport_f64(Instance*, int32_t, int32_t, uint64_t*);
     static uint32_t growMemory_i32(Instance* instance, uint32_t delta);
     static uint32_t currentMemory_i32(Instance* instance);
+    static int32_t wait_i32(Instance* instance, uint32_t byteOffset, int32_t value, int64_t timeout);
+    static int32_t wait_i64(Instance* instance, uint32_t byteOffset, int64_t value, int64_t timeout);
+    static int32_t wake(Instance* instance, uint32_t byteOffset, int32_t count);
 };
 
 typedef UniquePtr<Instance> UniqueInstance;
 
 bool InitInstanceStaticData();
 void ShutDownInstanceStaticData();
 
 } // namespace wasm
--- a/js/src/wasm/WasmTypes.h
+++ b/js/src/wasm/WasmTypes.h
@@ -910,29 +910,35 @@ enum class Trap
     // An integer arithmetic operation led to an overflow.
     IntegerOverflow,
     // Trying to coerce NaN to an integer.
     InvalidConversionToInteger,
     // Integer division by zero.
     IntegerDivideByZero,
     // Out of bounds on wasm memory accesses and asm.js SIMD/atomic accesses.
     OutOfBounds,
+    // Unaligned on wasm atomic accesses; also used for non-standard ARM
+    // unaligned access faults.
+    UnalignedAccess,
     // call_indirect to null.
     IndirectCallToNull,
     // call_indirect signature mismatch.
     IndirectCallBadSig,
 
     // (asm.js only) SIMD float to int conversion failed because the input
     // wasn't in bounds.
     ImpreciseSimdConversion,
 
     // The internal stack space was exhausted. For compatibility, this throws
     // the same over-recursed error as JS.
     StackOverflow,
 
+    // Signal an error that was reported in C++ code.
+    ThrowReported,
+
     Limit
 };
 
 // The (,Callable,Func)Offsets classes are used to record the offsets of
 // different key points in a CodeRange during compilation.
 
 struct Offsets
 {
@@ -1006,17 +1012,18 @@ class CodeRange
         InterpEntry,       // calls into wasm from C++
         ImportJitExit,     // fast-path calling from wasm into JIT code
         ImportInterpExit,  // slow-path calling from wasm into C++ interp
         BuiltinThunk,      // fast-path calling from wasm into a C++ native
         TrapExit,          // calls C++ to report and jumps to throw stub
         DebugTrap,         // calls C++ to handle debug event
         FarJumpIsland,     // inserted to connect otherwise out-of-range insns
         OutOfBoundsExit,   // stub jumped to by non-standard asm.js SIMD/Atomics
-        UnalignedExit,     // stub jumped to by non-standard ARM unaligned trap
+        UnalignedExit,     // stub jumped to by wasm Atomics and non-standard
+                           // ARM unaligned trap
         Interrupt,         // stub executes asynchronously to interrupt wasm
         Throw              // special stack-unwinding stub jumped to by other stubs
     };
 
   private:
     // All fields are treated as cacheable POD:
     uint32_t begin_;
     uint32_t ret_;
@@ -1351,16 +1358,19 @@ enum class SymbolicAddress
     TruncateDoubleToInt64,
     TruncateDoubleToUint64,
     Uint64ToFloat32,
     Uint64ToDouble,
     Int64ToFloat32,
     Int64ToDouble,
     GrowMemory,
     CurrentMemory,
+    WaitI32,
+    WaitI64,
+    Wake,
     Limit
 };
 
 bool
 IsRoundingFunction(SymbolicAddress callee, jit::RoundingMode* mode);
 
 // Assumptions captures ambient state that must be the same when compiling and
 // deserializing a module for the compiled code to be valid. If it's not, then