Bug 1353763 - Baldr: always set exitFP before calling into C++ (r=bbouvier)
authorLuke Wagner <luke@mozilla.com>
Fri, 14 Apr 2017 09:18:02 -0500
changeset 353272 f90707333e7f6daaaf364bc61890ba2b1849199d
parent 353271 20e85b5bd3429c3e7386b56fe6c6056d52219eb0
child 353273 f44ec6523ac2253757bfa4bbdbe2f629d1758f2f
push id31661
push userarchaeopteryx@coole-files.de
push dateSat, 15 Apr 2017 18:00:16 +0000
treeherdermozilla-central@d65b53cf8fd9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbbouvier
bugs1353763
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1353763 - Baldr: always set exitFP before calling into C++ (r=bbouvier) MozReview-Commit-ID: 5575v9Os6ji
js/src/jit-test/tests/wasm/timeout/debug-interrupt-1.js
js/src/jit-test/tests/wasm/timeout/debug-interrupt-2.js
js/src/jit/arm/Simulator-arm.cpp
js/src/jit/arm/Simulator-arm.h
js/src/jit/arm64/vixl/MozSimulator-vixl.cpp
js/src/jit/arm64/vixl/Simulator-vixl.h
js/src/jscompartment.cpp
js/src/vm/Stack.cpp
js/src/vm/Stack.h
js/src/wasm/WasmCompartment.cpp
js/src/wasm/WasmCompartment.h
js/src/wasm/WasmFrameIterator.cpp
js/src/wasm/WasmFrameIterator.h
js/src/wasm/WasmRuntime.cpp
js/src/wasm/WasmSignalHandlers.cpp
js/src/wasm/WasmStubs.cpp
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/timeout/debug-interrupt-1.js
@@ -0,0 +1,17 @@
+// |jit-test| exitstatus: 6;
+
+// Don't include wasm.js in timeout tests: when wasm isn't supported, it will
+// quit(0) which will cause the test to fail.
+if (!wasmIsSupported())
+    quit(6);
+
+var g = newGlobal();
+g.parent = this;
+g.eval("Debugger(parent).onEnterFrame = function() {};");
+timeout(0.01);
+var code = wasmTextToBinary(`(module
+    (func (export "f1")
+        (loop $top br $top)
+    )
+)`);
+new WebAssembly.Instance(new WebAssembly.Module(code)).exports.f1();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/timeout/debug-interrupt-2.js
@@ -0,0 +1,22 @@
+// |jit-test| exitstatus: 6;
+
+// Don't include wasm.js in timeout tests: when wasm isn't supported, it will
+// quit(0) which will cause the test to fail.
+if (!wasmIsSupported())
+    quit(6);
+
+var g = newGlobal();
+g.parent = this;
+g.eval("Debugger(parent).onEnterFrame = function() {};");
+timeout(0.01);
+var code = wasmTextToBinary(`(module
+    (func $f2
+        loop $top
+            br $top
+        end
+    )
+    (func (export "f1")
+        call $f2
+    )
+)`);
+new WebAssembly.Instance(new WebAssembly.Module(code)).exports.f1();
--- a/js/src/jit/arm/Simulator-arm.cpp
+++ b/js/src/jit/arm/Simulator-arm.cpp
@@ -1119,17 +1119,17 @@ Simulator::Simulator(JSContext* cx)
 
     // Note, allocation and anything that depends on allocated memory is
     // deferred until init(), in order to handle OOM properly.
 
     stack_ = nullptr;
     stackLimit_ = 0;
     pc_modified_ = false;
     icount_ = 0L;
-    resume_pc_ = 0;
+    wasm_interrupt_ = false;
     break_pc_ = nullptr;
     break_instr_ = 0;
     single_stepping_ = false;
     single_step_callback_ = nullptr;
     single_step_callback_arg_ = nullptr;
     skipCalleeSavedRegsCheck = false;
 
     // Set up architecture state.
@@ -1542,37 +1542,62 @@ Simulator::exclusiveMonitorGetAndClear(b
 }
 
 void
 Simulator::exclusiveMonitorClear()
 {
     exclusiveMonitorHeld_ = false;
 }
 
+// The signal handler only redirects the PC to the interrupt stub when the PC is
+// in function code. However, this guard is racy for the ARM simulator since the
+// signal handler samples PC in the middle of simulating an instruction and thus
+// the current PC may have advanced once since the signal handler's guard. So we
+// re-check here.
+void
+Simulator::handleWasmInterrupt()
+{
+    void* pc = (void*)get_pc();
+    uint8_t* fp = (uint8_t*)get_register(r11);
+
+    WasmActivation* activation = JSContext::innermostWasmActivation();
+    const wasm::Code* code = activation->compartment()->wasm.lookupCode(pc);
+    if (!code || !code->segment().containsFunctionPC(pc))
+        return;
+
+    // fp can be null during the prologue/epilogue of the entry function.
+    if (!fp)
+        return;
+
+    activation->startInterrupt(pc, fp);
+    set_pc(int32_t(code->segment().interruptCode()));
+}
+
 // WebAssembly memories contain an extra region of guard pages (see
 // WasmArrayRawBuffer comment). The guard pages catch out-of-bounds accesses
 // using a signal handler that redirects PC to a stub that safely reports an
 // error. However, if the handler is hit by the simulator, the PC is in C++ code
 // and cannot be redirected. Therefore, we must avoid hitting the handler by
 // redirecting in the simulator before the real handler would have been hit.
 bool
 Simulator::handleWasmFault(int32_t addr, unsigned numBytes)
 {
     WasmActivation* act = cx_->wasmActivationStack();
     if (!act)
         return false;
 
     void* pc = reinterpret_cast<void*>(get_pc());
-    void* fp = reinterpret_cast<void*>(get_register(r11));
+    uint8_t* fp = reinterpret_cast<uint8_t*>(get_register(r11));
     wasm::Instance* instance = wasm::LookupFaultingInstance(act, pc, fp);
     if (!instance || !instance->memoryAccessInGuardRegion((uint8_t*)addr, numBytes))
         return false;
 
     const wasm::MemoryAccess* memoryAccess = instance->code().lookupMemoryAccess(pc);
     if (!memoryAccess) {
+        act->startInterrupt(pc, fp);
         set_pc(int32_t(instance->codeSegment().outOfBoundsCode()));
         return true;
     }
 
     MOZ_ASSERT(memoryAccess->hasTrapOutOfLineCode());
     set_pc(int32_t(memoryAccess->trapOutOfLineCode(instance->codeBase())));
     return true;
 }
@@ -4771,22 +4796,19 @@ Simulator::execute()
             dbg.debug();
         } else {
             if (single_stepping_)
                 single_step_callback_(single_step_callback_arg_, this, (void*)program_counter);
             SimInstruction* instr = reinterpret_cast<SimInstruction*>(program_counter);
             instructionDecode(instr);
             icount_++;
 
-            int32_t rpc = resume_pc_;
-            if (MOZ_UNLIKELY(rpc != 0)) {
-                // wasm signal handler ran and we have to adjust the pc.
-                JSContext::innermostWasmActivation()->setResumePC((void*)get_pc());
-                set_pc(rpc);
-                resume_pc_ = 0;
+            if (MOZ_UNLIKELY(wasm_interrupt_)) {
+                handleWasmInterrupt();
+                wasm_interrupt_ = false;
             }
         }
         program_counter = get_pc();
     }
 
     if (single_stepping_)
         single_step_callback_(single_step_callback_arg_, this, nullptr);
 }
