Bug 1435360 - Baldr: implement wasm interrupt in terms of TlsData branch and stack overflow check (r=bbouvier)
☠☠ backed out by 55c87e7ea09d ☠ ☠
authorLuke Wagner <luke@mozilla.com>
Fri, 09 Mar 2018 13:04:53 -0600
changeset 765590 8cdf945be534dacae33245106e6718055a80bd7f
parent 765589 a463d224c412529aa8d7b02103506f9a714a6dd9
child 765591 f22f1ab67c5ace0d966a3f60f7b1e25dfe4ec6c9
push id102114
push userbmo:jgilbert@mozilla.com
push dateFri, 09 Mar 2018 22:13:16 +0000
reviewersbbouvier
bugs1435360
milestone60.0a1
Bug 1435360 - Baldr: implement wasm interrupt in terms of TlsData branch and stack overflow check (r=bbouvier)
js/public/MemoryMetrics.h
js/src/jit/CodeGenerator.cpp
js/src/jit/CodeGenerator.h
js/src/jit/Lowering.cpp
js/src/jit/Lowering.h
js/src/jit/MIR.h
js/src/jit/MOpcodes.h
js/src/jit/MacroAssembler.cpp
js/src/jit/MacroAssembler.h
js/src/jit/arm/Assembler-arm.h
js/src/jit/arm64/Assembler-arm64.h
js/src/jit/arm64/vixl/MozSimulator-vixl.cpp
js/src/jit/mips32/Assembler-mips32.h
js/src/jit/mips64/Assembler-mips64.h
js/src/jit/none/Architecture-none.h
js/src/jit/none/MacroAssembler-none.h
js/src/jit/shared/LIR-shared.h
js/src/jit/shared/LOpcodes-shared.h
js/src/jit/x64/Assembler-x64.h
js/src/jit/x86/Assembler-x86.h
js/src/vm/JSCompartment.cpp
js/src/vm/MutexIDs.h
js/src/vm/Runtime.cpp
js/src/vm/Runtime.h
js/src/vm/Stack.cpp
js/src/vm/Stack.h
js/src/wasm/WasmBaselineCompile.cpp
js/src/wasm/WasmBuiltins.cpp
js/src/wasm/WasmCompartment.cpp
js/src/wasm/WasmCompartment.h
js/src/wasm/WasmFrameIter.cpp
js/src/wasm/WasmInstance.cpp
js/src/wasm/WasmIonCompile.cpp
js/src/wasm/WasmSignalHandlers.h
js/src/wasm/WasmStubs.cpp
js/src/wasm/WasmTypes.cpp
js/src/wasm/WasmTypes.h
js/xpconnect/src/XPCJSRuntime.cpp
--- a/js/public/MemoryMetrics.h
+++ b/js/public/MemoryMetrics.h
@@ -511,17 +511,18 @@ struct RuntimeSizes
     macro(_, MallocHeap, contexts) \
     macro(_, MallocHeap, temporary) \
     macro(_, MallocHeap, interpreterStack) \
     macro(_, MallocHeap, mathCache) \
     macro(_, MallocHeap, sharedImmutableStringsCache) \
     macro(_, MallocHeap, sharedIntlData) \
     macro(_, MallocHeap, uncompressedSourceCache) \
     macro(_, MallocHeap, scriptData) \
