Bug 1505271 - Baldr: only create one wasm exception handler thread per process, and lazily (r=bbouvier)
☠☠ backed out by 111655db0b16 ☠ ☠
authorLuke Wagner <luke@mozilla.com>
Fri, 09 Nov 2018 09:44:33 -0600
changeset 445375 0abbc0f316d3d1b4c47338ff34e8a2e02e6479f0
parent 445374 dfdd63aae0c53fe1ce5b91bccb0262040cbf90b4
child 445376 6efc034e158167911c023da1231d90a287c4cb1b
push id109731
push userlwagner@mozilla.com
push dateFri, 09 Nov 2018 15:46:27 +0000
treeherdermozilla-inbound@0abbc0f316d3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbbouvier
bugs1505271
milestone65.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 1505271 - Baldr: only create one wasm exception handler thread per process, and lazily (r=bbouvier)
js/src/vm/ArrayBufferObject.cpp
js/src/vm/JSContext.cpp
js/src/vm/JSContext.h
js/src/vm/MutexIDs.h
js/src/vm/Runtime.cpp
js/src/vm/Stack.h
js/src/wasm/WasmBaselineCompile.cpp
js/src/wasm/WasmCompile.cpp
js/src/wasm/WasmJS.cpp
js/src/wasm/WasmModule.cpp
js/src/wasm/WasmSignalHandlers.cpp
js/src/wasm/WasmSignalHandlers.h
--- a/js/src/vm/ArrayBufferObject.cpp
+++ b/js/src/vm/ArrayBufferObject.cpp
@@ -881,17 +881,17 @@ CreateBuffer(JSContext* cx, uint32_t ini
     return true;
 }
 
 bool
 js::CreateWasmBuffer(JSContext* cx, const wasm::Limits& memory,
                      MutableHandleArrayBufferObjectMaybeShared buffer)
 {
     MOZ_ASSERT(memory.initial % wasm::PageSize == 0);
-    MOZ_RELEASE_ASSERT(wasm::HaveSignalHandlers());
+    MOZ_RELEASE_ASSERT(cx->wasmHaveSignalHandlers);
     MOZ_RELEASE_ASSERT((memory.initial / wasm::PageSize) <= wasm::MaxMemoryInitialPages);
 
     // Prevent applications specifying a large max (like UINT32_MAX) from
     // unintentially OOMing the browser on 32-bit: they just want "a lot of
     // memory". Maintain the invariant that initialSize <= maxSize.
 
     Maybe<uint32_t> maxSize = memory.maximum;
     if (sizeof(void*) == 4 && maxSize) {
@@ -927,17 +927,17 @@ js::CreateWasmBuffer(JSContext* cx, cons
 // Note this function can return false with or without an exception pending. The
 // asm.js caller checks cx->isExceptionPending before propagating failure.
 // Returning false without throwing means that asm.js linking will fail which
 // will recompile as non-asm.js.
 /* static */ bool
 ArrayBufferObject::prepareForAsmJS(JSContext* cx, Handle<ArrayBufferObject*> buffer)
 {
     MOZ_ASSERT(buffer->byteLength() % wasm::PageSize == 0);
-    MOZ_RELEASE_ASSERT(wasm::HaveSignalHandlers());
+    MOZ_RELEASE_ASSERT(cx->wasmHaveSignalHandlers);
 
     if (buffer->forInlineTypedObject()) {
         return false;
     }
 
     if (!buffer->isWasm() && buffer->isPreparedForAsmJS()) {
         return true;
     }
--- a/js/src/vm/JSContext.cpp
+++ b/js/src/vm/JSContext.cpp
@@ -119,19 +119,16 @@ JSContext::init(ContextKind kind)
 
 #ifdef JS_SIMULATOR
         simulator_ = jit::Simulator::Create();
         if (!simulator_) {
             return false;
         }
 #endif
 
-        if (!wasm::EnsureSignalHandlers(this)) {
-            return false;
-        }
     } else {
         atomsZoneFreeLists_ = js_new<gc::FreeLists>();
         if (!atomsZoneFreeLists_) {
             return false;
         }
     }
 
     // Set the ContextKind last, so that ProtectedData checks will allow us to
@@ -1338,16 +1335,18 @@ JSContext::JSContext(JSRuntime* runtime,
 #if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
     runningOOMTest(false),
 #endif
     enableAccessValidation(false),
     inUnsafeRegion(0),
     generationalDisabled(0),
     compactingDisabledCount(0),
     suppressProfilerSampling(false),
+    wasmTriedToInstallSignalHandlers(false),
+    wasmHaveSignalHandlers(false),
     tempLifoAlloc_((size_t)TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
     debuggerMutations(0),
     ionPcScriptCache(nullptr),
     throwing(false),
     overRecursed_(false),
     propagatingForcedReturn_(false),
     liveVolatileJitFrameIter_(nullptr),
     reportGranularity(JS_DEFAULT_JITREPORT_GRANULARITY),
--- a/js/src/vm/JSContext.h
+++ b/js/src/vm/JSContext.h
@@ -618,19 +618,20 @@ struct JSContext : public JS::RootingCon
     }
     void disableProfilerSampling() {
         suppressProfilerSampling = true;
     }
     void enableProfilerSampling() {
         suppressProfilerSampling = false;
     }
 
-#if defined(XP_DARWIN)
-    js::wasm::MachExceptionHandler wasmMachExceptionHandler;
-#endif
+    // Used by wasm::EnsureThreadSignalHandlers(cx) to install thread signal
+    // handlers once per JSContext/thread.
+    bool wasmTriedToInstallSignalHandlers;
+    bool wasmHaveSignalHandlers;
 
     /* Temporary arena pool used while compiling and decompiling. */
     static const size_t TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE = 4 * 1024;
   private:
     js::ThreadData<js::LifoAlloc> tempLifoAlloc_;
   public:
     js::LifoAlloc& tempLifoAlloc() { return tempLifoAlloc_.ref(); }
     const js::LifoAlloc& tempLifoAlloc() const { return tempLifoAlloc_.ref(); }
--- a/js/src/vm/MutexIDs.h
+++ b/js/src/vm/MutexIDs.h
@@ -50,16 +50,17 @@
   _(RuntimeScriptData,           500) \
   _(WasmFuncTypeIdSet,           500) \
   _(WasmCodeProfilingLabels,     500) \
   _(WasmCompileTaskState,        500) \
   _(WasmCodeBytesEnd,            500) \
   _(WasmStreamEnd,               500) \
   _(WasmStreamStatus,            500) \
   _(WasmRuntimeInstances,        500) \
+  _(WasmSignalInstallState,      500) \
                                       \
   _(IcuTimeZoneStateMutex,       600) \
   _(ThreadId,                    600) \
   _(WasmCodeSegmentMap,          600) \
   _(WasmDeferredValidation,      600) \
   _(TraceLoggerGraphState,       600) \
   _(VTuneLock,                   600)
 
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -43,16 +43,17 @@
 #include "util/Windows.h"
 #include "vm/DateTime.h"
 #include "vm/Debugger.h"
 #include "vm/JSAtom.h"
 #include "vm/JSObject.h"
 #include "vm/JSScript.h"
 #include "vm/TraceLogging.h"
 #include "vm/TraceLoggingGraph.h"
+#include "wasm/WasmSignalHandlers.h"
 
 #include "gc/GC-inl.h"
 #include "vm/JSContext-inl.h"
 
 using namespace js;
 
 using mozilla::Atomic;
 using mozilla::DebugOnly;
@@ -176,16 +177,19 @@ JSRuntime::JSRuntime(JSRuntime* parentRu
                                : js::StackFormat::SpiderMonkey),
     wasmInstances(mutexid::WasmRuntimeInstances),
     moduleResolveHook(),
     moduleMetadataHook(),
     moduleDynamicImportHook()
 {
     JS_COUNT_CTOR(JSRuntime);
     liveRuntimesCount++;
+
+    // See function comment for why we call this now, not in JS_Init().
+    wasm::EnsureEagerProcessSignalHandlers();
 }
 
 JSRuntime::~JSRuntime()
 {
     JS_COUNT_DTOR(JSRuntime);
     MOZ_ASSERT(!initialized_);
 
     DebugOnly<size_t> oldCount = liveRuntimesCount--;
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -24,18 +24,16 @@
 #include "js/RootingAPI.h"
 #include "js/TypeDecls.h"
 #include "js/UniquePtr.h"
 #include "vm/ArgumentsObject.h"
 #include "vm/JSFunction.h"
 #include "vm/JSScript.h"
 #include "vm/SavedFrame.h"
 #include "wasm/WasmFrameIter.h"
-#include "wasm/WasmSignalHandlers.h"
-#include "wasm/WasmTypes.h"
 
 namespace JS {
 namespace dbg {
 #ifdef JS_BROKEN_GCC_ATTRIBUTE_WARNING
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wattributes"
 #endif // JS_BROKEN_GCC_ATTRIBUTE_WARNING
 
--- a/js/src/wasm/WasmBaselineCompile.cpp
+++ b/js/src/wasm/WasmBaselineCompile.cpp
@@ -11086,20 +11086,16 @@ BaseCompiler::finish()
 }
 
 } // wasm
 } // js
 
 bool
 js::wasm::BaselineCanCompile()
 {
-    // On all platforms we require signals for Wasm.
-    // If we made it this far we must have signals.
-    MOZ_RELEASE_ASSERT(wasm::HaveSignalHandlers());
-
 #if defined(JS_CODEGEN_ARM)
     // Simplifying assumption: require SDIV and UDIV.
     //
     // I have no good data on ARM populations allowing me to say that
     // X% of devices in the market implement SDIV and UDIV.  However,
     // they are definitely implemented on the Cortex-A7 and Cortex-A15
     // and on all ARMv8 systems.
     if (!HasIDIV()) {
--- a/js/src/wasm/WasmCompile.cpp
+++ b/js/src/wasm/WasmCompile.cpp
@@ -527,18 +527,16 @@ DecodeCodeSection(const ModuleEnvironmen
 
     return mg.finishFuncDefs();
 }
 
 SharedModule
 wasm::CompileBuffer(const CompileArgs& args, const ShareableBytes& bytecode, UniqueChars* error,
                     UniqueCharsVector* warnings, UniqueLinkData* maybeLinkData)
 {
-    MOZ_RELEASE_ASSERT(wasm::HaveSignalHandlers());
-
     Decoder d(bytecode.bytes, 0, error, warnings);
 
     CompilerEnvironment compilerEnv(args);
     ModuleEnvironment env(args.gcTypesConfigured,
                           &compilerEnv,
                           args.sharedMemoryEnabled ? Shareable::True : Shareable::False);
     if (!DecodeModuleEnvironment(d, &env)) {
         return nullptr;
@@ -571,18 +569,16 @@ wasm::CompileBuffer(const CompileArgs& a
 
     return mg.finishModule(bytecode, nullptr, maybeLinkData);
 }
 
 void
 wasm::CompileTier2(const CompileArgs& args, const Bytes& bytecode, const Module& module,
                    Atomic<bool>* cancelled)
 {
-    MOZ_RELEASE_ASSERT(wasm::HaveSignalHandlers());
-
     UniqueChars error;
     Decoder d(bytecode, 0, &error);
 
     HasGcTypes gcTypesConfigured = HasGcTypes::False; // No optimized backend support yet
     OptimizedBackend optimizedBackend = args.forceCranelift
                                       ? OptimizedBackend::Cranelift
                                       : OptimizedBackend::Ion;
 
@@ -709,18 +705,16 @@ wasm::CompileStreaming(const CompileArgs
                        const Bytes& envBytes,
                        const Bytes& codeBytes,
                        const ExclusiveBytesPtr& codeBytesEnd,
                        const ExclusiveStreamEndData& exclusiveStreamEnd,
                        const Atomic<bool>& cancelled,
                        UniqueChars* error,
                        UniqueCharsVector* warnings)
 {
-    MOZ_ASSERT(wasm::HaveSignalHandlers());
-
     CompilerEnvironment compilerEnv(args);
     Maybe<ModuleEnvironment> env;
 
     {
         Decoder d(envBytes, 0, error, warnings);
 
         env.emplace(args.gcTypesConfigured,
                     &compilerEnv,
--- a/js/src/wasm/WasmJS.cpp
+++ b/js/src/wasm/WasmJS.cpp
@@ -66,17 +66,17 @@ wasm::HasCompilerSupport(JSContext* cx)
     if (!cx->jitSupportsFloatingPoint()) {
         return false;
     }
 
     if (!cx->jitSupportsUnalignedAccesses()) {
         return false;
     }
 
-    if (!wasm::HaveSignalHandlers()) {
+    if (!wasm::EnsureFullSignalHandlers(cx)) {
         return false;
     }
 
 #if !MOZ_LITTLE_ENDIAN
     return false;
 #endif
 
 #ifdef ENABLE_WASM_THREAD_OPS
--- a/js/src/wasm/WasmModule.cpp
+++ b/js/src/wasm/WasmModule.cpp
@@ -1248,16 +1248,18 @@ Module::instantiate(JSContext* cx,
                     Handle<FunctionVector> funcImports,
                     HandleWasmTableObject tableImport,
                     HandleWasmMemoryObject memoryImport,
                     HandleValVector globalImportValues,
                     WasmGlobalObjectVector& globalObjs,
                     HandleObject instanceProto,
                     MutableHandleWasmInstanceObject instance) const
 {
+    MOZ_RELEASE_ASSERT(cx->wasmHaveSignalHandlers);
+
     if (!instantiateFunctions(cx, funcImports)) {
         return false;
     }
 
     RootedWasmMemoryObject memory(cx, memoryImport);
     if (!instantiateMemory(cx, &memory)) {
         return false;
     }
--- a/js/src/wasm/WasmSignalHandlers.cpp
+++ b/js/src/wasm/WasmSignalHandlers.cpp
@@ -17,37 +17,40 @@
  */
 
 #include "wasm/WasmSignalHandlers.h"
 
 #include "mozilla/DebugOnly.h"
 #include "mozilla/ScopeExit.h"
 #include "mozilla/ThreadLocal.h"
 
+#include "threading/Thread.h"
 #include "vm/Runtime.h"
 #include "wasm/WasmInstance.h"
 
+#if defined(XP_WIN)
+# include "util/Windows.h"
+#elif defined(XP_DARWIN)
+# include <mach/exc.h>
+# include <mach/mach.h>
+#else
+# include <signal.h>
+#endif
+
 using namespace js;
 using namespace js::wasm;
 
 using mozilla::DebugOnly;
 
 // =============================================================================
 // This following pile of macros and includes defines the ToRegisterState() and
 // the ContextTo{PC,FP,SP,LR}() functions from the (highly) platform-specific
 // CONTEXT struct which is provided to the signal handler.
 // =============================================================================
 
-#if defined(XP_WIN)
-# include "util/Windows.h"
-#else
-# include <signal.h>
-# include <sys/mman.h>
-#endif
-
 #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
 # include <sys/ucontext.h> // for ucontext_t, mcontext_t
 #endif
 
 #if defined(__x86_64__)
 # if defined(__DragonFly__)
 #  include <machine/npx.h> // for union savefpu
 # elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || \
@@ -473,19 +476,18 @@ HandleTrap(CONTEXT* context, JSContext* 
     // will call finishWasmTrap().
     jit::JitActivation* activation = cx->activation()->asJit();
     activation->startWasmTrap(trap, bytecode.offset(), ToRegisterState(context));
     SetContextPC(context, segment.trapCode());
     return true;
 }
 
 // =============================================================================
-// The following platform specific signal/exception handlers are installed by
-// wasm::EnsureSignalHandlers() and funnel all potential wasm traps into
-// HandleTrap() above.
+// The following platform-specific handlers funnel all signals/exceptions into
+// the shared HandleTrap() above.
 // =============================================================================
 
 #if defined(XP_WIN)
 
 static LONG WINAPI
 WasmTrapHandler(LPEXCEPTION_POINTERS exception)
 {
     if (sAlreadyHandlingTrap.get()) {
@@ -503,17 +505,19 @@ WasmTrapHandler(LPEXCEPTION_POINTERS exc
     if (!HandleTrap(exception->ContextRecord, TlsContext.get())) {
         return EXCEPTION_CONTINUE_SEARCH;
     }
 
     return EXCEPTION_CONTINUE_EXECUTION;
 }
 
 #elif defined(XP_DARWIN)
-# include <mach/exc.h>
+// On OSX we are forced to use the lower-level Mach exception mechanism instead
+// of Unix signals because breakpad uses Mach exceptions and would otherwise
+// report a crash before wasm gets a chance to handle the exception.
 
 // 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;
@@ -530,17 +534,17 @@ typedef struct {
 // The full Mach message also includes a trailer.
 struct ExceptionRequest
 {
     Request__mach_exception_raise_t body;
     mach_msg_trailer_t trailer;
 };
 
 static bool
-HandleMachException(JSContext* cx, const ExceptionRequest& request)
+HandleMachException(const ExceptionRequest& request)
 {
     // Get the port of the JSContext's thread from the message.
     mach_port_t cxThread = request.body.thread.name;
 
     // Read out the JSRuntime thread's register state.
     CONTEXT context;
 # if defined(__x86_64__)
     unsigned int thread_state_count = x86_THREAD_STATE64_COUNT;
@@ -576,17 +580,17 @@ HandleMachException(JSContext* cx, const
         request.body.exception != EXC_BAD_INSTRUCTION)
     {
         return false;
     }
 
     {
         AutoNoteSingleThreadedRegion anstr;
         AutoHandlingTrap aht;
-        if (!HandleTrap(&context, cx)) {
+        if (!HandleTrap(&context)) {
             return false;
         }
     }
 
     // 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;
@@ -594,58 +598,48 @@ HandleMachException(JSContext* cx, const
     kret = thread_set_state(cxThread, thread_state, (thread_state_t)&context.thread, thread_state_count);
     if (kret != KERN_SUCCESS) {
         return false;
     }
 
     return true;
 }
 
-// Taken from mach_exc in /usr/include/mach/mach_exc.defs.
-static const mach_msg_id_t sExceptionId = 2405;
-
-// The choice of id here is arbitrary, the only constraint is that sQuitId != sExceptionId.
-static const mach_msg_id_t sQuitId = 42;
+static mach_port_t sMachDebugPort = MACH_PORT_NULL;
 
 static void
-MachExceptionHandlerThread(JSContext* cx)
+MachExceptionHandlerThread()
 {
-    mach_port_t port = cx->wasmMachExceptionHandler.port();
-    kern_return_t kret;
+    // Taken from mach_exc in /usr/include/mach/mach_exc.defs.
+    static const unsigned EXCEPTION_MSG_ID = 2405;
 
-    while(true) {
+    while (true) {
         ExceptionRequest request;
-        kret = mach_msg(&request.body.Head, MACH_RCV_MSG, 0, sizeof(request),
-                        port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
+        kern_return_t kret = mach_msg(&request.body.Head, MACH_RCV_MSG, 0, sizeof(request),
+                                      sMachDebugPort, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
 
         // If we fail even receiving the message, we can't even send a reply!
         // Rather than hanging the faulting thread (hanging the browser), crash.
         if (kret != KERN_SUCCESS) {
             fprintf(stderr, "MachExceptionHandlerThread: mach_msg failed with %d\n", (int)kret);
             MOZ_CRASH();
         }
 
-        // There are only two messages we should be receiving: an exception
-        // message that occurs when the runtime's thread faults and the quit
-        // message sent when the runtime is shutting down.
-        if (request.body.Head.msgh_id == sQuitId) {
-            break;
-        }
-        if (request.body.Head.msgh_id != sExceptionId) {
+        if (request.body.Head.msgh_id != EXCEPTION_MSG_ID) {
             fprintf(stderr, "Unexpected msg header id %d\n", (int)request.body.Head.msgh_bits);
             MOZ_CRASH();
         }
 
         // Some thread just commited an EXC_BAD_ACCESS and has been suspended by
         // the kernel. The kernel is waiting for us to reply with instructions.
         // Our default is the "not handled" reply (by setting the RetCode field
         // of the reply to KERN_FAILURE) which tells the kernel to continue
         // searching at the process and system level. If this is an asm.js
         // expected exception, we handle it and return KERN_SUCCESS.
-        bool handled = HandleMachException(cx, request);
+        bool handled = HandleMachException(request);
         kern_return_t replyCode = handled ? KERN_SUCCESS : KERN_FAILURE;
 
         // This magic incantation to send a reply back to the kernel was derived
         // from the exc_server generated by 'mig -v /usr/include/mach/mach_exc.defs'.
         __Reply__exception_raise_t reply;
         reply.Head.msgh_bits = MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(request.body.Head.msgh_bits), 0);
         reply.Head.msgh_size = sizeof(reply);
         reply.Head.msgh_remote_port = request.body.Head.msgh_remote_port;
@@ -653,111 +647,16 @@ MachExceptionHandlerThread(JSContext* cx
         reply.Head.msgh_id = request.body.Head.msgh_id + 100;
         reply.NDR = NDR_record;
         reply.RetCode = replyCode;
         mach_msg(&reply.Head, MACH_SEND_MSG, sizeof(reply), 0, MACH_PORT_NULL,
                  MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
     }
 }
 
-MachExceptionHandler::MachExceptionHandler()
-  : installed_(false),
-    thread_(),
-    port_(MACH_PORT_NULL)
-{}
-
-void
-MachExceptionHandler::uninstall()
-{
-    if (installed_) {
-        thread_port_t thread = mach_thread_self();
-        kern_return_t kret = thread_set_exception_ports(thread,
-                                                        EXC_MASK_BAD_ACCESS | EXC_MASK_BAD_INSTRUCTION,
-                                                        MACH_PORT_NULL,
-                                                        EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES,
-                                                        THREAD_STATE_NONE);
-        mach_port_deallocate(mach_task_self(), thread);
-        if (kret != KERN_SUCCESS) {
-            MOZ_CRASH();
-        }
-        installed_ = false;
-    }
-    if (thread_.joinable()) {
-        // Break the handler thread out of the mach_msg loop.
-        mach_msg_header_t msg;
-        msg.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
-        msg.msgh_size = sizeof(msg);
-        msg.msgh_remote_port = port_;
-        msg.msgh_local_port = MACH_PORT_NULL;
-        msg.msgh_reserved = 0;
-        msg.msgh_id = sQuitId;
-        kern_return_t kret = mach_msg(&msg, MACH_SEND_MSG, sizeof(msg), 0, MACH_PORT_NULL,
-                                      MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
-        if (kret != KERN_SUCCESS) {
-            fprintf(stderr, "MachExceptionHandler: failed to send quit message: %d\n", (int)kret);
-            MOZ_CRASH();
-        }
-
-        // Wait for the handler thread to complete before deallocating the port.
-        thread_.join();
-    }
-    if (port_ != MACH_PORT_NULL) {
-        DebugOnly<kern_return_t> kret = mach_port_destroy(mach_task_self(), port_);
-        MOZ_ASSERT(kret == KERN_SUCCESS);
-        port_ = MACH_PORT_NULL;
-    }
-}
-
-bool
-MachExceptionHandler::install(JSContext* cx)
-{
-    MOZ_ASSERT(!installed());
-    kern_return_t kret;
-    mach_port_t thread;
-
-    auto onFailure = mozilla::MakeScopeExit([&] {
-        uninstall();
-    });
-
-    // Get a port which can send and receive data.
-    kret = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port_);
-    if (kret != KERN_SUCCESS) {
-        return false;
-    }
-    kret = mach_port_insert_right(mach_task_self(), port_, port_, MACH_MSG_TYPE_MAKE_SEND);
-    if (kret != KERN_SUCCESS) {
-        return false;
-    }
-
-    // Create a thread to block on reading port_.
-    if (!thread_.init(MachExceptionHandlerThread, cx)) {
-        return false;
-    }
-
-    // Direct exceptions on this thread to port_ (and thus our handler thread).
-    // Note: we are totally clobbering any existing *thread* exception ports and
-    // not even attempting to forward. Breakpad and gdb both use the *process*
-    // exception ports which are only called if the thread doesn't handle the
-    // exception, so we should be fine.
-    thread = mach_thread_self();
-    kret = thread_set_exception_ports(thread,
-                                      EXC_MASK_BAD_ACCESS | EXC_MASK_BAD_INSTRUCTION,
-                                      port_,
-                                      EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES,
-                                      THREAD_STATE_NONE);
-    mach_port_deallocate(mach_task_self(), thread);
-    if (kret != KERN_SUCCESS) {
-        return false;
-    }
-
-    installed_ = true;
-    onFailure.release();
-    return true;
-}
-
 #else  // If not Windows or Mac, assume Unix
 
 #ifdef __mips__
 static const uint32_t kWasmTrapSignal = SIGFPE;
 #else
 static const uint32_t kWasmTrapSignal = SIGILL;
 #endif
 
@@ -805,74 +704,83 @@ WasmTrapHandler(int signum, siginfo_t* i
     }
 }
 # endif // XP_WIN || XP_DARWIN || assume unix
 
 #if defined(ANDROID) && defined(MOZ_LINKER)
 extern "C" MFBT_API bool IsSignalHandlingBroken();
 #endif
 
-static bool sTriedInstallSignalHandlers = false;
-static bool sHaveSignalHandlers = false;
-
-static bool
-ProcessHasSignalHandlers()
+struct InstallState
 {
-    // We assume that there are no races creating the first JSRuntime of the process.
-    if (sTriedInstallSignalHandlers) {
-        return sHaveSignalHandlers;
+    bool tried;
+    bool success;
+    InstallState() : tried(false), success(false) {}
+};
+
+static ExclusiveData<InstallState> sEagerInstallState(mutexid::WasmSignalInstallState);
+
+void
+wasm::EnsureEagerProcessSignalHandlers()
+{
+    auto eagerInstallState = sEagerInstallState.lock();
+    if (eagerInstallState->tried) {
+        return;
     }
-    sTriedInstallSignalHandlers = true;
+
+    eagerInstallState->tried = true;
+    MOZ_RELEASE_ASSERT(eagerInstallState->success == false);
 
 #if defined (JS_CODEGEN_NONE)
     // If there is no JIT, then there should be no Wasm signal handlers.
-    return false;
+    return;
 #endif
 
     // Signal handlers are currently disabled when recording or replaying.
     if (mozilla::recordreplay::IsRecordingOrReplaying()) {
-        return false;
+        return;
     }
 
 #if defined(ANDROID) && defined(MOZ_LINKER)
     // Signal handling is broken on some android systems.
     if (IsSignalHandlingBroken()) {
-        return false;
+        return;
     }
 #endif
 
     // Initalize ThreadLocal flag used by WasmTrapHandler
     sAlreadyHandlingTrap.infallibleInit();
 
     // Install a SIGSEGV handler to handle safely-out-of-bounds asm.js heap
     // access and/or unaligned accesses.
 #if defined(XP_WIN)
 
 # if defined(_M_ARM64)
     // The AArch64 Windows build is not ready for this yet.
-    return false;
+    return;
 # endif
 
 # if defined(MOZ_ASAN)
     // Under ASan we need to let the ASan runtime's ShadowExceptionHandler stay
     // in the first handler position. This requires some coordination with
     // MemoryProtectionExceptionHandler::isDisabled().
     const bool firstHandler = false;
 # else
     // Otherwise, WasmTrapHandler needs to go first, so that we can recover
     // from wasm faults and continue execution without triggering handlers
     // such as MemoryProtectionExceptionHandler that assume we are crashing.
     const bool firstHandler = true;
 # endif
     if (!AddVectoredExceptionHandler(firstHandler, WasmTrapHandler)) {
-        return false;
+        // Windows has all sorts of random security knobs for disabling things
+        // so make this a dynamic failure that disables wasm, not a MOZ_CRASH().
+        return;
     }
 #elif defined(XP_DARWIN)
-    // OSX handles seg faults via the Mach exception handler above, so don't
-    // install WasmTrapHandler.
+    // All the Mach setup in EnsureLazyProcessSignalHandlers.
 #else
     // SA_NODEFER allows us to reenter the signal handler if we crash while
     // handling the signal, and fall through to the Breakpad handler by testing
     // handlingSegFault.
 
     // Allow handling OOB with signals on all architectures
     struct sigaction faultHandler;
     faultHandler.sa_flags = SA_SIGINFO | SA_NODEFER | SA_ONSTACK;
@@ -899,46 +807,107 @@ ProcessHasSignalHandlers()
     wasmTrapHandler.sa_flags = SA_SIGINFO | SA_NODEFER | SA_ONSTACK;
     wasmTrapHandler.sa_sigaction = WasmTrapHandler;
     sigemptyset(&wasmTrapHandler.sa_mask);
     if (sigaction(kWasmTrapSignal, &wasmTrapHandler, &sPrevWasmTrapHandler)) {
         MOZ_CRASH("unable to install wasm trap handler");
     }
 #endif
 
-    sHaveSignalHandlers = true;
+    eagerInstallState->success = true;
+}
+
+static ExclusiveData<InstallState> sLazyInstallState(mutexid::WasmSignalInstallState);
+
+static bool
+EnsureLazyProcessSignalHandlers()
+{
+    auto lazyInstallState = sLazyInstallState.lock();
+    if (lazyInstallState->tried) {
+        return lazyInstallState->success;
+    }
+
+    lazyInstallState->tried = true;
+    MOZ_RELEASE_ASSERT(lazyInstallState->success == false);
+
+#ifdef XP_DARWIN
+    // Create the port that all JSContext threads will redirect their traps to.
+    kern_return_t kret;
+    kret = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &sMachDebugPort);
+    if (kret != KERN_SUCCESS) {
+        return false;
+    }
+    kret = mach_port_insert_right(mach_task_self(), sMachDebugPort, sMachDebugPort,
+                                  MACH_MSG_TYPE_MAKE_SEND);
+    if (kret != KERN_SUCCESS) {
+        return false;
+    }
+
+    // Create the thread that will wait on and service sMachDebugPort.
+    // It's not useful to destroy this thread on process shutdown so
+    // immediately detach on successful start.
+    Thread handlerThread;
+    if (!handlerThread.init(MachExceptionHandlerThread)) {
+        return false;
+    }
+    handlerThread.detach();
+#endif
+
+    lazyInstallState->success = true;
     return true;
 }
 
 bool
-wasm::EnsureSignalHandlers(JSContext* cx)
+wasm::EnsureFullSignalHandlers(JSContext* cx)
 {
-    // Nothing to do if the platform doesn't support it.
-    if (!ProcessHasSignalHandlers()) {
-        return true;
+    if (cx->wasmTriedToInstallSignalHandlers) {
+        return cx->wasmHaveSignalHandlers;
+    }
+
+    cx->wasmTriedToInstallSignalHandlers = true;
+    MOZ_RELEASE_ASSERT(!cx->wasmHaveSignalHandlers);
+
+    {
+        auto eagerInstallState = sEagerInstallState.lock();
+        MOZ_RELEASE_ASSERT(eagerInstallState->tried);
+        if (!eagerInstallState->success) {
+            return false;
+        }
     }
 
-#if defined(XP_DARWIN)
-    // On OSX, each JSContext which runs wasm gets its own handler thread.
-    if (!cx->wasmMachExceptionHandler.installed() && !cx->wasmMachExceptionHandler.install(cx)) {
+    if (!EnsureLazyProcessSignalHandlers()) {
+        return false;
+    }
+
+#ifdef XP_DARWIN
+    // In addition to the process-wide signal handler setup, OSX needs each
+    // thread configured to send its exceptions to sMachDebugPort. While there
+    // are also task-level (i.e. process-level) exception ports, those are
+    // "claimed" by breakpad and chaining Mach exceptions is dark magic that we
+    // avoid by instead intercepting exceptions at the thread level before they
+    // propagate to the process-level. This works because there are no other
+    // uses of thread-level exception ports.
+    MOZ_RELEASE_ASSERT(sMachDebugPort != MACH_PORT_NULL);
+    thread_port_t thisThread = mach_thread_self();
+    kern_return_t kret = thread_set_exception_ports(thisThread,
+                                                    EXC_MASK_BAD_ACCESS | EXC_MASK_BAD_INSTRUCTION,
+                                                    sMachDebugPort,
+                                                    EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES,
+                                                    THREAD_STATE_NONE);
+    mach_port_deallocate(mach_task_self(), thisThread);
+    if (kret != KERN_SUCCESS) {
         return false;
     }
 #endif
 
+    cx->wasmHaveSignalHandlers = true;
     return true;
 }
 
 bool
-wasm::HaveSignalHandlers()
-{
-    MOZ_ASSERT(sTriedInstallSignalHandlers);
-    return sHaveSignalHandlers;
-}
-
-bool
 wasm::MemoryAccessTraps(const RegisterState& regs, uint8_t* addr, uint32_t numBytes, uint8_t** newPC)
 {
     const wasm::CodeSegment* codeSegment = wasm::LookupCodeSegment(regs.pc);
     if (!codeSegment || !codeSegment->isModule()) {
         return false;
     }
 
     const wasm::ModuleSegment& segment = *codeSegment->asModule();
--- a/js/src/wasm/WasmSignalHandlers.h
+++ b/js/src/wasm/WasmSignalHandlers.h
@@ -16,71 +16,53 @@
  * limitations under the License.
  */
 
 #ifndef wasm_signal_handlers_h
 #define wasm_signal_handlers_h
 
 #include "mozilla/Attributes.h"
 
-#if defined(XP_DARWIN)
-# include <mach/mach.h>
-#endif
-
 #include "js/ProfilingFrameIterator.h"
-#include "threading/Thread.h"
 #include "wasm/WasmProcess.h"
 
 namespace js {
 namespace wasm {
 
 typedef JS::ProfilingFrameIterator::RegisterState RegisterState;
 
-// Ensure the given JSRuntime is set up to use signals. Failure to enable signal
-// handlers indicates some catastrophic failure and creation of the runtime must
-// fail.
-MOZ_MUST_USE bool
-EnsureSignalHandlers(JSContext* cx);
+// This function performs the low-overhead signal handler initialization that we
+// want to do eagerly to ensure a more-deterministic global process state. This
+// is especially relevant for signal handlers since handler ordering depends on
+// installation order: the wasm signal handler must run *before* the other crash
+// handlers (ds/MemoryProtectionExceptionHandler.h and breakpad) and since POSIX
+// signal handlers work LIFO, this function needs to be called at the end of the
+// startup process, after the other two handlers have been installed. Currently,
+// this is achieved by having JSRuntime() call this function. There can be
+// multiple JSRuntimes per process so this function can thus be called multiple
+// times, having no effect after the first call.
+void
+EnsureEagerProcessSignalHandlers();
 
-// Return whether signals can be used in this process for asm.js/wasm
-// out-of-bounds.
+// Assuming EnsureEagerProcessSignalHandlers() has already been called,
+// this function performs the full installation of signal handlers which must
+// be performed per-thread/JSContext. This operation may incur some overhead and
+// so should be done only when needed to use wasm. Currently, this is done in
+// wasm::HasCompilerSupport() which is called when deciding whether to expose the
+// 'WebAssembly' object on the global object.
 bool
-HaveSignalHandlers();
+EnsureFullSignalHandlers(JSContext* cx);
 
 // Return whether, with the given simulator register state, a memory access to
 // 'addr' of size 'numBytes' needs to trap and, if so, where the simulator
 // should redirect pc to.
 bool
 MemoryAccessTraps(const RegisterState& regs, uint8_t* addr, uint32_t numBytes, uint8_t** newPC);
 
 // Return whether, with the given simulator register state, an illegal
 // instruction fault is expected and, if so, the value of the next PC.
 bool
 HandleIllegalInstruction(const RegisterState& regs, uint8_t** newPC);
 
-#if defined(XP_DARWIN)
-// On OSX we are forced to use the lower-level Mach exception mechanism instead
-// of Unix signals. Mach exceptions are not handled on the victim's stack but
-// rather require an extra thread. For simplicity, we create one such thread
-// per JSContext (upon the first use of wasm in the JSContext). This thread
-// and related resources are owned by AsmJSMachExceptionHandler which is owned
-// by JSContext.
-class MachExceptionHandler
-{
-    bool installed_;
-    js::Thread thread_;
-    mach_port_t port_;
-
-    void uninstall();
-
-  public:
-    MachExceptionHandler();
-    ~MachExceptionHandler() { uninstall(); }
-    mach_port_t port() const { return port_; }
-    bool installed() const { return installed_; }
-    bool install(JSContext* cx);
-};
-#endif
-
 } // namespace wasm
 } // namespace js
 
 #endif // wasm_signal_handlers_h