--- a/js/src/jit/arm/Simulator-arm.h
+++ b/js/src/jit/arm/Simulator-arm.h
@@ -187,18 +187,19 @@ class Simulator
 
     // Special case of set_register and get_register to access the raw PC value.
     void set_pc(int32_t value);
     int32_t get_pc() const;
 
     template <typename T>
     T get_pc_as() const { return reinterpret_cast<T>(get_pc()); }
 
-    void set_resume_pc(void* value) {
-        resume_pc_ = int32_t(value);
+    void trigger_wasm_interrupt() {
+        MOZ_ASSERT(!wasm_interrupt_);
+        wasm_interrupt_ = true;
     }
 
     void enable_single_stepping(SingleStepCallback cb, void* arg);
     void disable_single_stepping();
 
     uintptr_t stackLimit() const;
     bool overRecursed(uintptr_t newsp = 0) const;
     bool overRecursedWithExtra(uint32_t extra) const;
@@ -279,18 +280,21 @@ class Simulator
     inline bool isStopInstruction(SimInstruction* instr);
     inline bool isWatchedStop(uint32_t bkpt_code);
     inline bool isEnabledStop(uint32_t bkpt_code);
     inline void enableStop(uint32_t bkpt_code);
     inline void disableStop(uint32_t bkpt_code);
     inline void increaseStopCounter(uint32_t bkpt_code);
     void printStopInfo(uint32_t code);
 
+    // Handle a wasm interrupt triggered by an async signal handler.
+    void handleWasmInterrupt();
+
     // Handle any wasm faults, returning true if the fault was handled.
-    inline bool handleWasmFault(int32_t addr, unsigned numBytes);
+    bool handleWasmFault(int32_t addr, unsigned numBytes);
 
     // Read and write memory.
     inline uint8_t readBU(int32_t addr);
     inline int8_t readB(int32_t addr);
     inline void writeB(int32_t addr, uint8_t value);
     inline void writeB(int32_t addr, int8_t value);
 
     inline uint8_t readExBU(int32_t addr);
@@ -408,17 +412,18 @@ class Simulator
     bool inexact_vfp_flag_;
 
     // Simulator support.
     char* stack_;
     uintptr_t stackLimit_;
     bool pc_modified_;
     int64_t icount_;
 
-    int32_t resume_pc_;
+    // wasm async interrupt support
+    bool wasm_interrupt_;
 
     // Debugger input.
     char* lastDebuggerInput_;
 
     // Registered breakpoints.
     SimInstruction* break_pc_;
     Instr break_instr_;
 
--- a/js/src/jit/arm64/vixl/MozSimulator-vixl.cpp
+++ b/js/src/jit/arm64/vixl/MozSimulator-vixl.cpp
@@ -26,16 +26,17 @@
 
 #include "mozilla/DebugOnly.h"
 
 #include "jit/arm64/vixl/Debugger-vixl.h"
 #include "jit/arm64/vixl/Simulator-vixl.h"
 #include "jit/IonTypes.h"
 #include "threading/LockGuard.h"
 #include "vm/Runtime.h"
+#include "wasm/WasmCode.h"
 
 js::jit::SimulatorProcess* js::jit::SimulatorProcess::singleton_ = nullptr;
 
 namespace vixl {
 
 
 using mozilla::DebugOnly;
 using js::jit::ABIFunctionType;
@@ -76,29 +77,29 @@ Simulator::~Simulator() {
 void Simulator::ResetState() {
   // Reset the system registers.
   nzcv_ = SimSystemRegister::DefaultValueFor(NZCV);
   fpcr_ = SimSystemRegister::DefaultValueFor(FPCR);
 
   // Reset registers to 0.
   pc_ = nullptr;
   pc_modified_ = false;
+  wasm_interrupt_ = false;
   for (unsigned i = 0; i < kNumberOfRegisters; i++) {
     set_xreg(i, 0xbadbeef);
   }
   // Set FP registers to a value that is a NaN in both 32-bit and 64-bit FP.
   uint64_t nan_bits = UINT64_C(0x7ff0dead7f8beef1);
   VIXL_ASSERT(IsSignallingNaN(rawbits_to_double(nan_bits & kDRegMask)));
   VIXL_ASSERT(IsSignallingNaN(rawbits_to_float(nan_bits & kSRegMask)));
   for (unsigned i = 0; i < kNumberOfFPRegisters; i++) {
     set_dreg_bits(i, nan_bits);
   }
   // Returning to address 0 exits the Simulator.
   set_lr(kEndOfSimAddress);
-  set_resume_pc(nullptr);
 }
 
 
 void Simulator::init(Decoder* decoder, FILE* stream) {
   // Ensure that shift operations act as the simulator expects.
   VIXL_ASSERT((static_cast<int32_t>(-1) >> 1) == -1);
   VIXL_ASSERT((static_cast<uint32_t>(-1) >> 1) == 0x7FFFFFFF);
 
@@ -184,27 +185,25 @@ void Simulator::Destroy(Simulator* sim) 
   js_delete(sim);
 }
 
 
 void Simulator::ExecuteInstruction() {
   // The program counter should always be aligned.
   VIXL_ASSERT(IsWordAligned(pc_));
   decoder_->Decode(pc_);
-  const Instruction* rpc = resume_pc_;
   increment_pc();
 
-  if (MOZ_UNLIKELY(rpc)) {
-    JSContext::innermostWasmActivation()->setResumePC((void*)pc());
-    set_pc(rpc);
+  if (MOZ_UNLIKELY(wasm_interrupt_)) {
+    handle_wasm_interrupt();
     // Just calling set_pc turns the pc_modified_ flag on, which means it doesn't
     // auto-step after executing the next instruction.  Force that to off so it
     // will auto-step after executing the first instruction of the handler.
     pc_modified_ = false;
-    resume_pc_ = nullptr;
+    wasm_interrupt_ = false;
   }
 }
 
 
 uintptr_t Simulator::stackLimit() const {
   return reinterpret_cast<uintptr_t>(stack_limit_);
 }
 
@@ -222,18 +221,38 @@ bool Simulator::overRecursed(uintptr_t n
 
 
 bool Simulator::overRecursedWithExtra(uint32_t extra) const {
   uintptr_t newsp = xreg(31, Reg31IsStackPointer) - extra;
   return newsp <= stackLimit();
 }
 
 
-void Simulator::set_resume_pc(void* new_resume_pc) {
-  resume_pc_ = AddressUntag(reinterpret_cast<Instruction*>(new_resume_pc));
+void Simulator::trigger_wasm_interrupt() {
+  MOZ_ASSERT(!wasm_interrupt_);
+  wasm_interrupt_ = true;
+}
+
+
+// The signal handler only redirects the PC to the interrupt stub when the PC is
+// in function code. However, this guard is racy for the ARM simulator since the
+// signal handler samples PC in the middle of simulating an instruction and thus
+// the current PC may have advanced once since the signal handler's guard. So we
+// re-check here.
+void Simulator::handle_wasm_interrupt() {
+  void* pc = (void*)get_pc();
+  uint8_t* fp = (uint8_t*)xreg(30);
+
+  js::WasmActivation* activation = JSContext::innermostWasmActivation();
+  const js::wasm::Code* code = activation->compartment()->wasm.lookupCode(pc);
+  if (!code || !code->segment().containsFunctionPC(pc))
+    return;
+
+  activation->startInterrupt(pc, fp);
+  set_pc((Instruction*)code->segment().interruptCode());
 }
 
 
 int64_t Simulator::call(uint8_t* entry, int argument_count, ...) {
   va_list parameters;
   va_start(parameters, argument_count);
 
   // First eight arguments passed in registers.
--- a/js/src/jit/arm64/vixl/Simulator-vixl.h
+++ b/js/src/jit/arm64/vixl/Simulator-vixl.h
@@ -733,17 +733,18 @@ class Simulator : public DecoderVisitor 
   template <typename T>
   T get_pc_as() const { return reinterpret_cast<T>(const_cast<Instruction*>(pc())); }
 
   void set_pc(const Instruction* new_pc) {
     pc_ = Memory::AddressUntag(new_pc);
     pc_modified_ = true;
   }
 
-  void set_resume_pc(void* new_resume_pc);
+  void trigger_wasm_interrupt();
+  void handle_wasm_interrupt();
 
   void increment_pc() {
     if (!pc_modified_) {
       pc_ = pc_->NextInstruction();
     }
 
     pc_modified_ = false;
   }
@@ -2565,17 +2566,17 @@ class Simulator : public DecoderVisitor 
   static const int stack_size_ = (2 * MBytes) + (2 * stack_protection_size_);
   byte* stack_limit_;
 
   Decoder* decoder_;
   // Indicates if the pc has been modified by the instruction and should not be
   // automatically incremented.
   bool pc_modified_;
   const Instruction* pc_;
-  const Instruction* resume_pc_;
+  bool wasm_interrupt_;
 
   static const char* xreg_names[];
   static const char* wreg_names[];
   static const char* sreg_names[];
   static const char* dreg_names[];
   static const char* vreg_names[];
 
   static const Instruction* kEndOfSimAddress;
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -785,18 +785,16 @@ JSCompartment::traceRoots(JSTracer* trc,
             MOZ_ASSERT(script->hasScriptCounts());
             TraceRoot(trc, &script, "profilingScripts");
             MOZ_ASSERT(script == r.front().key(), "const_cast is only a work-around");
         }
     }
 
     if (nonSyntacticLexicalEnvironments_)
         nonSyntacticLexicalEnvironments_->trace(trc);
-
-    wasm.trace(trc);
 }
 
 void
 JSCompartment::finishRoots()
 {
     if (watchpointMap)
         watchpointMap->clear();
 
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -1659,30 +1659,59 @@ WasmActivation::WasmActivation(JSContext
     registerProfiling();
 }
 
 WasmActivation::~WasmActivation()
 {
     // Hide this activation from the profiler before is is destroyed.
     unregisterProfiling();
 
+    MOZ_ASSERT(!interrupted());
     MOZ_ASSERT(exitFP_ == nullptr);
     MOZ_ASSERT(exitReason_.isNone());
 
     MOZ_ASSERT(cx_->wasmActivationStack_ == this);
     cx_->wasmActivationStack_ = prevWasm_;
 }
 
 void
 WasmActivation::unwindExitFP(uint8_t* exitFP)
 {
     exitFP_ = exitFP;
     exitReason_ = wasm::ExitReason::Fixed::None;
 }
 
+void
+WasmActivation::startInterrupt(void* pc, uint8_t* fp)
+{
+    MOZ_ASSERT(pc);
+    MOZ_ASSERT(fp);
+
+    // Execution can only be interrupted in function code. Afterwards, control
+    // flow does not reenter function code and thus there should be no
+    // interrupt-during-interrupt.
+    MOZ_ASSERT(!interrupted());
+    MOZ_ASSERT(compartment()->wasm.lookupCode(pc)->lookupRange(pc)->isFunction());
+
+    resumePC_ = pc;
+    exitFP_ = fp;
+
+    MOZ_ASSERT(interrupted());
+}
+
+void
+WasmActivation::finishInterrupt()
+{
+    MOZ_ASSERT(interrupted());
+    MOZ_ASSERT(exitFP_);
+
+    resumePC_ = nullptr;
+    exitFP_ = nullptr;
+}
+
 InterpreterFrameIterator&
 InterpreterFrameIterator::operator++()
 {
     MOZ_ASSERT(!done());
     if (fp_ != activation_->entryFrame_) {
         pc_ = fp_->prevpc();
         sp_ = fp_->prevsp();
         fp_ = fp_->prev();
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -1755,19 +1755,23 @@ class WasmActivation : public Activation
     // Returns the reason why wasm code called out of wasm code.
     wasm::ExitReason exitReason() const { return exitReason_; }
 
     // Written by JIT code:
     static unsigned offsetOfEntrySP() { return offsetof(WasmActivation, entrySP_); }
     static unsigned offsetOfExitFP() { return offsetof(WasmActivation, exitFP_); }
     static unsigned offsetOfExitReason() { return offsetof(WasmActivation, exitReason_); }
 
-    // Read/written from SIGSEGV handler:
-    void setResumePC(void* pc) { resumePC_ = pc; }
-    void* resumePC() const { return resumePC_; }
+    // Interrupts are started from the interrupt signal handler (or the ARM
+    // simulator) and cleared by WasmHandleExecutionInterrupt or WasmHandleThrow
+    // when the interrupt is handled.
+    void startInterrupt(void* pc, uint8_t* fp);
+    void finishInterrupt();
+    bool interrupted() const { return !!resumePC_; }
+    void* resumePC() const { MOZ_ASSERT(interrupted()); return resumePC_; }
 
     // Used by wasm::FrameIterator during stack unwinding.
     void unwindExitFP(uint8_t* exitFP);
 };
 
 // A FrameIter walks over a context's stack of JS script activations,
 // abstracting over whether the JS scripts were running in the interpreter or
 // different modes of compiled code.
--- a/js/src/wasm/WasmCompartment.cpp
+++ b/js/src/wasm/WasmCompartment.cpp
@@ -23,23 +23,21 @@
 #include "wasm/WasmInstance.h"
 
 #include "vm/Debugger-inl.h"
 
 using namespace js;
 using namespace wasm;
 
 Compartment::Compartment(Zone* zone)
-  : mutatingInstances_(false),
-    interruptedCount_(0)
+  : mutatingInstances_(false)
 {}
 
 Compartment::~Compartment()
 {
-    MOZ_ASSERT(interruptedCount_ == 0);
     MOZ_ASSERT(instances_.empty());
     MOZ_ASSERT(!mutatingInstances_);
 }
 
 struct InstanceComparator
 {
     const Instance& target;
     explicit InstanceComparator(const Instance& target) : target(target) {}
@@ -53,33 +51,16 @@ struct InstanceComparator
         // Instance address.  Thus a Code may map to many instances.
         if (instance->codeBase() == target.codeBase())
             return instance < &target ? -1 : 1;
 
         return target.codeBase() < instance->codeBase() ? -1 : 1;
     }
 };
 
-void
-Compartment::trace(JSTracer* trc)
-{
-    // A WasmInstanceObject that was initially reachable when called can become
-    // unreachable while executing on the stack. When execution in a compartment
-    // is interrupted inside wasm code, wasm::TraceActivations() may miss frames
-    // due to its use of FrameIterator which assumes wasm has exited through an
-    // exit stub. This could be fixed by changing wasm::TraceActivations() to
-    // use a ProfilingFrameIterator, which inspects register state, but for now
-    // just mark everything in the compartment in this super-rare case.
-
-    if (interruptedCount_) {
-        for (Instance* i : instances_)
-            i->trace(trc);
-    }
-}
-
 bool
 Compartment::registerInstance(JSContext* cx, HandleWasmInstanceObject instanceObj)
 {
     Instance& instance = instanceObj->instance();
     MOZ_ASSERT(this == &instance.compartment()->wasm);
 
     instance.code().ensureProfilingLabels(cx->runtime()->geckoProfiler().enabled());
 
@@ -126,42 +107,31 @@ struct PCComparator
             return 0;
         return pc < instance->codeBase() ? -1 : 1;
     }
 };
 
 Code*
 Compartment::lookupCode(const void* pc) const
 {
-    // lookupInstanceDeprecated can be called asynchronously from the interrupt
-    // signal handler. In that case, the signal handler is just asking whether
-    // the pc is in wasm code. If instances_ is being mutated then we can't be
+    // lookupCode() can be called asynchronously from the interrupt signal
+    // handler. In that case, the signal handler is just asking whether the pc
+    // is in wasm code. If instances_ is being mutated then we can't be
     // executing wasm code so returning nullptr is fine.
     if (mutatingInstances_)
         return nullptr;
 
     size_t index;
     if (!BinarySearchIf(instances_, 0, instances_.length(), PCComparator(pc), &index))
         return nullptr;
 
     return &instances_[index]->code();
 }
 
 void
-Compartment::setInterrupted(bool interrupted)
-{
-    if (interrupted) {
-        interruptedCount_++;
-    } else {
-        MOZ_ASSERT(interruptedCount_ > 0);
-        interruptedCount_--;
-    }
-}
-
-void
 Compartment::ensureProfilingLabels(bool profilingEnabled)
 {
     for (Instance* instance : instances_)
         instance->code().ensureProfilingLabels(profilingEnabled);
 }
 
 void
 Compartment::addSizeOfExcludingThis(MallocSizeOf mallocSizeOf, size_t* compartmentTables)
--- a/js/src/wasm/WasmCompartment.h
+++ b/js/src/wasm/WasmCompartment.h
@@ -34,17 +34,16 @@ typedef Vector<Instance*, 0, SystemAlloc
 // per-compartment state. wasm::Compartment tracks every live instance in the
 // compartment and must be notified, via registerInstance(), of any new
 // WasmInstanceObject.
 
 class Compartment
 {
     InstanceVector instances_;
     volatile bool  mutatingInstances_;
-    size_t         interruptedCount_;
 
     friend class js::WasmActivation;
 
     struct AutoMutateInstances {
         Compartment &c;
         explicit AutoMutateInstances(Compartment& c) : c(c) {
             MOZ_ASSERT(!c.mutatingInstances_);
             c.mutatingInstances_ = true;
@@ -53,17 +52,16 @@ class Compartment
             MOZ_ASSERT(c.mutatingInstances_);
             c.mutatingInstances_ = false;
         }
     };
 
   public:
     explicit Compartment(Zone* zone);
     ~Compartment();
-    void trace(JSTracer* trc);
 
     // Before a WasmInstanceObject can be considered fully constructed and
     // valid, it must be registered with the Compartment. If this method fails,
     // an error has been reported and the instance object must be abandoned.
     // After a successful registration, an Instance must call
     // unregisterInstance() before being destroyed.
 
     bool registerInstance(JSContext* cx, HandleWasmInstanceObject instanceObj);
@@ -76,21 +74,16 @@ class Compartment
 
     const InstanceVector& instances() const { return instances_; }
 
     // This methods returns the wasm::Code containing the given pc, if any
     // exists in the compartment.
 
     Code* lookupCode(const void* pc) const;
 
-    // The wasm::Compartment must be notified when execution is interrupted
-    // while executing in wasm code in this compartment.
-
-    void setInterrupted(bool interrupted);
-
     // Ensure all Instances in this JSCompartment have profiling labels created.
 
     void ensureProfilingLabels(bool profilingEnabled);
 
     // about:memory reporting
 
     void addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf, size_t* compartmentTables);
 };
--- a/js/src/wasm/WasmFrameIterator.cpp
+++ b/js/src/wasm/WasmFrameIterator.cpp
@@ -51,85 +51,93 @@ FrameToDebugFrame(void* fp)
 }
 
 FrameIterator::FrameIterator()
   : activation_(nullptr),
     code_(nullptr),
     callsite_(nullptr),
     codeRange_(nullptr),
     fp_(nullptr),
-    unwind_(Unwind::False),
-    missingFrameMessage_(false)
+    unwind_(Unwind::False)
 {
     MOZ_ASSERT(done());
 }
 
 FrameIterator::FrameIterator(WasmActivation* activation, Unwind unwind)
   : activation_(activation),
     code_(nullptr),
     callsite_(nullptr),
     codeRange_(nullptr),
-    fp_(nullptr),
-    unwind_(unwind),
-    missingFrameMessage_(false)
+    fp_(activation->exitFP()),
+    unwind_(unwind)
 {
-    // When execution is interrupted, the embedding may capture a stack trace.
-    // Since we've lost all the register state, we can't unwind the full stack
-    // like ProfilingFrameIterator does. However, we can recover the interrupted
-    // function via the resumePC and at least print that frame.
-    if (void* resumePC = activation->resumePC()) {
-        code_ = activation->compartment()->wasm.lookupCode(resumePC);
-        codeRange_ = code_->lookupRange(resumePC);
-        if (codeRange_->kind() != CodeRange::Function) {
-            // We might be in a stub inserted between functions, in which case
-            // we don't have a frame.
-            codeRange_ = nullptr;
-            missingFrameMessage_ = true;
-        }
+    MOZ_ASSERT(fp_);
+
+    // Normally, execution exits wasm code via an exit stub which sets exitFP to
+    // the exit stub's frame. Thus, in this case, we want to start iteration at
+    // the caller of the exit frame, whose Code, CodeRange and CallSite are
+    // indicated by the returnAddress of the exit stub's frame.
+
+    if (!activation->interrupted()) {
+        popFrame();
         MOZ_ASSERT(!done());
         return;
     }
 
-    fp_ = activation->exitFP();
+    // When asynchronously interrupted, exitFP is set to the interrupted frame
+    // itself and so we do not want to skip it. Instead, we can recover the
+    // Code and CodeRange from the WasmActivation, which set these when control
+    // flow was interrupted. There is no CallSite (b/c the interrupt was async),
+    // but this is fine because CallSite is only used for line number for which
+    // we can use the beginning of the function from the CodeRange instead.
 
-    if (!fp_) {
-        MOZ_ASSERT(done());
-        return;
-    }
+    code_ = activation_->compartment()->wasm.lookupCode(activation->resumePC());
+    MOZ_ASSERT(code_);
 
-    settle();
+    codeRange_ = code_->lookupRange(activation->resumePC());
+    MOZ_ASSERT(codeRange_->kind() == CodeRange::Function);
+
+    MOZ_ASSERT(!done());
 }
 
 bool
 FrameIterator::done() const
 {
-    return !codeRange_ && !missingFrameMessage_;
+    MOZ_ASSERT(!!fp_ == !!code_);
+    MOZ_ASSERT(!!fp_ == !!codeRange_);
+    return !fp_;
 }
 
 void
 FrameIterator::operator++()
 {
     MOZ_ASSERT(!done());
-    if (fp_) {
-        settle();
-    } else if (codeRange_) {
-        codeRange_ = nullptr;
-        missingFrameMessage_ = true;
-    } else {
-        MOZ_ASSERT(missingFrameMessage_);
-        missingFrameMessage_ = false;
+
+    // When the iterator is set to Unwind::True, each time the iterator pops a
+    // frame, the WasmActivation is updated so that the just-popped frame
+    // is no longer visible. This is necessary since Debugger::onLeaveFrame is
+    // called before popping each frame and, once onLeaveFrame is called for a
+    // given frame, that frame must not be visible to subsequent stack iteration
+    // (or it could be added as a "new" frame just as it becomes garbage).
+    // When the frame is "interrupted", then exitFP is included in the callstack
+    // (otherwise, it is skipped, as explained above). So to unwind the
+    // innermost frame, we just clear the interrupt state.
+
+    if (unwind_ == Unwind::True) {
+        if (activation_->interrupted())
+            activation_->finishInterrupt();
+        activation_->unwindExitFP(fp_);
     }
+
+    popFrame();
 }
 
 void
-FrameIterator::settle()
+FrameIterator::popFrame()
 {
-    if (unwind_ == Unwind::True)
-        activation_->unwindExitFP(fp_);
-
     void* returnAddress = ReturnAddressFromFP(fp_);
 
     fp_ = CallerFPFromFP(fp_);
 
     if (!fp_) {
         code_ = nullptr;
         codeRange_ = nullptr;
         callsite_ = nullptr;
@@ -175,88 +183,66 @@ FrameIterator::mutedErrors() const
 }
 
 JSAtom*
 FrameIterator::functionDisplayAtom() const
 {
     MOZ_ASSERT(!done());
 
     JSContext* cx = activation_->cx();
-
-    if (missingFrameMessage_) {
-        const char* msg = "asm.js/wasm frames may be missing below this one";
-        JSAtom* atom = Atomize(cx, msg, strlen(msg));
-        if (!atom) {
-            cx->clearPendingException();
-            return cx->names().empty;
-        }
-
-        return atom;
-    }
-
-    MOZ_ASSERT(codeRange_);
-
     JSAtom* atom = code_->getFuncAtom(cx, codeRange_->funcIndex());
     if (!atom) {
         cx->clearPendingException();
         return cx->names().empty;
     }
 
     return atom;
 }
 
 unsigned
 FrameIterator::lineOrBytecode() const
 {
     MOZ_ASSERT(!done());
-    return callsite_ ? callsite_->lineOrBytecode()
-                     : (codeRange_ ? codeRange_->funcLineOrBytecode() : 0);
-}
-
-bool
-FrameIterator::hasInstance() const
-{
-    MOZ_ASSERT(!done());
-    return !!fp_;
+    MOZ_ASSERT_IF(!callsite_, activation_->interrupted());
+    return callsite_ ? callsite_->lineOrBytecode() : codeRange_->funcLineOrBytecode();
 }
 
 Instance*
 FrameIterator::instance() const
 {
     MOZ_ASSERT(!done());
-    MOZ_ASSERT(hasInstance());
     return FrameToDebugFrame(fp_)->instance();
 }
 
 bool
 FrameIterator::debugEnabled() const
 {
-    MOZ_ASSERT(!done() && code_);
-    MOZ_ASSERT_IF(!missingFrameMessage_, codeRange_->kind() == CodeRange::Function);
-    MOZ_ASSERT_IF(missingFrameMessage_, !codeRange_ && !fp_);
+    MOZ_ASSERT(!done());
+
     // Only non-imported functions can have debug frames.
     return code_->metadata().debugEnabled &&
-           fp_ &&
-           !missingFrameMessage_ &&
            codeRange_->funcIndex() >= code_->metadata().funcImports.length();
 }
 
 DebugFrame*
 FrameIterator::debugFrame() const
 {
-    MOZ_ASSERT(!done() && debugEnabled());
-    MOZ_ASSERT(fp_);
+    MOZ_ASSERT(!done());
+    MOZ_ASSERT(debugEnabled());
     return FrameToDebugFrame(fp_);
 }
 
 const CallSite*
 FrameIterator::debugTrapCallsite() const
 {
-    MOZ_ASSERT(!done() && debugEnabled());
-    MOZ_ASSERT(callsite_->kind() == CallSite::EnterFrame || callsite_->kind() == CallSite::LeaveFrame ||
+    MOZ_ASSERT(!done());
+    MOZ_ASSERT(callsite_);
+    MOZ_ASSERT(debugEnabled());
+    MOZ_ASSERT(callsite_->kind() == CallSite::EnterFrame ||
+               callsite_->kind() == CallSite::LeaveFrame ||
                callsite_->kind() == CallSite::Breakpoint);
     return callsite_;
 }
 
 /*****************************************************************************/
 // Prologue/epilogue code generation
 
 // These constants reflect statically-determined offsets in the
@@ -519,26 +505,19 @@ AssertMatchesCallSite(const WasmActivati
     MOZ_ASSERT(callsite);
 #endif
 }
 
 void
 ProfilingFrameIterator::initFromExitFP()
 {
     uint8_t* fp = activation_->exitFP();
-    stackAddress_ = fp;
+    void* pc = ReturnAddressFromFP(fp);
 
-    // If a signal was handled while entering an activation, the frame will
-    // still be null.
-    if (!fp) {
-        MOZ_ASSERT(done());
-        return;
-    }
-
-    void* pc = ReturnAddressFromFP(fp);
+    stackAddress_ = fp;
 
     code_ = activation_->compartment()->wasm.lookupCode(pc);
     MOZ_ASSERT(code_);
 
     codeRange_ = code_->lookupRange(pc);
     MOZ_ASSERT(codeRange_);
 
     // Since we don't have the pc for fp, start unwinding at the caller of fp
@@ -593,30 +572,30 @@ ProfilingFrameIterator::ProfilingFrameIt
 {
     // In the case of ImportJitExit, the fp register may be temporarily
     // clobbered on return from Ion so always use activation.fp when it is set.
     if (activation.exitFP()) {
         initFromExitFP();
         return;
     }
 
-    // If pc isn't in the instance's code, we must have exited the code via an
-    // exit trampoline or signal handler.
     code_ = activation_->compartment()->wasm.lookupCode(state.pc);
 
     const CodeRange* codeRange = nullptr;
     uint8_t* codeBase = nullptr;
     if (!code_) {
         // Optimized builtin exits (see MaybeGetMatchingBuiltin in
         // WasmInstance.cpp) are outside module's code.
         AutoNoteSingleThreadedRegion anstr;
         if (BuiltinThunk* thunk = activation_->cx()->runtime()->wasm().lookupBuiltin(state.pc)) {
             codeRange = &thunk->codeRange;
             codeBase = (uint8_t*) thunk->base;
         } else {
+            // If pc isn't in any wasm code or builtin exit, we must be between
+            // pushing the WasmActivation and entering wasm code.
             MOZ_ASSERT(done());
             return;
         }
     } else {
         codeRange = code_->lookupRange(state.pc);
         codeBase = code_->segment().base();
     }
 
@@ -942,20 +921,18 @@ ProfilingFrameIterator::label() const
     MOZ_CRASH("bad code range kind");
 }
 
 void
 wasm::TraceActivations(JSContext* cx, const CooperatingContext& target, JSTracer* trc)
 {
     for (ActivationIterator iter(cx, target); !iter.done(); ++iter) {
         if (iter.activation()->isWasm()) {
-            for (FrameIterator fi(iter.activation()->asWasm()); !fi.done(); ++fi) {
-                if (fi.hasInstance())
-                    fi.instance()->trace(trc);
-            }
+            for (FrameIterator fi(iter.activation()->asWasm()); !fi.done(); ++fi)
+                fi.instance()->trace(trc);
         }
     }
 }
 
 Instance*
 wasm::LookupFaultingInstance(WasmActivation* activation, void* pc, void* fp)
 {
     // Assume bug-caused faults can be raised at any PC and apply the logic of
--- a/js/src/wasm/WasmFrameIterator.h
+++ b/js/src/wasm/WasmFrameIterator.h
@@ -54,32 +54,30 @@ class FrameIterator
 
   private:
     WasmActivation* activation_;
     const Code* code_;
     const CallSite* callsite_;
     const CodeRange* codeRange_;
     uint8_t* fp_;
     Unwind unwind_;
-    bool missingFrameMessage_;
 
-    void settle();
+    void popFrame();
 
   public:
     explicit FrameIterator();
     explicit FrameIterator(WasmActivation* activation, Unwind unwind = Unwind::False);
     void operator++();
     bool done() const;
     const char* filename() const;
     const char16_t* displayURL() const;
     bool mutedErrors() const;
     JSAtom* functionDisplayAtom() const;
     unsigned lineOrBytecode() const;
     const CodeRange* codeRange() const { return codeRange_; }
-    bool hasInstance() const;
     Instance* instance() const;
     bool debugEnabled() const;
     DebugFrame* debugFrame() const;
     const CallSite* debugTrapCallsite() const;
 };
 
 enum class SymbolicAddress;
 
--- a/js/src/wasm/WasmRuntime.cpp
+++ b/js/src/wasm/WasmRuntime.cpp
@@ -53,32 +53,32 @@ extern MOZ_EXPORT int64_t
 
 }
 #endif
 
 static void*
 WasmHandleExecutionInterrupt()
 {
     WasmActivation* activation = JSContext::innermostWasmActivation();
-
-    // wasm::Compartment requires notification when execution is interrupted in
-    // the compartment. Only the innermost compartment has been interrupted;
-    // enclosing compartments necessarily exited through an exit stub.
-    activation->compartment()->wasm.setInterrupted(true);
-    bool success = CheckForInterrupt(activation->cx());
-    activation->compartment()->wasm.setInterrupted(false);
+    MOZ_ASSERT(activation->interrupted());
 
-    // Preserve the invariant that having a non-null resumePC means that we are
-    // handling an interrupt.
+    if (!CheckForInterrupt(activation->cx())) {
+        // If CheckForInterrupt failed, it is time to interrupt execution.
+        // Returning nullptr to the caller will jump to the throw stub which
+        // will call WasmHandleThrow. The WasmActivation must stay in the
+        // interrupted state until then so that stack unwinding works in
+        // WasmHandleThrow.
+        return nullptr;
+    }
+
+    // If CheckForInterrupt succeeded, then execution can proceed and the
+    // interrupt is over.
     void* resumePC = activation->resumePC();
-    activation->setResumePC(nullptr);
-
-    // Return the resumePC if execution can continue or null if execution should
-    // jump to the throw stub.
-    return success ? resumePC : nullptr;
+    activation->finishInterrupt();
+    return resumePC;
 }
 
 static bool
 WasmHandleDebugTrap()
 {
     WasmActivation* activation = JSContext::innermostWasmActivation();
     MOZ_ASSERT(activation);
     JSContext* cx = activation->cx();
@@ -151,18 +151,20 @@ WasmHandleThrow()
     // FrameIterator iterates down wasm frames in the activation starting at
     // WasmActivation::exitFP. Pass Unwind::True to pop WasmActivation::exitFP
     // once each time FrameIterator is incremented, ultimately leaving exitFP
     // null when the FrameIterator is done(). This is necessary to prevent a
     // DebugFrame from being observed again after we just called onLeaveFrame
     // (which would lead to the frame being re-added to the map of live frames,
     // right as it becomes trash).
     FrameIterator iter(activation, FrameIterator::Unwind::True);
-    if (iter.done())
+    if (iter.done()) {
+        MOZ_ASSERT(!activation->interrupted());
         return activation;
+    }
 
     // Live wasm code on the stack is kept alive (in wasm::TraceActivations) by
     // marking the instance of every wasm::Frame found by FrameIterator.
     // However, as explained above, we're popping frames while iterating which
     // means that a GC during this loop could collect the code of frames whose
     // code is still on the stack. This is actually mostly fine: as soon as we
     // return to the throw stub, the entire stack will be popped as a whole,
     // returning to the C++ caller. However, we must keep the throw stub alive
@@ -193,16 +195,17 @@ WasmHandleThrow()
             // Unexpected success from the handler onLeaveFrame -- raising error
             // since throw recovery is not yet implemented in the wasm baseline.
             // TODO properly handle success and resume wasm execution.
             JS_ReportErrorASCII(cx, "Unexpected success from onLeaveFrame");
         }
         frame->leave(cx);
      }
 
+    MOZ_ASSERT(!activation->interrupted(), "unwinding clears the interrupt");
     return activation;
 }
 
 static void
 WasmReportTrap(int32_t trapIndex)
 {
     JSContext* cx = JSContext::innermostWasmActivation()->cx();
 
--- a/js/src/wasm/WasmSignalHandlers.cpp
+++ b/js/src/wasm/WasmSignalHandlers.cpp
@@ -365,26 +365,42 @@ ContextToPC(CONTEXT* context)
 {
 #ifdef JS_CODEGEN_NONE
     MOZ_CRASH();
 #else
     return reinterpret_cast<uint8_t**>(&PC_sig(context));
 #endif
 }
 
-uint8_t*
+static uint8_t*
 ContextToFP(CONTEXT* context)
 {
 #ifdef JS_CODEGEN_NONE
     MOZ_CRASH();
 #else
     return reinterpret_cast<uint8_t*>(FP_sig(context));
 #endif
 }
 
+#if defined(XP_DARWIN)
+static uint8_t*
+ContextToFP(EMULATOR_CONTEXT* context)
+{
+# if defined(__x86_64__)
+    return (uint8_t*)context->thread.__rbp;
+# elif defined(__i386__)
+    return (uint8_t*)context->thread.uts.ts32.__ebp;
+# elif defined(__arm__)
+    return (uint8_t*)context->thread.__fp;
+# else
+#  error Unsupported architecture
+# endif
+}
+#endif  // XP_DARWIN
+
 #if defined(WASM_HUGE_MEMORY)
 MOZ_COLD static void
 SetFPRegToNaN(size_t size, void* fp_reg)
 {
     MOZ_RELEASE_ASSERT(size <= Simd128DataSize);
     memset(fp_reg, 0, Simd128DataSize);
     switch (size) {
       case 4: *static_cast<float*>(fp_reg) = GenericNaN(); break;
@@ -611,26 +627,27 @@ ComputeAccessAddress(EMULATOR_CONTEXT* c
         result += index * (uintptr_t(1) << address.scale());
     }
 
     return reinterpret_cast<uint8_t*>(result);
 }
 
 MOZ_COLD static void
 HandleMemoryAccess(EMULATOR_CONTEXT* context, uint8_t* pc, uint8_t* faultingAddress,
-                   const Instance& instance, uint8_t** ppc)
+                   const Instance& instance, WasmActivation* activation, uint8_t** ppc)
 {
     MOZ_RELEASE_ASSERT(instance.codeSegment().containsFunctionPC(pc));
 
     const MemoryAccess* memoryAccess = instance.code().lookupMemoryAccess(pc);
     if (!memoryAccess) {
         // If there is no associated MemoryAccess for the faulting PC, this must be
         // experimental SIMD.js or Atomics. When these are converted to
         // non-experimental wasm features, this case, as well as outOfBoundsCode,
         // can be removed.
+        activation->startInterrupt(pc, ContextToFP(context));
         *ppc = instance.codeSegment().outOfBoundsCode();
         return;
     }
 
     MOZ_RELEASE_ASSERT(memoryAccess->insnOffset() == (pc - instance.codeBase()));
 
     // On WASM_HUGE_MEMORY platforms, asm.js code may fault. asm.js does not
     // trap on fault and so has no trap out-of-line path. Instead, stores are
@@ -756,23 +773,24 @@ HandleMemoryAccess(EMULATOR_CONTEXT* con
 
     *ppc = end;
 }
 
 #else // WASM_HUGE_MEMORY
 
 MOZ_COLD static void
 HandleMemoryAccess(EMULATOR_CONTEXT* context, uint8_t* pc, uint8_t* faultingAddress,
-                   const Instance& instance, uint8_t** ppc)
+                   const Instance& instance, WasmActivation* activation, uint8_t** ppc)
 {
     MOZ_RELEASE_ASSERT(instance.codeSegment().containsFunctionPC(pc));
 
     const MemoryAccess* memoryAccess = instance.code().lookupMemoryAccess(pc);
     if (!memoryAccess) {
         // See explanation in the WASM_HUGE_MEMORY HandleMemoryAccess.
+        activation->startInterrupt(pc, ContextToFP(context));
         *ppc = instance.codeSegment().outOfBoundsCode();
         return;
     }
 
     MOZ_RELEASE_ASSERT(memoryAccess->hasTrapOutOfLineCode());
     *ppc = memoryAccess->trapOutOfLineCode(instance.codeBase());
 }
 
@@ -815,41 +833,42 @@ HandleFault(PEXCEPTION_POINTERS exceptio
     if (!activation)
         return false;
 
     Code* code = activation->compartment()->wasm.lookupCode(pc);
     if (!code)
         return false;
 
     if (!code->segment().containsFunctionPC(pc)) {
-        // On Windows, it is possible for InterruptRunningCode to execute
+        // On Windows, it is possible for InterruptRunningJitCode to execute
         // between a faulting heap access and the handling of the fault due
-        // to InterruptRunningCode's use of SuspendThread. When this happens,
+        // to InterruptRunningJitCode's use of SuspendThread. When this happens,
         // after ResumeThread, the exception handler is called with pc equal to
         // CodeSegment.interrupt, which is logically wrong. The Right Thing would
         // be for the OS to make fault-handling atomic (so that CONTEXT.pc was
         // always the logically-faulting pc). Fortunately, we can detect this
         // case and silence the exception ourselves (the exception will
         // retrigger after the interrupt jumps back to resumePC).
         return pc == code->segment().interruptCode() &&
+               activation->interrupted() &&
                code->segment().containsFunctionPC(activation->resumePC());
     }
 
     const Instance* instance = LookupFaultingInstance(activation, pc, ContextToFP(context));
     if (!instance)
         return false;
 
     uint8_t* faultingAddress = reinterpret_cast<uint8_t*>(record->ExceptionInformation[1]);
 
     // This check isn't necessary, but, since we can, check anyway to make
     // sure we aren't covering up a real bug.
     if (!IsHeapAccessAddress(*instance, faultingAddress))
         return false;
 
-    HandleMemoryAccess(context, pc, faultingAddress, *instance, ppc);
+    HandleMemoryAccess(context, pc, faultingAddress, *instance, activation, ppc);
     return true;
 }
 
 static LONG WINAPI
 WasmFaultHandler(LPEXCEPTION_POINTERS exception)
 {
     if (HandleFault(exception))
         return EXCEPTION_CONTINUE_EXECUTION;
@@ -876,30 +895,16 @@ ContextToPC(EMULATOR_CONTEXT* context)
     static_assert(sizeof(context->thread.__pc) == sizeof(void*),
                   "stored IP should be compile-time pointer-sized");
     return reinterpret_cast<uint8_t**>(&context->thread.__pc);
 # else
 #  error Unsupported architecture
 # endif
 }
 
-static void*
-ContextToFP(EMULATOR_CONTEXT* context)
-{
-# if defined(__x86_64__)
-    return (void*)context->thread.__rbp;
-# elif defined(__i386__)
-    return (void*)context->thread.uts.ts32.__ebp;
-# elif defined(__arm__)
-    return (void*)context->thread.__fp;
-# else
-#  error Unsupported architecture
-# endif
-}
-
 // This definition was generated by mig (the Mach Interface Generator) for the
 // routine 'exception_raise' (exc.defs).
 #pragma pack(4)
 typedef struct {
     mach_msg_header_t Head;
     /* start of the kernel processed data */
     mach_msg_body_t msgh_body;
     mach_msg_port_descriptor_t thread;
@@ -976,17 +981,17 @@ HandleMachException(JSContext* cx, const
 
     uint8_t* faultingAddress = reinterpret_cast<uint8_t*>(request.body.code[1]);
 
     // This check isn't necessary, but, since we can, check anyway to make
     // sure we aren't covering up a real bug.
     if (!IsHeapAccessAddress(*instance, faultingAddress))
         return false;
 
-    HandleMemoryAccess(&context, pc, faultingAddress, *instance, ppc);
+    HandleMemoryAccess(&context, pc, faultingAddress, *instance, activation, ppc);
 
     // Update the thread state with the new pc and register values.
     kret = thread_set_state(cxThread, float_state, (thread_state_t)&context.float_, float_state_count);
     if (kret != KERN_SUCCESS)
         return false;
     kret = thread_set_state(cxThread, thread_state, (thread_state_t)&context.thread, thread_state_count);
     if (kret != KERN_SUCCESS)
         return false;
@@ -1203,22 +1208,23 @@ HandleFault(int signum, siginfo_t* info,
     }
 
 #ifdef JS_CODEGEN_ARM
     if (signal == Signal::BusError) {
         // TODO: We may see a bus error for something that is an unaligned access that
         // partly overlaps the end of the heap.  In this case, it is an out-of-bounds
         // error and we should signal that properly, but to do so we must inspect
         // the operand of the failed access.
+        activation->startInterrupt(pc, ContextToFP(context));
         *ppc = instance->codeSegment().unalignedAccessCode();
         return true;
     }
 #endif
 
-    HandleMemoryAccess(context, pc, faultingAddress, *instance, ppc);
+    HandleMemoryAccess(context, pc, faultingAddress, *instance, activation, ppc);
     return true;
 }
 
 static struct sigaction sPrevSEGVHandler;
 static struct sigaction sPrevSIGBUSHandler;
 
 template<Signal signal>
 static void
@@ -1285,24 +1291,25 @@ RedirectJitCodeToInterruptCheck(JSContex
     if (WasmActivation* activation = cx->wasmActivationStack()) {
 #ifdef JS_SIMULATOR
         (void)ContextToPC(context);  // silence static 'unused' errors
 
         void* pc = cx->simulator()->get_pc_as<void*>();
 
         const Code* code = activation->compartment()->wasm.lookupCode(pc);
         if (code && code->segment().containsFunctionPC(pc))
-            cx->simulator()->set_resume_pc(code->segment().interruptCode());
+            cx->simulator()->trigger_wasm_interrupt();
 #else
         uint8_t** ppc = ContextToPC(context);
         uint8_t* pc = *ppc;
+        uint8_t* fp = ContextToFP(context);
 
         const Code* code = activation->compartment()->wasm.lookupCode(pc);
-        if (code && code->segment().containsFunctionPC(pc)) {
-            activation->setResumePC(pc);
+        if (code && code->segment().containsFunctionPC(pc) && fp) {
+            activation->startInterrupt(pc, fp);
             *ppc = code->segment().interruptCode();
             return true;
         }
 #endif
     }
 
     return false;
 }
@@ -1495,17 +1502,17 @@ js::InterruptRunningJitCode(JSContext* c
 #if defined(XP_WIN)
     // On Windows, we can simply suspend the active thread and work directly on
     // its context from this thread. SuspendThread can sporadically fail if the
     // thread is in the middle of a syscall. Rather than retrying in a loop,
     // just wait for the next request for interrupt.
     HANDLE thread = (HANDLE)cx->threadNative();
     if (SuspendThread(thread) != -1) {
         CONTEXT context;
-        context.ContextFlags = CONTEXT_CONTROL;
+        context.ContextFlags = CONTEXT_FULL;
         if (GetThreadContext(thread, &context)) {
             if (RedirectJitCodeToInterruptCheck(cx, &context))
                 SetThreadContext(thread, &context);
         }
         ResumeThread(thread);
     }
     cx->finishHandlingJitInterrupt();
 #else
--- a/js/src/wasm/WasmStubs.cpp
+++ b/js/src/wasm/WasmStubs.cpp
@@ -722,17 +722,17 @@ wasm::GenerateImportJitExit(MacroAssembl
         masm.storePtr(act, Address(cx, JSContext::offsetOfProfilingActivation()));
     }
 
     AssertStackAlignment(masm, JitStackAlignment, sizeOfRetAddr);
     masm.callJitNoProfiler(callee);
     AssertStackAlignment(masm, JitStackAlignment, sizeOfRetAddr);
 
     // The JIT callee clobbers all registers, including WasmTlsReg and
-    // FrameRegister, so restore those here.
+    // FramePointer, so restore those here.
     masm.loadWasmTlsRegFromFrame();
     masm.moveStackPtrTo(FramePointer);
     masm.addPtr(Imm32(masm.framePushed()), FramePointer);
 
     {
         // Disable Activation.
         //
         // This sequence needs three registers and must preserve WasmTlsReg,
@@ -1049,23 +1049,22 @@ static const LiveRegisterSet AllRegsExce
 static_assert(!SupportsSimd, "high lanes of SIMD registers need to be saved too.");
 #else
 static const LiveRegisterSet AllRegsExceptSP(
     GeneralRegisterSet(Registers::AllMask & ~(uint32_t(1) << Registers::StackPointer)),
     FloatRegisterSet(FloatRegisters::AllMask));
 #endif
 
 // The async interrupt-callback exit is called from arbitrarily-interrupted wasm
-// code. That means we must first save *all* registers and restore *all*
-// registers (except the stack pointer) when we resume. The address to resume to
-// (assuming that js::HandleExecutionInterrupt doesn't indicate that the
-// execution should be aborted) is stored in WasmActivation::resumePC_.
-// Unfortunately, loading this requires a scratch register which we don't have
-// after restoring all registers. To hack around this, push the resumePC on the
-// stack so that it can be popped directly into PC.
+// code. It calls into the WasmHandleExecutionInterrupt to determine whether we must
+// really halt execution which can reenter the VM (e.g., to display the slow
+// script dialog). If execution is not interrupted, this stub must carefully
+// preserve *all* register state. If execution is interrupted, the entire
+// activation will be popped by the throw stub, so register state does not need
+// to be restored.
 Offsets
 wasm::GenerateInterruptExit(MacroAssembler& masm, Label* throwLabel)
 {
     masm.haltingAlign(CodeAlignment);
 
     Offsets offsets;
     offsets.begin = masm.currentOffset();