-    macro(_, MallocHeap, tracelogger)
+    macro(_, MallocHeap, tracelogger) \
+    macro(_, MallocHeap, wasmRuntime)
 
     RuntimeSizes()
       : FOR_EACH_SIZE(ZERO_SIZE)
         scriptSourceInfo(),
         code(),
         gc(),
         notableScriptSources()
     {
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -12706,16 +12706,24 @@ CodeGenerator::visitInterruptCheck(LInte
     const void* contextAddr = gen->compartment->zone()->addressOfJSContext();
     masm.loadPtr(AbsoluteAddress(contextAddr), temp);
     masm.branch32(Assembler::NotEqual, Address(temp, offsetof(JSContext, interrupt_)),
                   Imm32(0), ool->entry());
     masm.bind(ool->rejoin());
 }
 
 void
+CodeGenerator::visitWasmInterruptCheck(LWasmInterruptCheck* lir)
+{
+    MOZ_ASSERT(gen->compilingWasm());
+
+    masm.wasmInterruptCheck(ToRegister(lir->tlsPtr()), lir->mir()->bytecodeOffset());
+}
+
+void
 CodeGenerator::visitWasmTrap(LWasmTrap* lir)
 {
     MOZ_ASSERT(gen->compilingWasm());
     const MWasmTrap* mir = lir->mir();
 
     masm.wasmTrap(mir->trap(), mir->bytecodeOffset());
 }
 
--- a/js/src/jit/CodeGenerator.h
+++ b/js/src/jit/CodeGenerator.h
@@ -460,16 +460,17 @@ class CodeGenerator final : public CodeG
 
 #ifdef DEBUG
     void emitAssertResultV(const ValueOperand output, const TemporaryTypeSet* typeset);
     void emitAssertObjectOrStringResult(Register input, MIRType type, const TemporaryTypeSet* typeset);
 #endif
 
     void visitInterruptCheck(LInterruptCheck* lir);
     void visitOutOfLineInterruptCheckImplicit(OutOfLineInterruptCheckImplicit* ins);
+    void visitWasmInterruptCheck(LWasmInterruptCheck* lir);
     void visitWasmTrap(LWasmTrap* lir);
     void visitWasmLoadTls(LWasmLoadTls* ins);
     void visitWasmBoundsCheck(LWasmBoundsCheck* ins);
     void visitWasmAlignmentCheck(LWasmAlignmentCheck* ins);
     void visitRecompileCheck(LRecompileCheck* ins);
     void visitRotate(LRotate* ins);
 
     void visitRandom(LRandom* ins);
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -2733,16 +2733,23 @@ void
 LIRGenerator::visitInterruptCheck(MInterruptCheck* ins)
 {
     LInstruction* lir = new(alloc()) LInterruptCheck(temp());
     add(lir, ins);
     assignSafepoint(lir, ins);
 }
 
 void
+LIRGenerator::visitWasmInterruptCheck(MWasmInterruptCheck* ins)
+{
+    auto* lir = new(alloc()) LWasmInterruptCheck(useRegisterAtStart(ins->tlsPtr()));
+    add(lir, ins);
+}
+
+void
 LIRGenerator::visitWasmTrap(MWasmTrap* ins)
 {
     add(new(alloc()) LWasmTrap, ins);
 }
 
 void
 LIRGenerator::visitWasmReinterpret(MWasmReinterpret* ins)
 {
--- a/js/src/jit/Lowering.h
+++ b/js/src/jit/Lowering.h
@@ -206,16 +206,17 @@ class LIRGenerator : public LIRGenerator
     void visitMaybeToDoubleElement(MMaybeToDoubleElement* ins) override;
     void visitMaybeCopyElementsForWrite(MMaybeCopyElementsForWrite* ins) override;
     void visitLoadSlot(MLoadSlot* ins) override;
     void visitLoadFixedSlotAndUnbox(MLoadFixedSlotAndUnbox* ins) override;
     void visitFunctionEnvironment(MFunctionEnvironment* ins) override;
     void visitHomeObject(MHomeObject* ins) override;
     void visitHomeObjectSuperBase(MHomeObjectSuperBase* ins) override;
     void visitInterruptCheck(MInterruptCheck* ins) override;
+    void visitWasmInterruptCheck(MWasmInterruptCheck* ins) override;
     void visitWasmTrap(MWasmTrap* ins) override;
     void visitWasmReinterpret(MWasmReinterpret* ins) override;
     void visitStoreSlot(MStoreSlot* ins) override;
     void visitFilterTypeSet(MFilterTypeSet* ins) override;
     void visitTypeBarrier(MTypeBarrier* ins) override;
     void visitPostWriteBarrier(MPostWriteBarrier* ins) override;
     void visitPostWriteElementBarrier(MPostWriteElementBarrier* ins) override;
     void visitArrayLength(MArrayLength* ins) override;
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -8298,16 +8298,43 @@ class MInterruptCheck : public MNullaryI
     INSTRUCTION_HEADER(InterruptCheck)
     TRIVIAL_NEW_WRAPPERS
 
     AliasSet getAliasSet() const override {
         return AliasSet::None();
     }
 };
 
+// Check whether we need to fire the interrupt handler (in wasm code).
+class MWasmInterruptCheck
+  : public MUnaryInstruction,
+    public NoTypePolicy::Data
+{
+    wasm::BytecodeOffset bytecodeOffset_;
+
+    MWasmInterruptCheck(MDefinition* tlsPointer, wasm::BytecodeOffset bytecodeOffset)
+      : MUnaryInstruction(classOpcode, tlsPointer),
+        bytecodeOffset_(bytecodeOffset)
+    {
+        setGuard();
+    }
+
+  public:
+    INSTRUCTION_HEADER(WasmInterruptCheck)
+    TRIVIAL_NEW_WRAPPERS
+    NAMED_OPERANDS((0, tlsPtr))
+
+    AliasSet getAliasSet() const override {
+        return AliasSet::None();
+    }
+    wasm::BytecodeOffset bytecodeOffset() const {
+        return bytecodeOffset_;
+    }
+};
+
 // Directly jumps to the indicated trap, leaving Wasm code and reporting a
 // runtime error.
 
 class MWasmTrap
   : public MAryControlInstruction<0, 0>,
     public NoTypePolicy::Data
 {
     wasm::Trap trap_;
--- a/js/src/jit/MOpcodes.h
+++ b/js/src/jit/MOpcodes.h
@@ -272,16 +272,17 @@ namespace jit {
     _(Ceil)                                                                 \
     _(Round)                                                                \
     _(NearbyInt)                                                            \
     _(InCache)                                                              \
     _(HasOwnCache)                                                          \
     _(InstanceOf)                                                           \
     _(InstanceOfCache)                                                      \
     _(InterruptCheck)                                                       \
+    _(WasmInterruptCheck)                                                   \
     _(GetDOMProperty)                                                       \
     _(GetDOMMember)                                                         \
     _(SetDOMProperty)                                                       \
     _(IsConstructor)                                                        \
     _(IsCallable)                                                           \
     _(IsArray)                                                              \
     _(IsTypedArray)                                                         \
     _(IsObject)                                                             \
--- a/js/src/jit/MacroAssembler.cpp
+++ b/js/src/jit/MacroAssembler.cpp
@@ -3352,17 +3352,29 @@ MacroAssembler::maybeBranchTestType(MIRT
             MOZ_CRASH("Unsupported type");
         }
     }
 }
 
 void
 MacroAssembler::wasmTrap(wasm::Trap trap, wasm::BytecodeOffset bytecodeOffset)
 {
-    append(trap, wasm::TrapSite(wasmTrapInstruction().offset(), bytecodeOffset));
+    uint32_t trapOffset = wasmTrapInstruction().offset();
+    MOZ_ASSERT_IF(!oom(), currentOffset() - trapOffset == WasmTrapInstructionLength);
+
+    append(trap, wasm::TrapSite(trapOffset, bytecodeOffset));
+}
+
+void
+MacroAssembler::wasmInterruptCheck(Register tls, wasm::BytecodeOffset bytecodeOffset)
+{
+    Label ok;
+    branch32(Assembler::Equal, Address(tls, offsetof(wasm::TlsData, interrupt)), Imm32(0), &ok);
+    wasmTrap(wasm::Trap::CheckInterrupt, bytecodeOffset);
+    bind(&ok);
 }
 
 void
 MacroAssembler::wasmCallImport(const wasm::CallSiteDesc& desc, const wasm::CalleeDesc& callee)
 {
     // Load the callee, before the caller's registers are clobbered.
     uint32_t globalDataOffset = callee.importGlobalDataOffset();
     loadWasmGlobalPtr(globalDataOffset + offsetof(wasm::FuncImportTls, code), ABINonArgReg0);
--- a/js/src/jit/MacroAssembler.h
+++ b/js/src/jit/MacroAssembler.h
@@ -1488,16 +1488,17 @@ class MacroAssembler : public MacroAssem
 
   public:
     // ========================================================================
     // wasm support
 
     CodeOffset wasmTrapInstruction() PER_SHARED_ARCH;
 
     void wasmTrap(wasm::Trap trap, wasm::BytecodeOffset bytecodeOffset);
+    void wasmInterruptCheck(Register tls, wasm::BytecodeOffset bytecodeOffset);
 
     // Emit a bounds check against the wasm heap limit, jumping to 'label' if
     // 'cond' holds. Required when WASM_HUGE_MEMORY is not defined. If
     // JitOptions.spectreMaskIndex is true, in speculative executions 'index' is
     // saturated in-place to 'boundsCheckLimit'.
     template <class L>
     inline void wasmBoundsCheck(Condition cond, Register index, Register boundsCheckLimit, L label)
         DEFINED_ON(arm, arm64, mips32, mips64, x86);
--- a/js/src/jit/arm/Assembler-arm.h
+++ b/js/src/jit/arm/Assembler-arm.h
@@ -123,16 +123,17 @@ static constexpr Register ABINonArgReg2 
 // This register may be volatile or nonvolatile. Avoid d15 which is the
 // ScratchDoubleReg.
 static constexpr FloatRegister ABINonArgDoubleReg { FloatRegisters::d8, VFPRegister::Double };
 
 // These registers may be volatile or nonvolatile.
 // Note: these three registers are all guaranteed to be different
 static constexpr Register ABINonArgReturnReg0 = r4;
 static constexpr Register ABINonArgReturnReg1 = r5;
+static constexpr Register ABINonVolatileReg = r6;
 
 // This register is guaranteed to be clobberable during the prologue and
 // epilogue of an ABI call which must preserve both ABI argument, return
 // and non-volatile registers.
 static constexpr Register ABINonArgReturnVolatileReg = lr;
 
 // TLS pointer argument register for WebAssembly functions. This must not alias
 // any other register used for passing function arguments or return values.
@@ -244,16 +245,17 @@ static_assert(CodeAlignment % SimdMemory
   "the constant sections of the code buffer.  Thus it should be larger than the "
   "alignment for SIMD constants.");
 
 static_assert(JitStackAlignment % SimdMemoryAlignment == 0,
   "Stack alignment should be larger than any of the alignments which are used for "
   "spilled values.  Thus it should be larger than the alignment for SIMD accesses.");
 
 static const uint32_t WasmStackAlignment = SimdMemoryAlignment;
+static const uint32_t WasmTrapInstructionLength = 4;
 
 // Does this architecture support SIMD conversions between Uint32x4 and Float32x4?
 static constexpr bool SupportsUint32x4FloatConversions = false;
 
 // Does this architecture support comparisons of unsigned integer vectors?
 static constexpr bool SupportsUint8x16Compares = false;
 static constexpr bool SupportsUint16x8Compares = false;
 static constexpr bool SupportsUint32x4Compares = false;
--- a/js/src/jit/arm64/Assembler-arm64.h
+++ b/js/src/jit/arm64/Assembler-arm64.h
@@ -167,16 +167,17 @@ static constexpr bool SupportsSimd = fal
 static constexpr uint32_t SimdMemoryAlignment = 16;
 
 static_assert(CodeAlignment % SimdMemoryAlignment == 0,
   "Code alignment should be larger than any of the alignments which are used for "
   "the constant sections of the code buffer.  Thus it should be larger than the "
   "alignment for SIMD constants.");
 
 static const uint32_t WasmStackAlignment = SimdMemoryAlignment;
+static const uint32_t WasmTrapInstructionLength = 4;
 
 // Does this architecture support SIMD conversions between Uint32x4 and Float32x4?
 static constexpr bool SupportsUint32x4FloatConversions = false;
 
 // Does this architecture support comparisons of unsigned integer vectors?
 static constexpr bool SupportsUint8x16Compares = false;
 static constexpr bool SupportsUint16x8Compares = false;
 static constexpr bool SupportsUint32x4Compares = false;
@@ -452,16 +453,17 @@ static constexpr Register ABINonArgReg2 
 // This register may be volatile or nonvolatile. Avoid d31 which is the
 // ScratchDoubleReg.
 static constexpr FloatRegister ABINonArgDoubleReg = { FloatRegisters::s16, FloatRegisters::Single };
 
 // These registers may be volatile or nonvolatile.
 // Note: these three registers are all guaranteed to be different
 static constexpr Register ABINonArgReturnReg0 = r8;
 static constexpr Register ABINonArgReturnReg1 = r9;
+static constexpr Register ABINonVolatileReg { Registers::x19 };
 
 // This register is guaranteed to be clobberable during the prologue and
 // epilogue of an ABI call which must preserve both ABI argument, return
 // and non-volatile registers.
 static constexpr Register ABINonArgReturnVolatileReg = lr;
 
 // TLS pointer argument register for WebAssembly functions. This must not alias
 // any other register used for passing function arguments or return values.
--- a/js/src/jit/arm64/vixl/MozSimulator-vixl.cpp
+++ b/js/src/jit/arm64/vixl/MozSimulator-vixl.cpp
@@ -262,17 +262,17 @@ Simulator::handle_wasm_seg_fault(uintptr
 
     MOZ_RELEASE_ASSERT(&instance->code() == &moduleSegment->code());
 
     if (!instance->memoryAccessInGuardRegion((uint8_t*)addr, numBytes))
         return false;
 
     const js::wasm::MemoryAccess* memoryAccess = instance->code().lookupMemoryAccess(pc);
     if (!memoryAccess) {
-        act->startWasmTrap(wasm::Trap::OutOfBounds, 0, registerState());
+        act->startWasmTrap(js::wasm::Trap::OutOfBounds, 0, registerState());
         if (!instance->code().containsCodePC(pc))
             MOZ_CRASH("Cannot map PC to trap handler");
         set_pc((Instruction*)moduleSegment->outOfBoundsCode());
         return true;
     }
 
     MOZ_ASSERT(memoryAccess->hasTrapOutOfLineCode());
     set_pc((Instruction*)memoryAccess->trapOutOfLineCode(moduleSegment->base()));
--- a/js/src/jit/mips32/Assembler-mips32.h
+++ b/js/src/jit/mips32/Assembler-mips32.h
@@ -130,16 +130,17 @@ static constexpr uint32_t JitStackValueA
 static_assert(JitStackAlignment % sizeof(Value) == 0 && JitStackValueAlignment >= 1,
   "Stack alignment should be a non-zero multiple of sizeof(Value)");
 
 // TODO this is just a filler to prevent a build failure. The MIPS SIMD
 // alignment requirements still need to be explored.
 // TODO Copy the static_asserts from x64/x86 assembler files.
 static constexpr uint32_t SimdMemoryAlignment = 8;
 static constexpr uint32_t WasmStackAlignment = SimdMemoryAlignment;
+static const uint32_t WasmTrapInstructionLength = 4;
 
 // Does this architecture support SIMD conversions between Uint32x4 and Float32x4?
 static constexpr bool SupportsUint32x4FloatConversions = false;
 
 // Does this architecture support comparisons of unsigned integer vectors?
 static constexpr bool SupportsUint8x16Compares = false;
 static constexpr bool SupportsUint16x8Compares = false;
 static constexpr bool SupportsUint32x4Compares = false;
--- a/js/src/jit/mips64/Assembler-mips64.h
+++ b/js/src/jit/mips64/Assembler-mips64.h
@@ -141,16 +141,17 @@ static_assert(JitStackAlignment % sizeof
   "Stack alignment should be a non-zero multiple of sizeof(Value)");
 
 // TODO this is just a filler to prevent a build failure. The MIPS SIMD
 // alignment requirements still need to be explored.
 // TODO Copy the static_asserts from x64/x86 assembler files.
 static constexpr uint32_t SimdMemoryAlignment = 16;
 
 static constexpr uint32_t WasmStackAlignment = SimdMemoryAlignment;
+static const uint32_t WasmTrapInstructionLength = 4;
 
 // Does this architecture support SIMD conversions between Uint32x4 and Float32x4?
 static constexpr bool SupportsUint32x4FloatConversions = false;
 
 // Does this architecture support comparisons of unsigned integer vectors?
 static constexpr bool SupportsUint8x16Compares = false;
 static constexpr bool SupportsUint16x8Compares = false;
 static constexpr bool SupportsUint32x4Compares = false;
--- a/js/src/jit/none/Architecture-none.h
+++ b/js/src/jit/none/Architecture-none.h
@@ -14,16 +14,17 @@
 #include "jit/shared/Architecture-shared.h"
 
 namespace js {
 namespace jit {
 
 static const bool SupportsSimd = false;
 static const uint32_t SimdMemoryAlignment = 4; // Make it 4 to avoid a bunch of div-by-zero warnings
 static const uint32_t WasmStackAlignment = 8;
+static const uint32_t WasmTrapInstructionLength = 0;
 
 // Does this architecture support SIMD conversions between Uint32x4 and Float32x4?
 static constexpr bool SupportsUint32x4FloatConversions = false;
 
 // Does this architecture support comparisons of unsigned integer vectors?
 static constexpr bool SupportsUint8x16Compares = false;
 static constexpr bool SupportsUint16x8Compares = false;
 static constexpr bool SupportsUint32x4Compares = false;
--- a/js/src/jit/none/MacroAssembler-none.h
+++ b/js/src/jit/none/MacroAssembler-none.h
@@ -76,16 +76,17 @@ static constexpr Register64 ReturnReg64(
 #error "Bad architecture"
 #endif
 
 static constexpr Register ABINonArgReg0 { Registers::invalid_reg };
 static constexpr Register ABINonArgReg1 { Registers::invalid_reg };
 static constexpr Register ABINonArgReg2 { Registers::invalid_reg };
 static constexpr Register ABINonArgReturnReg0 { Registers::invalid_reg };
 static constexpr Register ABINonArgReturnReg1 { Registers::invalid_reg };
+static constexpr Register ABINonVolatileReg { Registers::invalid_reg };
 static constexpr Register ABINonArgReturnVolatileReg { Registers::invalid_reg };
 
 static constexpr FloatRegister ABINonArgDoubleReg = { FloatRegisters::invalid_reg };
 
 static constexpr Register WasmTableCallScratchReg { Registers::invalid_reg };
 static constexpr Register WasmTableCallSigReg { Registers::invalid_reg };
 static constexpr Register WasmTableCallIndexReg { Registers::invalid_reg };
 static constexpr Register WasmTlsReg { Registers::invalid_reg };
--- a/js/src/jit/shared/LIR-shared.h
+++ b/js/src/jit/shared/LIR-shared.h
@@ -1658,16 +1658,34 @@ class LInterruptCheck : public LInstruct
         return implicit_;
     }
 
     const LDefinition* temp() {
         return getTemp(0);
     }
 };
 
+class LWasmInterruptCheck : public LInstructionHelper<0, 1, 0>
+{
+  public:
+    LIR_HEADER(WasmInterruptCheck)
+
+    explicit LWasmInterruptCheck(const LAllocation& tlsData)
+      : LInstructionHelper(classOpcode)
+    {
+        setOperand(0, tlsData);
+    }
+    MWasmInterruptCheck* mir() const {
+        return mir_->toWasmInterruptCheck();
+    }
+    const LAllocation* tlsPtr() {
+        return getOperand(0);
+    }
+};
+
 class LDefVar : public LCallInstructionHelper<0, 1, 0>
 {
   public:
     LIR_HEADER(DefVar)
 
     explicit LDefVar(const LAllocation& envChain)
       : LCallInstructionHelper(classOpcode)
     {
--- a/js/src/jit/shared/LOpcodes-shared.h
+++ b/js/src/jit/shared/LOpcodes-shared.h
@@ -384,16 +384,17 @@
     _(NearbyIntF)                   \
     _(InCache)                      \
     _(InArray)                      \
     _(HasOwnCache)                  \
     _(InstanceOfO)                  \
     _(InstanceOfV)                  \
     _(InstanceOfCache)              \
     _(InterruptCheck)               \
+    _(WasmInterruptCheck)           \
     _(Rotate)                       \
     _(RotateI64)                    \
     _(GetDOMProperty)               \
     _(GetDOMMemberV)                \
     _(GetDOMMemberT)                \
     _(SetDOMProperty)               \
     _(CallDOMNative)                \
     _(IsCallableO)                  \
--- a/js/src/jit/x64/Assembler-x64.h
+++ b/js/src/jit/x64/Assembler-x64.h
@@ -245,16 +245,17 @@ static_assert(CodeAlignment % SimdMemory
   "the constant sections of the code buffer.  Thus it should be larger than the "
   "alignment for SIMD constants.");
 
 static_assert(JitStackAlignment % SimdMemoryAlignment == 0,
   "Stack alignment should be larger than any of the alignments which are used for "
   "spilled values.  Thus it should be larger than the alignment for SIMD accesses.");
 
 static const uint32_t WasmStackAlignment = SimdMemoryAlignment;
+static const uint32_t WasmTrapInstructionLength = 2;
 
 static const Scale ScalePointer = TimesEight;
 
 } // namespace jit
 } // namespace js
 
 #include "jit/x86-shared/Assembler-x86-shared.h"
 
--- a/js/src/jit/x86/Assembler-x86.h
+++ b/js/src/jit/x86/Assembler-x86.h
@@ -162,16 +162,17 @@ static_assert(CodeAlignment % SimdMemory
   "the constant sections of the code buffer.  Thus it should be larger than the "
   "alignment for SIMD constants.");
 
 static_assert(JitStackAlignment % SimdMemoryAlignment == 0,
   "Stack alignment should be larger than any of the alignments which are used for "
   "spilled values.  Thus it should be larger than the alignment for SIMD accesses.");
 
 static const uint32_t WasmStackAlignment = SimdMemoryAlignment;
+static const uint32_t WasmTrapInstructionLength = 2;
 
 struct ImmTag : public Imm32
 {
     explicit ImmTag(JSValueTag mask)
       : Imm32(int32_t(mask))
     { }
 };
 
--- a/js/src/vm/JSCompartment.cpp
+++ b/js/src/vm/JSCompartment.cpp
@@ -68,17 +68,17 @@ JSCompartment::JSCompartment(Zone* zone,
     arraySpeciesLookup(),
     globalWriteBarriered(0),
     detachedTypedObjects(0),
     objectMetadataState(ImmediateMetadata()),
     selfHostingScriptSource(nullptr),
     objectMetadataTable(nullptr),
     innerViews(zone),
     lazyArrayBuffers(nullptr),
-    wasm(zone),
+    wasm(zone->runtimeFromActiveCooperatingThread()),
     nonSyntacticLexicalEnvironments_(nullptr),
     gcIncomingGrayPointers(nullptr),
     debugModeBits(0),
     validAccessPtr(nullptr),
     randomKeyGenerator_(runtime_->forkRandomKeyGenerator()),
     scriptCountsMap(nullptr),
     scriptNameMap(nullptr),
     debugScriptMap(nullptr),
--- a/js/src/vm/MutexIDs.h
+++ b/js/src/vm/MutexIDs.h
@@ -31,37 +31,38 @@
                                       \
   _(WasmLazyStubsTier1,          475) \
   _(WasmLazyStubsTier2,          476) \
                                       \
   _(SharedImmutableStringsCache, 500) \
   _(FutexThread,                 500) \
   _(GeckoProfilerStrings,        500) \
   _(ProtectedRegionTree,         500) \
-  _(WasmSigIdSet,                500) \
   _(ShellOffThreadState,         500) \
   _(SimulatorCacheLock,          500) \
   _(Arm64SimulatorLock,          500) \
   _(IonSpewer,                   500) \
   _(PerfSpewer,                  500) \
   _(CacheIRSpewer,               500) \
   _(TraceLoggerThreadState,      500) \
   _(DateTimeInfoMutex,           500) \
   _(IcuTimeZoneStateMutex,       500) \
   _(ProcessExecutableRegion,     500) \
   _(OffThreadPromiseState,       500) \
   _(BufferStreamState,           500) \
+  _(SharedArrayGrow,             500) \
+  _(RuntimeScriptData,           500) \
+  _(WasmSigIdSet,                500) \
   _(WasmCodeProfilingLabels,     500) \
   _(WasmModuleTieringLock,       500) \
   _(WasmCompileTaskState,        500) \
   _(WasmCodeStreamEnd,           500) \
   _(WasmTailBytesPtr,            500) \
   _(WasmStreamStatus,            500) \
-  _(SharedArrayGrow,             500) \
-  _(RuntimeScriptData,           500) \
+  _(WasmRuntimeInstances,        500) \
                                       \
   _(ThreadId,                    600) \
   _(WasmCodeSegmentMap,          600) \
   _(TraceLoggerGraphState,       600) \
   _(VTuneLock,                   600)
 
 namespace js {
 namespace mutexid {
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -172,32 +172,35 @@ JSRuntime::JSRuntime(JSRuntime* parentRu
     offthreadIonCompilationEnabled_(true),
     parallelParsingEnabled_(true),
     autoWritableJitCodeActive_(false),
     oomCallback(nullptr),
     debuggerMallocSizeOf(ReturnZeroSize),
     lastAnimationTime(0),
     performanceMonitoring_(),
     stackFormat_(parentRuntime ? js::StackFormat::Default
-                               : js::StackFormat::SpiderMonkey)
+                               : js::StackFormat::SpiderMonkey),
+    wasmInstances(mutexid::WasmRuntimeInstances)
 {
     liveRuntimesCount++;
 
     /* Initialize infallibly first, so we can goto bad and JS_DestroyRuntime. */
 
     PodZero(&asmJSCacheOps);
     lcovOutput().init();
 }
 
 JSRuntime::~JSRuntime()
 {
     MOZ_ASSERT(!initialized_);
 
     DebugOnly<size_t> oldCount = liveRuntimesCount--;
     MOZ_ASSERT(oldCount > 0);
+
+    MOZ_ASSERT(wasmInstances.lock()->empty());
 }
 
 bool
 JSRuntime::init(JSContext* cx, uint32_t maxbytes, uint32_t maxNurseryBytes)
 {
 #ifdef DEBUG
     MOZ_ASSERT(!initialized_);
     initialized_ = true;
@@ -504,16 +507,18 @@ JSRuntime::addSizeOfIncludingThis(mozill
         for (ScriptDataTable::Range r = scriptDataTable(lock).all(); !r.empty(); r.popFront())
             rtSizes->scriptData += mallocSizeOf(r.front());
     }
 
     if (jitRuntime_) {
         jitRuntime_->execAlloc().addSizeOfCode(&rtSizes->code);
         jitRuntime_->backedgeExecAlloc().addSizeOfCode(&rtSizes->code);
     }
+
+    rtSizes->wasmRuntime += wasmInstances.lock()->sizeOfExcludingThis(mallocSizeOf);
 }
 
 static bool
 InvokeInterruptCallback(JSContext* cx)
 {
     MOZ_ASSERT(cx->requestDepth >= 1);
     MOZ_ASSERT(!cx->compartment()->isAtomsCompartment());
 
@@ -594,16 +599,17 @@ JSContext::requestInterrupt(InterruptMod
         // not regularly polled. Wake ilooping Ion code, irregexp JIT code and
         // Atomics.wait()
         interruptRegExpJit_ = true;
         fx.lock();
         if (fx.isWaiting())
             fx.wake(FutexThread::WakeForJSInterrupt);
         fx.unlock();
         jit::InterruptRunningCode(this);
+        wasm::InterruptRunningCode(this);
     }
 }
 
 bool
 JSContext::handleInterrupt()
 {
     MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime()));
     if (interrupt_ || jitStackLimit == UINTPTR_MAX) {
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -998,16 +998,21 @@ struct JSRuntime : public js::MallocProv
     js::ActiveThreadData<js::RuntimeCaches> caches_;
   public:
     js::RuntimeCaches& caches() { return caches_.ref(); }
 
     // When wasm traps, the signal handler records some data for unwinding
     // purposes. Wasm code can't trap reentrantly.
     js::ActiveThreadData<mozilla::Maybe<js::wasm::TrapData>> wasmTrapData;
 
+    // List of all the live wasm::Instances in the runtime. Equal to the union
+    // of all instances registered in all JSCompartments. Accessed from watchdog
+    // threads for purposes of wasm::InterruptRunningCode().
+    js::ExclusiveData<js::wasm::InstanceVector> wasmInstances;
+
   public:
 #if defined(NIGHTLY_BUILD)
     // Support for informing the embedding of any error thrown.
     // This mechanism is designed to let the embedding
     // log/report/fail in case certain errors are thrown
     // (e.g. SyntaxError, ReferenceError or TypeError
     // in critical code).
     struct ErrorInterceptionSupport {
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -1757,17 +1757,23 @@ jit::JitActivation::startWasmTrap(wasm::
     const wasm::Code& code = fp->tls->instance->code();
     MOZ_RELEASE_ASSERT(&code == wasm::LookupCode(pc));
 
     // If the frame was unwound, the bytecodeOffset must be recovered from the
     // callsite so that it is accurate.
     if (unwound)
         bytecodeOffset = code.lookupCallSite(pc)->lineOrBytecode();
 
-    cx_->runtime()->wasmTrapData.ref().emplace(pc, trap, bytecodeOffset);
+    wasm::TrapData trapData;
+    trapData.resumePC = ((uint8_t*)state.pc) + jit::WasmTrapInstructionLength;
+    trapData.unwoundPC = pc;
+    trapData.trap = trap;
+    trapData.bytecodeOffset = bytecodeOffset;
+
+    cx_->runtime()->wasmTrapData = Some(trapData);
     setWasmExitFP(fp);
 }
 
 void
 jit::JitActivation::finishWasmTrap()
 {
     MOZ_ASSERT(isWasmTrapping());
 
@@ -1784,27 +1790,32 @@ jit::JitActivation::isWasmTrapping() con
 
     Activation* act = cx_->activation();
     while (act && !act->hasWasmExitFP())
         act = act->prev();
 
     if (act != this)
         return false;
 
-    DebugOnly<const wasm::Frame*> fp = wasmExitFP();
-    DebugOnly<void*> unwindPC = rt->wasmTrapData->pc;
-    MOZ_ASSERT(fp->instance()->code().containsCodePC(unwindPC));
+    MOZ_ASSERT(wasmExitFP()->instance()->code().containsCodePC(rt->wasmTrapData->unwoundPC));
     return true;
 }
 
 void*
-jit::JitActivation::wasmTrapPC() const
+jit::JitActivation::wasmTrapResumePC() const
 {
     MOZ_ASSERT(isWasmTrapping());
-    return cx_->runtime()->wasmTrapData->pc;
+    return cx_->runtime()->wasmTrapData->resumePC;
+}
+
+void*
+jit::JitActivation::wasmTrapUnwoundPC() const
+{
+    MOZ_ASSERT(isWasmTrapping());
+    return cx_->runtime()->wasmTrapData->unwoundPC;
 }
 
 uint32_t
 jit::JitActivation::wasmTrapBytecodeOffset() const
 {
     MOZ_ASSERT(isWasmTrapping());
     return cx_->runtime()->wasmTrapData->bytecodeOffset;
 }
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -1708,17 +1708,18 @@ class JitActivation : public Activation
     }
     static size_t offsetOfEncodedWasmExitReason() {
         return offsetof(JitActivation, encodedWasmExitReason_);
     }
 
     void startWasmTrap(wasm::Trap trap, uint32_t bytecodeOffset, const wasm::RegisterState& state);
     void finishWasmTrap();
     bool isWasmTrapping() const;
-    void* wasmTrapPC() const;
+    void* wasmTrapResumePC() const;
+    void* wasmTrapUnwoundPC() const;
     uint32_t wasmTrapBytecodeOffset() const;
 };
 
 // A filtering of the ActivationIterator to only stop at JitActivations.
 class JitActivationIterator : public ActivationIterator
 {
     void settle() {
         while (!done() && !activation_->isJit())
--- a/js/src/wasm/WasmBaselineCompile.cpp
+++ b/js/src/wasm/WasmBaselineCompile.cpp
@@ -3366,19 +3366,20 @@ class BaseCompiler final : public BaseCo
     void moveImmF32(float f, RegF32 dest) {
         masm.loadConstantFloat32(f, dest);
     }
 
     void moveImmF64(double d, RegF64 dest) {
         masm.loadConstantDouble(d, dest);
     }
 
-    void addInterruptCheck()
-    {
-        // TODO
+    void addInterruptCheck() {
+        ScratchI32 tmp(*this);
+        masm.loadWasmTlsRegFromFrame(tmp);
+        masm.wasmInterruptCheck(tmp, bytecodeOffset());
     }
 
     void jumpTable(const LabelVector& labels, Label* theTable) {
         // Flush constant pools to ensure that the table is never interrupted by
         // constant pool entries.
         masm.flush();
 
         masm.bind(theTable);
--- a/js/src/wasm/WasmBuiltins.cpp
+++ b/js/src/wasm/WasmBuiltins.cpp
@@ -206,71 +206,104 @@ static void*
 WasmHandleThrow()
 {
     JitActivation* activation = CallingActivation();
     JSContext* cx = activation->cx();
     WasmFrameIter iter(activation);
     return HandleThrow(cx, iter);
 }
 
+// Unconditionally returns nullptr per calling convention of OnTrap().
+static void*
+ReportError(JSContext* cx, unsigned errorNumber)
+{
+    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, errorNumber);
+    return nullptr;
+};
+
+// Has the same return-value convention as OnTrap().
+static void*
+CheckInterrupt(JSContext* cx, JitActivation* activation)
+{
+    ResetInterruptState(cx);
+
+    if (!CheckForInterrupt(cx))
+        return nullptr;
+
+    void* resumePC = activation->wasmTrapResumePC();
+    activation->finishWasmTrap();
+    return resumePC;
+}
+
+// The calling convention between this function and its caller in the stub
+// generated by GenerateTrapExit() is:
+//   - return nullptr if the stub should jump to the throw stub to unwind
+//     the activation;
+//   - return the (non-null) resumePC that should be jumped if execution should
+//     resume after the trap.
+static void*
+OnTrap(Trap trap)
+{
+    JitActivation* activation = CallingActivation();
+    JSContext* cx = activation->cx();
+
+    switch (trap) {
+      case Trap::Unreachable:
+        return ReportError(cx, JSMSG_WASM_UNREACHABLE);
+      case Trap::IntegerOverflow:
+        return ReportError(cx, JSMSG_WASM_INTEGER_OVERFLOW);
+      case Trap::InvalidConversionToInteger:
+        return ReportError(cx, JSMSG_WASM_INVALID_CONVERSION);
+      case Trap::IntegerDivideByZero:
+        return ReportError(cx, JSMSG_WASM_INT_DIVIDE_BY_ZERO);
+      case Trap::IndirectCallToNull:
+        return ReportError(cx, JSMSG_WASM_IND_CALL_TO_NULL);
+      case Trap::IndirectCallBadSig:
+        return ReportError(cx, JSMSG_WASM_IND_CALL_BAD_SIG);
+      case Trap::ImpreciseSimdConversion:
+        return ReportError(cx, JSMSG_SIMD_FAILED_CONVERSION);
+      case Trap::OutOfBounds:
+        return ReportError(cx, JSMSG_WASM_OUT_OF_BOUNDS);
+      case Trap::UnalignedAccess:
+        return ReportError(cx, JSMSG_WASM_UNALIGNED_ACCESS);
+      case Trap::CheckInterrupt:
+        return CheckInterrupt(cx, activation);
+      case Trap::StackOverflow:
+        // TlsData::setInterrupt() causes a fake stack overflow. Since
+        // TlsData::setInterrupt() is called racily, it's possible for a real
+        // stack overflow to trap, followed by a racy call to setInterrupt().
+        // Thus, we must check for a real stack overflow first before we
+        // CheckInterrupt() and possibly resume execution.
+        if (!CheckRecursionLimit(cx))
+            return nullptr;
+        if (activation->wasmExitFP()->tls->isInterrupted())
+            return CheckInterrupt(cx, activation);
+        return ReportError(cx, JSMSG_OVER_RECURSED);
+      case Trap::ThrowReported:
+        // Error was already reported under another name.
+        return nullptr;
+      case Trap::Limit:
+        break;
+    }
+
+    MOZ_CRASH("unexpected trap");
+}
+
 static void
 WasmOldReportTrap(int32_t trapIndex)
 {
-    JSContext* cx = TlsContext.get();
-
     MOZ_ASSERT(trapIndex < int32_t(Trap::Limit) && trapIndex >= 0);
-    Trap trap = Trap(trapIndex);
-
-    unsigned errorNumber;
-    switch (trap) {
-      case Trap::Unreachable:
-        errorNumber = JSMSG_WASM_UNREACHABLE;
-        break;
-      case Trap::IntegerOverflow:
-        errorNumber = JSMSG_WASM_INTEGER_OVERFLOW;
-        break;
-      case Trap::InvalidConversionToInteger:
-        errorNumber = JSMSG_WASM_INVALID_CONVERSION;
-        break;
-      case Trap::IntegerDivideByZero:
-        errorNumber = JSMSG_WASM_INT_DIVIDE_BY_ZERO;
-        break;
-      case Trap::IndirectCallToNull:
-        errorNumber = JSMSG_WASM_IND_CALL_TO_NULL;
-        break;
-      case Trap::IndirectCallBadSig:
-        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);
+    DebugOnly<void*> resumePC = OnTrap(Trap(trapIndex));
+    MOZ_ASSERT(!resumePC);
 }
 
-static void
-WasmReportTrap()
+static void*
+WasmOnTrap()
 {
-    Trap trap = TlsContext.get()->runtime()->wasmTrapData->trap;
-    WasmOldReportTrap(int32_t(trap));
+    return OnTrap(TlsContext.get()->runtime()->wasmTrapData->trap);
 }
 
 static void
 WasmReportOutOfBounds()
 {
     JSContext* cx = TlsContext.get();
     JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_OUT_OF_BOUNDS);
 }
@@ -489,19 +522,19 @@ wasm::AddressOf(SymbolicAddress imm, ABI
 {
     switch (imm) {
       case SymbolicAddress::HandleDebugTrap:
         *abiType = Args_General0;
         return FuncCast(WasmHandleDebugTrap, *abiType);
       case SymbolicAddress::HandleThrow:
         *abiType = Args_General0;
         return FuncCast(WasmHandleThrow, *abiType);
-      case SymbolicAddress::ReportTrap:
+      case SymbolicAddress::OnTrap:
         *abiType = Args_General0;
-        return FuncCast(WasmReportTrap, *abiType);
+        return FuncCast(WasmOnTrap, *abiType);
       case SymbolicAddress::OldReportTrap:
         *abiType = Args_General1;
         return FuncCast(WasmOldReportTrap, *abiType);
       case SymbolicAddress::ReportOutOfBounds:
         *abiType = Args_General0;
         return FuncCast(WasmReportOutOfBounds, *abiType);
       case SymbolicAddress::ReportUnalignedAccess:
         *abiType = Args_General0;
@@ -663,17 +696,17 @@ wasm::AddressOf(SymbolicAddress imm, ABI
 bool
 wasm::NeedsBuiltinThunk(SymbolicAddress sym)
 {
     // Some functions don't want to a thunk, because they already have one or
     // they don't have frame info.
     switch (sym) {
       case SymbolicAddress::HandleDebugTrap:          // GenerateDebugTrapStub
       case SymbolicAddress::HandleThrow:              // GenerateThrowStub
-      case SymbolicAddress::ReportTrap:               // GenerateTrapExit
+      case SymbolicAddress::OnTrap:                   // GenerateTrapExit
       case SymbolicAddress::OldReportTrap:            // GenerateOldTrapExit
       case SymbolicAddress::ReportOutOfBounds:        // GenerateOutOfBoundsExit
       case SymbolicAddress::ReportUnalignedAccess:    // GenerateUnalignedExit
       case SymbolicAddress::CallImport_Void:          // GenerateImportInterpExit
       case SymbolicAddress::CallImport_I32:
       case SymbolicAddress::CallImport_I64:
       case SymbolicAddress::CallImport_F64:
       case SymbolicAddress::CoerceInPlace_ToInt32:    // GenerateImportJitExit
--- a/js/src/wasm/WasmCompartment.cpp
+++ b/js/src/wasm/WasmCompartment.cpp
@@ -21,17 +21,18 @@
 #include "vm/JSCompartment.h"
 #include "wasm/WasmInstance.h"
 
 #include "vm/Debugger-inl.h"
 
 using namespace js;
 using namespace wasm;
 
-Compartment::Compartment(Zone* zone)
+Compartment::Compartment(JSRuntime* rt)
+  : runtime_(rt)
 {}
 
 Compartment::~Compartment()
 {
     MOZ_ASSERT(instances_.empty());
 }
 
 struct InstanceComparator
@@ -57,50 +58,85 @@ struct InstanceComparator
 
         return target.codeBase(targetTier) < instance->codeBase(instanceTier) ? -1 : 1;
     }
 };
 
 bool
 Compartment::registerInstance(JSContext* cx, HandleWasmInstanceObject instanceObj)
 {
+    MOZ_ASSERT(runtime_ == cx->runtime());
+
     Instance& instance = instanceObj->instance();
     MOZ_ASSERT(this == &instance.compartment()->wasm);
 
     instance.ensureProfilingLabels(cx->runtime()->geckoProfiler().enabled());
 
     if (instance.debugEnabled() && instance.compartment()->debuggerObservesAllExecution())
         instance.ensureEnterFrameTrapsState(cx, true);
 
-    size_t index;
-    if (BinarySearchIf(instances_, 0, instances_.length(), InstanceComparator(instance), &index))
-        MOZ_CRASH("duplicate registration");
+    {
+        if (!instances_.reserve(instances_.length() + 1))
+            return false;
+
+        auto runtimeInstances = cx->runtime()->wasmInstances.lock();
+        if (!runtimeInstances->reserve(runtimeInstances->length() + 1))
+            return false;
 
-    if (!instances_.insert(instances_.begin() + index, &instance)) {
-        ReportOutOfMemory(cx);
-        return false;
+        // To avoid implementing rollback, do not fail after mutations start.
+
+        InstanceComparator cmp(instance);
+        size_t index;
+
+        MOZ_ALWAYS_FALSE(BinarySearchIf(instances_, 0, instances_.length(), cmp, &index));
+        MOZ_ALWAYS_TRUE(instances_.insert(instances_.begin() + index, &instance));
+
+        MOZ_ALWAYS_FALSE(BinarySearchIf(runtimeInstances.get(), 0, runtimeInstances->length(), cmp, &index));
+        MOZ_ALWAYS_TRUE(runtimeInstances->insert(runtimeInstances->begin() + index, &instance));
     }
 
+    // Notify the debugger after wasmInstances is unlocked.
     Debugger::onNewWasmInstance(cx, instanceObj);
     return true;
 }
 
 void
 Compartment::unregisterInstance(Instance& instance)
 {
+    InstanceComparator cmp(instance);
     size_t index;
-    if (!BinarySearchIf(instances_, 0, instances_.length(), InstanceComparator(instance), &index))
-        return;
-    instances_.erase(instances_.begin() + index);
+
+    if (BinarySearchIf(instances_, 0, instances_.length(), cmp, &index))
+        instances_.erase(instances_.begin() + index);
+
+    auto runtimeInstances = runtime_->wasmInstances.lock();
+    if (BinarySearchIf(runtimeInstances.get(), 0, runtimeInstances->length(), cmp, &index))
+        runtimeInstances->erase(runtimeInstances->begin() + index);
 }
 
 void
 Compartment::ensureProfilingLabels(bool profilingEnabled)
 {
     for (Instance* instance : instances_)
         instance->ensureProfilingLabels(profilingEnabled);
 }
 
 void
 Compartment::addSizeOfExcludingThis(MallocSizeOf mallocSizeOf, size_t* compartmentTables)
 {
     *compartmentTables += instances_.sizeOfExcludingThis(mallocSizeOf);
 }
+
+void
+wasm::InterruptRunningCode(JSContext* cx)
+{
+    auto runtimeInstances = cx->runtime()->wasmInstances.lock();
+    for (Instance* instance : runtimeInstances.get())
+        instance->tlsData()->setInterrupt();
+}
+
+void
+wasm::ResetInterruptState(JSContext* cx)
+{
+    auto runtimeInstances = cx->runtime()->wasmInstances.lock();
+    for (Instance* instance : runtimeInstances.get())
+        instance->tlsData()->resetInterrupt(cx);
+}
--- a/js/src/wasm/WasmCompartment.h
+++ b/js/src/wasm/WasmCompartment.h
@@ -19,29 +19,28 @@
 #ifndef wasm_compartment_h
 #define wasm_compartment_h
 
 #include "wasm/WasmJS.h"
 
 namespace js {
 namespace wasm {
 
-typedef Vector<Instance*, 0, SystemAllocPolicy> InstanceVector;
-
 // wasm::Compartment lives in JSCompartment and contains the wasm-related
 // per-compartment state. wasm::Compartment tracks every live instance in the
 // compartment and must be notified, via registerInstance(), of any new
 // WasmInstanceObject.
 
 class Compartment
 {
+    JSRuntime* runtime_;
     InstanceVector instances_;
 
   public:
-    explicit Compartment(Zone* zone);
+    explicit Compartment(JSRuntime* rt);
     ~Compartment();
 
     // 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.
 
@@ -59,12 +58,25 @@ class Compartment
 
     void ensureProfilingLabels(bool profilingEnabled);
 
     // about:memory reporting
 
     void addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf, size_t* compartmentTables);
 };
 
+// Interrupt all running wasm Instances that have been registered with
+// wasm::Compartments in the given JSContext.
+
+extern void
+InterruptRunningCode(JSContext* cx);
+
+// After a wasm Instance sees an interrupt request and calls
+// CheckForInterrupt(), it should call RunningCodeInterrupted() to clear the
+// interrupt request for all wasm Instances to avoid spurious trapping.
+
+void
+ResetInterruptState(JSContext* cx);
+
 } // namespace wasm
 } // namespace js
 
 #endif // wasm_compartment_h
--- a/js/src/wasm/WasmFrameIter.cpp
+++ b/js/src/wasm/WasmFrameIter.cpp
@@ -45,19 +45,19 @@ WasmFrameIter::WasmFrameIter(JitActivati
     MOZ_ASSERT(fp_);
 
     // When the stack is captured during a trap (viz., to create the .stack
     // for an Error object), use the pc/bytecode information captured by the
     // signal handler in the runtime.
 
     if (activation->isWasmTrapping()) {
         code_ = &fp_->tls->instance->code();
-        MOZ_ASSERT(code_ == LookupCode(activation->wasmTrapPC()));
+        MOZ_ASSERT(code_ == LookupCode(activation->wasmTrapUnwoundPC()));
 
-        codeRange_ = code_->lookupFuncRange(activation->wasmTrapPC());
+        codeRange_ = code_->lookupFuncRange(activation->wasmTrapUnwoundPC());
         MOZ_ASSERT(codeRange_);
 
         lineOrBytecode_ = activation->wasmTrapBytecodeOffset();
 
         MOZ_ASSERT(!done());
         return;
     }
 
@@ -1069,17 +1069,17 @@ ProfilingFrameIterator::operator++()
 
 static const char*
 ThunkedNativeToDescription(SymbolicAddress func)
 {
     MOZ_ASSERT(NeedsBuiltinThunk(func));
     switch (func) {
       case SymbolicAddress::HandleDebugTrap:
       case SymbolicAddress::HandleThrow:
-      case SymbolicAddress::ReportTrap:
+      case SymbolicAddress::OnTrap:
       case SymbolicAddress::OldReportTrap:
       case SymbolicAddress::ReportOutOfBounds:
       case SymbolicAddress::ReportUnalignedAccess:
       case SymbolicAddress::CallImport_Void:
       case SymbolicAddress::CallImport_I32:
       case SymbolicAddress::CallImport_I64:
       case SymbolicAddress::CallImport_F64:
       case SymbolicAddress::CoerceInPlace_ToInt32:
--- a/js/src/wasm/WasmInstance.cpp
+++ b/js/src/wasm/WasmInstance.cpp
@@ -401,17 +401,17 @@ Instance::Instance(JSContext* cx,
     MOZ_ASSERT(tables_.length() == metadata().tables.length());
 
     tlsData()->memoryBase = memory ? memory->buffer().dataPointerEither().unwrap() : nullptr;
 #ifndef WASM_HUGE_MEMORY
     tlsData()->boundsCheckLimit = memory ? memory->buffer().wasmBoundsCheckLimit() : 0;
 #endif
     tlsData()->instance = this;
     tlsData()->cx = cx;
-    tlsData()->stackLimit = cx->stackLimitForJitCode(JS::StackForUntrustedScript);
+    tlsData()->resetInterrupt(cx);
     tlsData()->jumpTable = code_->tieringJumpTable();
 
     Tier callerTier = code_->bestTier();
 
     for (size_t i = 0; i < metadata(callerTier).funcImports.length(); i++) {
         HandleFunction f = funcImports[i];
         const FuncImport& fi = metadata(callerTier).funcImports[i];
         FuncImportTls& import = funcImportTls(fi);
--- a/js/src/wasm/WasmIonCompile.cpp
+++ b/js/src/wasm/WasmIonCompile.cpp
@@ -1028,17 +1028,19 @@ class FunctionCompiler
     {
         if (inDeadCode())
             return;
         curBlock_->add(MWasmStoreGlobalVar::New(alloc(), globalDataOffset, v, tlsPointer_));
     }
 
     void addInterruptCheck()
     {
-        // TODO
+        if (inDeadCode())
+            return;
+        curBlock_->add(MWasmInterruptCheck::New(alloc(), tlsPointer_, bytecodeOffset()));
     }
 
     MDefinition* extractSimdElement(unsigned lane, MDefinition* base, MIRType type, SimdSign sign)
     {
         if (inDeadCode())
             return nullptr;
 
         MOZ_ASSERT(IsSimdType(base->type()));
--- a/js/src/wasm/WasmSignalHandlers.h
+++ b/js/src/wasm/WasmSignalHandlers.h
@@ -66,21 +66,18 @@ class MachExceptionHandler
     bool install(JSContext* cx);
 };
 #endif
 
 // On trap, the bytecode offset to be reported in callstacks is saved.
 
 struct TrapData
 {
-    void* pc;
+    void* resumePC;
+    void* unwoundPC;
     Trap trap;
     uint32_t bytecodeOffset;
-
-    TrapData(void* pc, Trap trap, uint32_t bytecodeOffset)
-      : pc(pc), trap(trap), bytecodeOffset(bytecodeOffset)
-    {}
 };
 
 } // namespace wasm
 } // namespace js
 
 #endif // wasm_signal_handlers_h
--- a/js/src/wasm/WasmStubs.cpp
+++ b/js/src/wasm/WasmStubs.cpp
@@ -1353,35 +1353,76 @@ wasm::GenerateBuiltinThunk(MacroAssemble
     if (!UseHardFpABI() && IsFloatingPointType(retType))
         masm.ma_vxfer(r0, r1, d0);
 #endif
 
     GenerateExitEpilogue(masm, framePushed, exitReason, offsets);
     return FinishOffsets(masm, offsets);
 }
 
+#if defined(JS_CODEGEN_ARM)
+static const LiveRegisterSet RegsToPreserve(
+    GeneralRegisterSet(Registers::AllMask & ~((uint32_t(1) << Registers::sp) |
+                                              (uint32_t(1) << Registers::pc))),
+    FloatRegisterSet(FloatRegisters::AllDoubleMask));
+static_assert(!SupportsSimd, "high lanes of SIMD registers need to be saved too.");
+#elif defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
+static const LiveRegisterSet RegsToPreserve(
+    GeneralRegisterSet(Registers::AllMask & ~((uint32_t(1) << Registers::k0) |
+                                              (uint32_t(1) << Registers::k1) |
+                                              (uint32_t(1) << Registers::sp) |
+                                              (uint32_t(1) << Registers::zero))),
+    FloatRegisterSet(FloatRegisters::AllDoubleMask));
+static_assert(!SupportsSimd, "high lanes of SIMD registers need to be saved too.");
+#else
+static const LiveRegisterSet RegsToPreserve(
+    GeneralRegisterSet(Registers::AllMask & ~(uint32_t(1) << Registers::StackPointer)),
+    FloatRegisterSet(FloatRegisters::AllMask));
+#endif
+
 // Generate a stub which calls WasmReportTrap() and can be executed by having
 // the signal handler redirect PC from any trapping instruction.
 static bool
 GenerateTrapExit(MacroAssembler& masm, Label* throwLabel, Offsets* offsets)
 {
     masm.haltingAlign(CodeAlignment);
 
     offsets->begin = masm.currentOffset();
 
+    // Traps can only happen at well-defined program points. However, since
+    // traps may resume and the optimal assumption for the surrounding code is
+    // that registers are not clobbered, we need to preserve all registers in
+    // the trap exit. One simplifying assumption is that flags may be clobbered.
+    // Push a dummy word to use as return address below.
+    masm.push(ImmWord(0));
+    masm.setFramePushed(0);
+    masm.PushRegsInMask(RegsToPreserve);
+
     // We know that StackPointer is word-aligned, but not necessarily
     // stack-aligned, so we need to align it dynamically.
+    Register preAlignStackPointer = ABINonVolatileReg;
+    masm.moveStackPtrTo(preAlignStackPointer);
     masm.andToStackPtr(Imm32(~(ABIStackAlignment - 1)));
     if (ShadowStackSpace)
         masm.subFromStackPtr(Imm32(ShadowStackSpace));
 
     masm.assertStackAlignment(ABIStackAlignment);
-    masm.call(SymbolicAddress::ReportTrap);
+    masm.call(SymbolicAddress::OnTrap);
+
+    // OnTrap returns null if control should transfer to the throw stub.
+    masm.branchTestPtr(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
 
-    masm.jump(throwLabel);
+    // Otherwise, the return value is the TrapData::resumePC we must jump to.
+    // We must restore register state before jumping, which will clobber
+    // ReturnReg, so store ReturnReg in the above-reserved stack slot which we
+    // use to jump to via ret.
+    masm.moveToStackPtr(preAlignStackPointer);
+    masm.storePtr(ReturnReg, Address(masm.getStackPointer(), masm.framePushed()));
+    masm.PopRegsInMask(RegsToPreserve);
+    masm.ret();
 
     return FinishOffsets(masm, offsets);
 }
 
 // Generate a stub that calls into WasmOldReportTrap with the right trap reason.
 // This stub is called with ABIStackAlignment by a trap out-of-line path. An
 // exit prologue/epilogue is used so that stack unwinding picks up the
 // current JitActivation. Unwinding will begin at the caller of this trap exit.
@@ -1455,36 +1496,16 @@ GenerateOutOfBoundsExit(MacroAssembler& 
 
 static bool
 GenerateUnalignedExit(MacroAssembler& masm, Label* throwLabel, Offsets* offsets)
 {
     return GenerateGenericMemoryAccessTrap(masm, SymbolicAddress::ReportUnalignedAccess, throwLabel,
                                            offsets);
 }
 
-#if defined(JS_CODEGEN_ARM)
-static const LiveRegisterSet AllRegsExceptPCSP(
-    GeneralRegisterSet(Registers::AllMask & ~((uint32_t(1) << Registers::sp) |
-                                              (uint32_t(1) << Registers::pc))),
-    FloatRegisterSet(FloatRegisters::AllDoubleMask));
-static_assert(!SupportsSimd, "high lanes of SIMD registers need to be saved too.");
-#elif defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
-static const LiveRegisterSet AllUserRegsExceptSP(
-    GeneralRegisterSet(Registers::AllMask & ~((uint32_t(1) << Registers::k0) |
-                                              (uint32_t(1) << Registers::k1) |
-                                              (uint32_t(1) << Registers::sp) |
-                                              (uint32_t(1) << Registers::zero))),
-    FloatRegisterSet(FloatRegisters::AllDoubleMask));
-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
-
 // Generate a stub that restores the stack pointer to what it was on entry to
 // the wasm activation, sets the return register to 'false' and then executes a
 // return which will return from this wasm activation to the caller. This stub
 // should only be called after the caller has reported an error.
 static bool
 GenerateThrowStub(MacroAssembler& masm, Label* throwLabel, Offsets* offsets)
 {
     masm.haltingAlign(CodeAlignment);
@@ -1633,16 +1654,17 @@ wasm::GenerateStubs(const ModuleEnvironm
           case Trap::Unreachable:
           case Trap::IntegerOverflow:
           case Trap::InvalidConversionToInteger:
           case Trap::IntegerDivideByZero:
           case Trap::IndirectCallToNull:
           case Trap::IndirectCallBadSig:
           case Trap::ImpreciseSimdConversion:
           case Trap::StackOverflow:
+          case Trap::CheckInterrupt:
           case Trap::ThrowReported:
             break;
           // The TODO list of "old" traps to convert to new traps:
           case Trap::OutOfBounds:
           case Trap::UnalignedAccess: {
             CallableOffsets offsets;
             if (!GenerateOldTrapExit(masm, trap, &throwLabel, &offsets))
                 return false;
--- a/js/src/wasm/WasmTypes.cpp
+++ b/js/src/wasm/WasmTypes.cpp
@@ -906,8 +906,28 @@ wasm::CreateTlsData(uint32_t globalDataL
     if (!allocatedBase)
         return nullptr;
 
     auto* tlsData = reinterpret_cast<TlsData*>(AlignBytes(uintptr_t(allocatedBase), TlsDataAlign));
     tlsData->allocatedBase = allocatedBase;
 
     return UniqueTlsData(tlsData);
 }
+
+void
+TlsData::setInterrupt()
+{
+    interrupt = true;
+    stackLimit = UINTPTR_MAX;
+}
+
+bool
+TlsData::isInterrupted() const
+{
+    return interrupt || stackLimit == UINTPTR_MAX;
+}
+
+void
+TlsData::resetInterrupt(JSContext* cx)
+{
+    interrupt = false;
+    stackLimit = cx->stackLimitForJitCode(JS::StackForUntrustedScript);
+}
--- a/js/src/wasm/WasmTypes.h
+++ b/js/src/wasm/WasmTypes.h
@@ -80,35 +80,36 @@ using mozilla::Move;
 using mozilla::MallocSizeOf;
 using mozilla::Nothing;
 using mozilla::PodZero;
 using mozilla::PodCopy;
 using mozilla::PodEqual;
 using mozilla::Some;
 using mozilla::Unused;
 
-typedef Vector<uint32_t, 0, SystemAllocPolicy> Uint32Vector;
-typedef Vector<uint8_t, 0, SystemAllocPolicy> Bytes;
-typedef UniquePtr<Bytes> UniqueBytes;
-typedef UniquePtr<const Bytes> UniqueConstBytes;
-typedef Vector<char, 0, SystemAllocPolicy> UTF8Bytes;
-
 typedef int8_t I8x16[16];
 typedef int16_t I16x8[8];
 typedef int32_t I32x4[4];
 typedef float F32x4[4];
 
 class Code;
 class DebugState;
 class GeneratedSourceMap;
 class Memory;
 class Module;
 class Instance;
 class Table;
 
+typedef Vector<uint32_t, 0, SystemAllocPolicy> Uint32Vector;
+typedef Vector<uint8_t, 0, SystemAllocPolicy> Bytes;
+typedef UniquePtr<Bytes> UniqueBytes;
+typedef UniquePtr<const Bytes> UniqueConstBytes;
+typedef Vector<char, 0, SystemAllocPolicy> UTF8Bytes;
+typedef Vector<Instance*, 0, SystemAllocPolicy> InstanceVector;
+
 // To call Vector::podResizeToFit, a type must specialize mozilla::IsPod
 // which is pretty verbose to do within js::wasm, so factor that process out
 // into a macro.
 
 #define WASM_DECLARE_POD_VECTOR(Type, VectorName)                               \
 } } namespace mozilla {                                                         \
 template <> struct IsPod<js::wasm::Type> : TrueType {};                         \
 } namespace js { namespace wasm {                                               \
@@ -923,16 +924,20 @@ enum class Trap
     // (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,
 
+    // The wasm execution has potentially run too long and the engine must call
+    // CheckForInterrupt(). This trap is resumable.
+    CheckInterrupt,
+
     // Signal an error that was reported in C++ code.
     ThrowReported,
 
     Limit
 };
 
 // A wrapper around the bytecode offset of a wasm instruction within a whole
 // module, used for trap offsets or call offsets. These offsets should refer to
@@ -1369,17 +1374,17 @@ enum class SymbolicAddress
     NearbyIntD,
     NearbyIntF,
     ExpD,
     LogD,
     PowD,
     ATan2D,
     HandleDebugTrap,
     HandleThrow,
-    ReportTrap,
+    OnTrap,
     OldReportTrap,
     ReportOutOfBounds,
     ReportUnalignedAccess,
     ReportInt64JSCall,
     CallImport_Void,
     CallImport_I32,
     CallImport_I64,
     CallImport_F64,
@@ -1522,19 +1527,29 @@ struct TlsData
 #endif
 
     // Pointer to the Instance that contains this TLS data.
     Instance* instance;
 
     // The containing JSContext.
     JSContext* cx;
 
-    // The native stack limit which is checked by prologues. Shortcut for
-    // cx->stackLimitForJitCode(JS::StackForUntrustedScript).
-    uintptr_t stackLimit;
+    // Usually equal to cx->stackLimitForJitCode(JS::StackForUntrustedScript),
+    // but can be racily set to trigger immediate trap as an opportunity to
+    // CheckForInterrupt without an additional branch.
+    Atomic<uintptr_t, mozilla::Relaxed> stackLimit;
+
+    // Set to 1 when wasm should call CheckForInterrupt.
+    Atomic<uint32_t, mozilla::Relaxed> interrupt;
+
+    // Methods to set, test and clear the above two fields. Both interrupt
+    // fields are Relaxed and so no consistency/ordering can be assumed.
+    void setInterrupt();
+    bool isInterrupted() const;
+    void resetInterrupt(JSContext* cx);
 
     // Pointer that should be freed (due to padding before the TlsData).
     void* allocatedBase;
 
     // When compiling with tiering, the jumpTable has one entry for each
     // baseline-compiled function.
     void** jumpTable;
 
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -2388,16 +2388,21 @@ JSReporter::CollectReports(WindowPaths* 
         KIND_OTHER, rtTotal,
         "The sum of all measurements under 'explicit/js-non-window/runtime/'.");
 
     // Report the numbers for memory used by tracelogger.
     REPORT_BYTES(NS_LITERAL_CSTRING("tracelogger"),
         KIND_OTHER, rtStats.runtime.tracelogger,
         "The memory used for the tracelogger, including the graph and events.");
 
+    // Report the numbers for memory used by wasm Runtime state.
+    REPORT_BYTES(NS_LITERAL_CSTRING("wasm-runtime"),
+        KIND_OTHER, rtStats.runtime.wasmRuntime,
+        "The memory used for wasm runtime bookkeeping.");
+
     // Report the numbers for memory outside of compartments.
 
     REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime/gc-heap/unused-chunks"),
         KIND_OTHER, rtStats.gcHeapUnusedChunks,
         "The same as 'explicit/js-non-window/gc-heap/unused-chunks'.");
 
     REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime/gc-heap/unused-arenas"),
         KIND_OTHER, rtStats.gcHeapUnusedArenas,