Bug 1334933 - Allocate executable pages from a pre-reserved range. r=luke, a=jcristau
authorJan de Mooij <jdemooij@mozilla.com>
Sat, 04 Feb 2017 11:03:58 +0100
changeset 375993 cc9701b48cd9006d9730bcbdd6715cd4c7fdb604
parent 375992 1b251eb90b7df35f25ae5ad58140a44f04e44c76
child 375994 70f5d60669540371ce4c967921ffc1ea0cb50424
push id6996
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 20:48:21 +0000
treeherdermozilla-beta@d89512dab048 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke, jcristau
bugs1334933, 1337561
milestone53.0a2
Bug 1334933 - Allocate executable pages from a pre-reserved range. r=luke, a=jcristau * * * Bug 1334933 - Randomize mmap address for executable code on posix platforms. r=luke * * * Bug 1334933 part 4 - Fix mmap randomization on Linux32 to be within a fixed range to avoid conflicts. r=luke * * * Bug 1337561 - Fix executable page allocator to avoid fragmenting the JIT code space. r=luke
js/src/gc/GCRuntime.h
js/src/irregexp/RegExpEngine.cpp
js/src/jit/ExecutableAllocator.cpp
js/src/jit/ExecutableAllocator.h
js/src/jit/ExecutableAllocatorPosix.cpp
js/src/jit/ExecutableAllocatorWin.cpp
js/src/jit/ProcessExecutableMemory.cpp
js/src/jit/ProcessExecutableMemory.h
js/src/jsgc.cpp
js/src/moz.build
js/src/vm/Initialization.cpp
js/src/vm/MutexIDs.h
js/src/wasm/WasmCode.cpp
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -946,17 +946,17 @@ class GCRuntime
     bool shouldRepeatForDeadZone(JS::gcreason::Reason reason);
     void incrementalCollectSlice(SliceBudget& budget, JS::gcreason::Reason reason,
                                  AutoLockForExclusiveAccess& lock);
 
     void pushZealSelectedObjects();
     void purgeRuntime(AutoLockForExclusiveAccess& lock);
     MOZ_MUST_USE bool beginMarkPhase(JS::gcreason::Reason reason, AutoLockForExclusiveAccess& lock);
     bool shouldPreserveJITCode(JSCompartment* comp, int64_t currentTime,
-                               JS::gcreason::Reason reason);
+                               JS::gcreason::Reason reason, bool canAllocateMoreCode);
     void traceRuntimeForMajorGC(JSTracer* trc, AutoLockForExclusiveAccess& lock);
     void traceRuntimeAtoms(JSTracer* trc, AutoLockForExclusiveAccess& lock);
     void traceRuntimeCommon(JSTracer* trc, TraceOrMarkRuntime traceOrMark,
                             AutoLockForExclusiveAccess& lock);
     void bufferGrayRoots();
     void maybeDoCycleCollection();
     void markCompartments();
     IncrementalProgress drainMarkStack(SliceBudget& sliceBudget, gcstats::Phase phase);
--- a/js/src/irregexp/RegExpEngine.cpp
+++ b/js/src/irregexp/RegExpEngine.cpp
@@ -1775,17 +1775,18 @@ irregexp::CompilePattern(JSContext* cx, 
 
     Maybe<jit::JitContext> ctx;
     Maybe<NativeRegExpMacroAssembler> native_assembler;
     Maybe<InterpretedRegExpMacroAssembler> interpreted_assembler;
 
     RegExpMacroAssembler* assembler;
     if (IsNativeRegExpEnabled(cx) &&
         !force_bytecode &&
-        jit::CanLikelyAllocateMoreExecutableMemory())
+        jit::CanLikelyAllocateMoreExecutableMemory() &&
+        shared->getSource()->length() < 32 * 1024)
     {
         NativeRegExpMacroAssembler::Mode mode =
             is_ascii ? NativeRegExpMacroAssembler::ASCII
                      : NativeRegExpMacroAssembler::CHAR16;
 
         ctx.emplace(cx, (jit::TempAllocator*) nullptr);
         native_assembler.emplace(&alloc, shared, cx->runtime(), mode, (data->capture_count + 1) * 2);
         assembler = native_assembler.ptr();
--- a/js/src/jit/ExecutableAllocator.cpp
+++ b/js/src/jit/ExecutableAllocator.cpp
@@ -22,30 +22,21 @@
  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
 #include "jit/ExecutableAllocator.h"
 
-#include "mozilla/Atomics.h"
-
 #include "jit/JitCompartment.h"
 #include "js/MemoryMetrics.h"
 
-#ifdef __APPLE__
-#include <TargetConditionals.h>
-#endif
-
 using namespace js::jit;
 
-size_t ExecutableAllocator::pageSize = 0;
-size_t ExecutableAllocator::largeAllocSize = 0;
-
 ExecutablePool::~ExecutablePool()
 {
     MOZ_ASSERT(m_ionCodeBytes == 0);
     MOZ_ASSERT(m_baselineCodeBytes == 0);
     MOZ_ASSERT(m_regexpCodeBytes == 0);
     MOZ_ASSERT(m_otherCodeBytes == 0);
 
     MOZ_ASSERT(!isMarked());
@@ -155,21 +146,21 @@ ExecutableAllocator::poolForSize(size_t 
             minPool = pool;
     }
     if (minPool) {
         minPool->addRef();
         return minPool;
     }
 
     // If the request is large, we just provide a unshared allocator
-    if (n > largeAllocSize)
+    if (n > ExecutableCodePageSize)
         return createPool(n);
 
     // Create a new allocator
-    ExecutablePool* pool = createPool(largeAllocSize);
+    ExecutablePool* pool = createPool(ExecutableCodePageSize);
     if (!pool)
         return nullptr;
     // At this point, local |pool| is the owner.
 
     if (m_smallPools.length() < maxSmallPools) {
         // We haven't hit the maximum number of live pools; add the new pool.
         // If append() OOMs, we just return an unshared allocator.
         if (m_smallPools.append(pool))
@@ -215,17 +206,17 @@ ExecutableAllocator::roundUpAllocationSi
     return size;
 }
 
 ExecutablePool*
 ExecutableAllocator::createPool(size_t n)
 {
     MOZ_ASSERT(rt_->jitRuntime()->preventBackedgePatching());
 
-    size_t allocSize = roundUpAllocationSize(n, pageSize);
+    size_t allocSize = roundUpAllocationSize(n, ExecutableCodePageSize);
     if (allocSize == OVERSIZE_ALLOCATION)
         return nullptr;
 
     if (!m_pools.initialized() && !m_pools.init())
         return nullptr;
 
     ExecutablePool::Allocation a = systemAlloc(allocSize);
     if (!a.pages)
@@ -295,38 +286,16 @@ ExecutableAllocator::purge()
     // Don't race with reprotectAll called from the signal handler.
     JitRuntime::AutoPreventBackedgePatching apbp(rt_);
 
     for (size_t i = 0; i < m_smallPools.length(); i++)
         m_smallPools[i]->release();
     m_smallPools.clear();
 }
 
-/* static */ void
-ExecutableAllocator::initStatic()
-{
-    if (!pageSize) {
-        pageSize = determinePageSize();
-        // On Windows, VirtualAlloc effectively allocates in 64K chunks.
-        // (Technically, it allocates in page chunks, but the starting
-        // address is always a multiple of 64K, so each allocation uses up
-        // 64K of address space.)  So a size less than that would be
-        // pointless.  But it turns out that 64KB is a reasonable size for
-        // all platforms.  (This assumes 4KB pages.) On 64-bit windows,
-        // AllocateExecutableMemory prepends an extra page for structured
-        // exception handling data (see comments in function) onto whatever
-        // is passed in, so subtract one page here.
-#if defined(XP_WIN) && (defined(_M_X64) || defined(__x86_64__))
-        largeAllocSize = pageSize * 15;
-#else
-        largeAllocSize = pageSize * 16;
-#endif
-    }
-}
-
 void
 ExecutableAllocator::addSizeOfCode(JS::CodeSizes* sizes) const
 {
     if (m_pools.initialized()) {
         for (ExecPoolHashSet::Range r = m_pools.all(); !r.empty(); r.popFront()) {
             ExecutablePool* pool = r.front();
             sizes->ion      += pool->m_ionCodeBytes;
             sizes->baseline += pool->m_baselineCodeBytes;
@@ -338,36 +307,32 @@ ExecutableAllocator::addSizeOfCode(JS::C
                                                        - pool->m_otherCodeBytes;
         }
     }
 }
 
 void
 ExecutableAllocator::reprotectAll(ProtectionSetting protection)
 {
-#ifdef NON_WRITABLE_JIT_CODE
     if (!m_pools.initialized())
         return;
 
     for (ExecPoolHashSet::Range r = m_pools.all(); !r.empty(); r.popFront())
         reprotectPool(rt_, r.front(), protection);
-#endif
 }
 
 /* static */ void
 ExecutableAllocator::reprotectPool(JSRuntime* rt, ExecutablePool* pool, ProtectionSetting protection)
 {
-#ifdef NON_WRITABLE_JIT_CODE
     // Don't race with reprotectAll called from the signal handler.
     MOZ_ASSERT(rt->jitRuntime()->preventBackedgePatching() || rt->handlingJitInterrupt());
 
     char* start = pool->m_allocation.pages;
-    if (!reprotectRegion(start, pool->m_freePtr - start, protection))
+    if (!ReprotectRegion(start, pool->m_freePtr - start, protection))
         MOZ_CRASH();
-#endif
 }
 
 /* static */ void
 ExecutableAllocator::poisonCode(JSRuntime* rt, JitPoisonRangeVector& ranges)
 {
     MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt));
 
     // Don't race with reprotectAll called from the signal handler.
@@ -387,79 +352,39 @@ ExecutableAllocator::poisonCode(JSRuntim
             continue;
         }
 
         MOZ_ASSERT(pool->m_refCount > 1);
 
         // Use the pool's mark bit to indicate we made the pool writable.
         // This avoids reprotecting a pool multiple times.
         if (!pool->isMarked()) {
-            reprotectPool(rt, pool, Writable);
+            reprotectPool(rt, pool, ProtectionSetting::Writable);
             pool->mark();
         }
 
         memset(ranges[i].start, JS_SWEPT_CODE_PATTERN, ranges[i].size);
     }
 
     // Make the pools executable again and drop references.
     for (size_t i = 0; i < ranges.length(); i++) {
         ExecutablePool* pool = ranges[i].pool;
         if (pool->isMarked()) {
-            reprotectPool(rt, pool, Executable);
+            reprotectPool(rt, pool, ProtectionSetting::Executable);
             pool->unmark();
         }
         pool->release();
     }
 }
 
-// Limit on the number of bytes of executable memory to prevent JIT spraying
-// attacks.
-#if JS_BITS_PER_WORD == 32
-static const size_t MaxCodeBytesPerProcess = 160 * 1024 * 1024;
-#else
-static const size_t MaxCodeBytesPerProcess = 640 * 1024 * 1024;
-#endif
-
-static mozilla::Atomic<size_t> allocatedExecutableBytes(0);
-
-bool
-js::jit::AddAllocatedExecutableBytes(size_t bytes)
+ExecutablePool::Allocation
+ExecutableAllocator::systemAlloc(size_t n)
 {
-    MOZ_ASSERT(allocatedExecutableBytes <= MaxCodeBytesPerProcess);
-
-    // Multiple threads can call this concurrently. We use compareExchange to
-    // ensure allocatedExecutableBytes is always <= MaxCodeBytesPerProcess.
-    while (true) {
-        size_t bytesOld = allocatedExecutableBytes;
-        size_t bytesNew = bytesOld + bytes;
-
-        if (bytesNew > MaxCodeBytesPerProcess)
-            return false;
-
-        if (allocatedExecutableBytes.compareExchange(bytesOld, bytesNew))
-            return true;
-    }
-
-    MOZ_CRASH();
+    void* allocation = AllocateExecutableMemory(n, ProtectionSetting::Executable);
+    ExecutablePool::Allocation alloc = { reinterpret_cast<char*>(allocation), n };
+    return alloc;
 }
 
 void
-js::jit::SubAllocatedExecutableBytes(size_t bytes)
+ExecutableAllocator::systemRelease(const ExecutablePool::Allocation& alloc)
 {
-    MOZ_ASSERT(bytes <= allocatedExecutableBytes);
-    allocatedExecutableBytes -= bytes;
-}
-
-void
-js::jit::AssertAllocatedExecutableBytesIsZero()
-{
-    MOZ_ASSERT(allocatedExecutableBytes == 0);
+    DeallocateExecutableMemory(alloc.pages, alloc.size);
 }
-
-bool
-js::jit::CanLikelyAllocateMoreExecutableMemory()
-{
-    // Use a 16 MB buffer.
-    static const size_t BufferSize = 16 * 1024 * 1024;
-
-    MOZ_ASSERT(allocatedExecutableBytes <= MaxCodeBytesPerProcess);
-    return allocatedExecutableBytes + BufferSize <= MaxCodeBytesPerProcess;
-}
--- a/js/src/jit/ExecutableAllocator.h
+++ b/js/src/jit/ExecutableAllocator.h
@@ -37,16 +37,17 @@
 #include "jsalloc.h"
 
 #include "jit/arm/Simulator-arm.h"
 #if defined(JS_CODEGEN_ARM64)
 #include "jit/arm64/vixl/Cpu-vixl.h"
 #endif
 #include "jit/mips32/Simulator-mips32.h"
 #include "jit/mips64/Simulator-mips64.h"
+#include "jit/ProcessExecutableMemory.h"
 #include "js/GCAPI.h"
 #include "js/HashTable.h"
 #include "js/Vector.h"
 
 #if defined(__sparc__)
 #ifdef __linux__  // bugzilla 502369
 static void sync_instruction_memory(caddr_t v, u_int len)
 {
@@ -155,45 +156,36 @@ struct JitPoisonRange
 
     JitPoisonRange(jit::ExecutablePool* pool, void* start, size_t size)
       : pool(pool), start(start), size(size)
     {}
 };
 
 typedef Vector<JitPoisonRange, 0, SystemAllocPolicy> JitPoisonRangeVector;
 
-#define NON_WRITABLE_JIT_CODE 1
-
 class ExecutableAllocator
 {
     JSRuntime* rt_;
 
   public:
-    enum ProtectionSetting { Writable, Executable };
-
     explicit ExecutableAllocator(JSRuntime* rt);
     ~ExecutableAllocator();
 
     void purge();
 
     // alloc() returns a pointer to some memory, and also (by reference) a
     // pointer to reference-counted pool. The caller owns a reference to the
     // pool; i.e. alloc() increments the count before returning the object.
     void* alloc(size_t n, ExecutablePool** poolp, CodeKind type);
 
     void releasePoolPages(ExecutablePool* pool);
 
     void addSizeOfCode(JS::CodeSizes* sizes) const;
 
-    static void initStatic();
-
   private:
-    static size_t pageSize;
-    static size_t largeAllocSize;
-
     static const size_t OVERSIZE_ALLOCATION = size_t(-1);
 
     static size_t roundUpAllocationSize(size_t request, size_t granularity);
 
     // On OOM, this will return an Allocation where pages is nullptr.
     ExecutablePool::Allocation systemAlloc(size_t n);
     static void systemRelease(const ExecutablePool::Allocation& alloc);
 
@@ -201,42 +193,32 @@ class ExecutableAllocator
     ExecutablePool* poolForSize(size_t n);
 
     static void reprotectPool(JSRuntime* rt, ExecutablePool* pool, ProtectionSetting protection);
 
   public:
     MOZ_MUST_USE
     static bool makeWritable(void* start, size_t size)
     {
-#ifdef NON_WRITABLE_JIT_CODE
-        return reprotectRegion(start, size, Writable);
-#else
-        return true;
-#endif
+        return ReprotectRegion(start, size, ProtectionSetting::Writable);
     }
 
     MOZ_MUST_USE
     static bool makeExecutable(void* start, size_t size)
     {
-#ifdef NON_WRITABLE_JIT_CODE
-        return reprotectRegion(start, size, Executable);
-#else
-        return true;
-#endif
+        return ReprotectRegion(start, size, ProtectionSetting::Executable);
     }
 
     void makeAllWritable() {
-        reprotectAll(Writable);
+        reprotectAll(ProtectionSetting::Writable);
     }
     void makeAllExecutable() {
-        reprotectAll(Executable);
+        reprotectAll(ProtectionSetting::Executable);
     }
 
-    static unsigned initialProtectionFlags(ProtectionSetting protection);
-
     static void poisonCode(JSRuntime* rt, JitPoisonRangeVector& ranges);
 
 #if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64) || defined(JS_SIMULATOR_ARM64)
     static void cacheFlush(void*, size_t)
     {
     }
 #elif defined(JS_SIMULATOR_ARM) || defined(JS_SIMULATOR_MIPS32) || defined(JS_SIMULATOR_MIPS64)
     static void cacheFlush(void* code, size_t size)
@@ -306,63 +288,27 @@ class ExecutableAllocator
         sync_instruction_memory((caddr_t)code, size);
     }
 #endif
 
   private:
     ExecutableAllocator(const ExecutableAllocator&) = delete;
     void operator=(const ExecutableAllocator&) = delete;
 
-#ifdef NON_WRITABLE_JIT_CODE
-    MOZ_MUST_USE
-    static bool reprotectRegion(void*, size_t, ProtectionSetting);
-#endif
-
     void reprotectAll(ProtectionSetting);
 
     // These are strong references;  they keep pools alive.
     static const size_t maxSmallPools = 4;
     typedef js::Vector<ExecutablePool*, maxSmallPools, js::SystemAllocPolicy> SmallExecPoolVector;
     SmallExecPoolVector m_smallPools;
 
     // All live pools are recorded here, just for stats purposes.  These are
     // weak references;  they don't keep pools alive.  When a pool is destroyed
     // its reference is removed from m_pools.
     typedef js::HashSet<ExecutablePool*, js::DefaultHasher<ExecutablePool*>, js::SystemAllocPolicy>
             ExecPoolHashSet;
     ExecPoolHashSet m_pools;    // All pools, just for stats purposes.
-
-    static size_t determinePageSize();
 };
 
-extern void*
-AllocateExecutableMemory(size_t bytes, unsigned permissions, const char* tag,
-                         size_t pageSize);
-
-extern void
-DeallocateExecutableMemory(void* addr, size_t bytes, size_t pageSize);
-
-// These functions are called by the platform-specific definitions of
-// (Allocate|Deallocate)ExecutableMemory and should not otherwise be
-// called directly.
-
-extern MOZ_MUST_USE bool
-AddAllocatedExecutableBytes(size_t bytes);
-
-extern void
-SubAllocatedExecutableBytes(size_t bytes);
-
-extern void
-AssertAllocatedExecutableBytesIsZero();
-
-// Returns true if we can allocate a few more MB of executable code without
-// hitting our code limit. This function can be used to stop compiling things
-// that are optional (like Baseline and Ion code) when we're about to reach the
-// limit, so we are less likely to OOM or crash. Note that the limit is
-// per-process, so other threads can also allocate code after we call this
-// function.
-extern bool
-CanLikelyAllocateMoreExecutableMemory();
-
 } // namespace jit
 } // namespace js
 
 #endif /* jit_ExecutableAllocator_h */
deleted file mode 100644
--- a/js/src/jit/ExecutableAllocatorPosix.cpp
+++ /dev/null
@@ -1,126 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
- * vim: set ts=8 sts=4 et sw=4 tw=99:
- *
- * Copyright (C) 2008 Apple Inc. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
- * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#include "mozilla/DebugOnly.h"
-#include "mozilla/ScopeExit.h"
-#include "mozilla/TaggedAnonymousMemory.h"
-
-#include <errno.h>
-#include <sys/mman.h>
-#include <unistd.h>
-
-#include "jit/ExecutableAllocator.h"
-#include "js/Utility.h"
-
-using namespace js::jit;
-
-size_t
-ExecutableAllocator::determinePageSize()
-{
-    return getpagesize();
-}
-
-void*
-js::jit::AllocateExecutableMemory(size_t bytes, unsigned permissions, const char* tag,
-                                  size_t pageSize)
-{
-    MOZ_ASSERT(bytes % pageSize == 0);
-
-    if (!AddAllocatedExecutableBytes(bytes))
-        return nullptr;
-
-    auto autoSubtract = mozilla::MakeScopeExit([&] { SubAllocatedExecutableBytes(bytes); });
-
-    void* p = MozTaggedAnonymousMmap(nullptr, bytes, permissions, MAP_PRIVATE | MAP_ANON, -1, 0,
-                                     tag);
-    if (p == MAP_FAILED)
-        return nullptr;
-
-    autoSubtract.release();
-    return p;
-}
-
-void
-js::jit::DeallocateExecutableMemory(void* addr, size_t bytes, size_t pageSize)
-{
-    MOZ_ASSERT(bytes % pageSize == 0);
-    mozilla::DebugOnly<int> result = munmap(addr, bytes);
-    MOZ_ASSERT(!result || errno == ENOMEM);
-
-    SubAllocatedExecutableBytes(bytes);
-}
-
-ExecutablePool::Allocation
-ExecutableAllocator::systemAlloc(size_t n)
-{
-    void* allocation = AllocateExecutableMemory(n, initialProtectionFlags(Executable),
-                                                "js-jit-code", pageSize);
-    ExecutablePool::Allocation alloc = { reinterpret_cast<char*>(allocation), n };
-    return alloc;
-}
-
-void
-ExecutableAllocator::systemRelease(const ExecutablePool::Allocation& alloc)
-{
-    DeallocateExecutableMemory(alloc.pages, alloc.size, pageSize);
-}
-
-static const unsigned FLAGS_RW = PROT_READ | PROT_WRITE;
-static const unsigned FLAGS_RX = PROT_READ | PROT_EXEC;
-
-#if defined(NON_WRITABLE_JIT_CODE)
-
-bool
-ExecutableAllocator::reprotectRegion(void* start, size_t size, ProtectionSetting setting)
-{
-    MOZ_ASSERT(pageSize);
-
-    // Calculate the start of the page containing this region,
-    // and account for this extra memory within size.
-    intptr_t startPtr = reinterpret_cast<intptr_t>(start);
-    intptr_t pageStartPtr = startPtr & ~(pageSize - 1);
-    void* pageStart = reinterpret_cast<void*>(pageStartPtr);
-    size += (startPtr - pageStartPtr);
-
-    // Round size up
-    size += (pageSize - 1);
-    size &= ~(pageSize - 1);
-
-    return !mprotect(pageStart, size, (setting == Writable) ? FLAGS_RW : FLAGS_RX);
-}
-
-#endif // defined(NON_WRITABLE_JIT_CODE)
-
-/* static */ unsigned
-ExecutableAllocator::initialProtectionFlags(ProtectionSetting protection)
-{
-#ifdef NON_WRITABLE_JIT_CODE
-    return (protection == Writable) ? FLAGS_RW : FLAGS_RX;
-#else
-    return FLAGS_RW | FLAGS_RX;
-#endif
-}
deleted file mode 100644
--- a/js/src/jit/ExecutableAllocatorWin.cpp
+++ /dev/null
@@ -1,303 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
- * vim: set ts=8 sts=4 et sw=4 tw=99:
- *
- * Copyright (C) 2008 Apple Inc. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
- * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#include "mozilla/ScopeExit.h"
-#include "mozilla/StackWalk_windows.h"
-#include "mozilla/WindowsVersion.h"
-
-#include "jsfriendapi.h"
-#include "jsmath.h"
-#include "jswin.h"
-
-#include "jit/ExecutableAllocator.h"
-
-using namespace js::jit;
-
-size_t
-ExecutableAllocator::determinePageSize()
-{
-    SYSTEM_INFO system_info;
-    GetSystemInfo(&system_info);
-    return system_info.dwPageSize;
-}
-
-static void*
-ComputeRandomAllocationAddress()
-{
-    /*
-     * Inspiration is V8's OS::Allocate in platform-win32.cc.
-     *
-     * VirtualAlloc takes 64K chunks out of the virtual address space, so we
-     * keep 16b alignment.
-     *
-     * x86: V8 comments say that keeping addresses in the [64MiB, 1GiB) range
-     * tries to avoid system default DLL mapping space. In the end, we get 13
-     * bits of randomness in our selection.
-     * x64: [2GiB, 4TiB), with 25 bits of randomness.
-     */
-#ifdef HAVE_64BIT_BUILD
-    static const uintptr_t base = 0x0000000080000000;
-    static const uintptr_t mask = 0x000003ffffff0000;
-#elif defined(_M_IX86) || defined(__i386__)
-    static const uintptr_t base = 0x04000000;
-    static const uintptr_t mask = 0x3fff0000;
-#else
-# error "Unsupported architecture"
-#endif
-
-    uint64_t rand = js::GenerateRandomSeed();
-    return (void*) (base | (rand & mask));
-}
-
-#ifdef HAVE_64BIT_BUILD
-static js::JitExceptionHandler sJitExceptionHandler;
-
-JS_FRIEND_API(void)
-js::SetJitExceptionHandler(JitExceptionHandler handler)
-{
-    MOZ_ASSERT(!sJitExceptionHandler);
-    sJitExceptionHandler = handler;
-}
-
-// From documentation for UNWIND_INFO on
-// http://msdn.microsoft.com/en-us/library/ddssxxy8.aspx
-struct UnwindInfo
-{
-    uint8_t version : 3;
-    uint8_t flags : 5;
-    uint8_t sizeOfPrologue;
-    uint8_t countOfUnwindCodes;
-    uint8_t frameRegister : 4;
-    uint8_t frameOffset : 4;
-    ULONG exceptionHandler;
-};
-
-static const unsigned ThunkLength = 12;
-
-struct ExceptionHandlerRecord
-{
-    RUNTIME_FUNCTION runtimeFunction;
-    UnwindInfo unwindInfo;
-    uint8_t thunk[ThunkLength];
-};
-
-// This function must match the function pointer type PEXCEPTION_HANDLER
-// mentioned in:
-//   http://msdn.microsoft.com/en-us/library/ssa62fwe.aspx.
-// This type is rather elusive in documentation; Wine is the best I've found:
-//   http://source.winehq.org/source/include/winnt.h
-static DWORD
-ExceptionHandler(PEXCEPTION_RECORD exceptionRecord, _EXCEPTION_REGISTRATION_RECORD*,
-                 PCONTEXT context, _EXCEPTION_REGISTRATION_RECORD**)
-{
-    return sJitExceptionHandler(exceptionRecord, context);
-}
-
-// For an explanation of the problem being solved here, see
-// SetJitExceptionFilter in jsfriendapi.h.
-static bool
-RegisterExecutableMemory(void* p, size_t bytes, size_t pageSize)
-{
-    DWORD oldProtect;
-    if (!VirtualProtect(p, pageSize, PAGE_READWRITE, &oldProtect))
-        return false;
-
-    ExceptionHandlerRecord* r = reinterpret_cast<ExceptionHandlerRecord*>(p);
-
-    // All these fields are specified to be offsets from the base of the
-    // executable code (which is 'p'), even if they have 'Address' in their
-    // names. In particular, exceptionHandler is a ULONG offset which is a
-    // 32-bit integer. Since 'p' can be farther than INT32_MAX away from
-    // sJitExceptionHandler, we must generate a little thunk inside the
-    // record. The record is put on its own page so that we can take away write
-    // access to protect against accidental clobbering.
-
-    r->runtimeFunction.BeginAddress = pageSize;
-    r->runtimeFunction.EndAddress = (DWORD)bytes;
-    r->runtimeFunction.UnwindData = offsetof(ExceptionHandlerRecord, unwindInfo);
-
-    r->unwindInfo.version = 1;
-    r->unwindInfo.flags = UNW_FLAG_EHANDLER;
-    r->unwindInfo.sizeOfPrologue = 0;
-    r->unwindInfo.countOfUnwindCodes = 0;
-    r->unwindInfo.frameRegister = 0;
-    r->unwindInfo.frameOffset = 0;
-    r->unwindInfo.exceptionHandler = offsetof(ExceptionHandlerRecord, thunk);
-
-    // mov imm64, rax
-    r->thunk[0]  = 0x48;
-    r->thunk[1]  = 0xb8;
-    void* handler = JS_FUNC_TO_DATA_PTR(void*, ExceptionHandler);
-    memcpy(&r->thunk[2], &handler, 8);
-
-    // jmp rax
-    r->thunk[10] = 0xff;
-    r->thunk[11] = 0xe0;
-
-    if (!VirtualProtect(p, pageSize, PAGE_EXECUTE_READ, &oldProtect))
-        return false;
-
-    // XXX NB: The profiler believes this function is only called from the main
-    // thread. If that ever becomes untrue, SPS must be updated immediately.
-    AcquireStackWalkWorkaroundLock();
-
-    bool success = RtlAddFunctionTable(&r->runtimeFunction, 1, reinterpret_cast<DWORD64>(p));
-
-    ReleaseStackWalkWorkaroundLock();
-
-    return success;
-}
-
-static void
-UnregisterExecutableMemory(void* p, size_t bytes, size_t pageSize)
-{
-    ExceptionHandlerRecord* r = reinterpret_cast<ExceptionHandlerRecord*>(p);
-
-    // XXX NB: The profiler believes this function is only called from the main
-    // thread. If that ever becomes untrue, SPS must be updated immediately.
-    AcquireStackWalkWorkaroundLock();
-
-    RtlDeleteFunctionTable(&r->runtimeFunction);
-
-    ReleaseStackWalkWorkaroundLock();
-}
-#endif
-
-static const size_t VirtualAllocGranularity = 64 * 1024;
-
-void*
-js::jit::AllocateExecutableMemory(size_t bytes, unsigned permissions, const char* tag,
-                                  size_t pageSize)
-{
-    MOZ_ASSERT(bytes % pageSize == 0);
-
-    // VirtualAlloc returns 64 KB chunks, so we round the value we pass to
-    // AddAllocatedExecutableBytes up to 64 KB to account for this. See
-    // bug 1325200.
-    size_t bytesRounded = JS_ROUNDUP(bytes, VirtualAllocGranularity);
-    if (!AddAllocatedExecutableBytes(bytesRounded))
-        return nullptr;
-
-    auto autoSubtract = mozilla::MakeScopeExit([&] { SubAllocatedExecutableBytes(bytesRounded); });
-
-#ifdef HAVE_64BIT_BUILD
-    if (sJitExceptionHandler)
-        bytes += pageSize;
-#endif
-
-    void* randomAddr = ComputeRandomAllocationAddress();
-
-    void* p = VirtualAlloc(randomAddr, bytes, MEM_COMMIT | MEM_RESERVE, permissions);
-    if (!p) {
-        // Try again without randomAddr.
-        p = VirtualAlloc(nullptr, bytes, MEM_COMMIT | MEM_RESERVE, permissions);
-        if (!p)
-            return nullptr;
-    }
-
-#ifdef HAVE_64BIT_BUILD
-    if (sJitExceptionHandler) {
-        if (!RegisterExecutableMemory(p, bytes, pageSize)) {
-            VirtualFree(p, 0, MEM_RELEASE);
-            return nullptr;
-        }
-
-        p = (uint8_t*)p + pageSize;
-    }
-#endif
-
-    autoSubtract.release();
-    return p;
-}
-
-void
-js::jit::DeallocateExecutableMemory(void* addr, size_t bytes, size_t pageSize)
-{
-    MOZ_ASSERT(bytes % pageSize == 0);
-
-#ifdef HAVE_64BIT_BUILD
-    if (sJitExceptionHandler) {
-        addr = (uint8_t*)addr - pageSize;
-        UnregisterExecutableMemory(addr, bytes, pageSize);
-    }
-#endif
-
-    VirtualFree(addr, 0, MEM_RELEASE);
-
-    SubAllocatedExecutableBytes(JS_ROUNDUP(bytes, VirtualAllocGranularity));
-}
-
-ExecutablePool::Allocation
-ExecutableAllocator::systemAlloc(size_t n)
-{
-    unsigned flags = initialProtectionFlags(Executable);
-    void* allocation = AllocateExecutableMemory(n, flags, "js-jit-code", pageSize);
-
-    ExecutablePool::Allocation alloc = { reinterpret_cast<char*>(allocation), n };
-    return alloc;
-}
-
-void
-ExecutableAllocator::systemRelease(const ExecutablePool::Allocation& alloc)
-{
-    DeallocateExecutableMemory(alloc.pages, alloc.size, pageSize);
-}
-
-#if defined(NON_WRITABLE_JIT_CODE)
-
-bool
-ExecutableAllocator::reprotectRegion(void* start, size_t size, ProtectionSetting setting)
-{
-    MOZ_ASSERT(pageSize);
-
-    // Calculate the start of the page containing this region,
-    // and account for this extra memory within size.
-    intptr_t startPtr = reinterpret_cast<intptr_t>(start);
-    intptr_t pageStartPtr = startPtr & ~(pageSize - 1);
-    void* pageStart = reinterpret_cast<void*>(pageStartPtr);
-    size += (startPtr - pageStartPtr);
-
-    // Round size up
-    size += (pageSize - 1);
-    size &= ~(pageSize - 1);
-
-    DWORD oldProtect;
-    int flags = (setting == Writable) ? PAGE_READWRITE : PAGE_EXECUTE_READ;
-    return VirtualProtect(pageStart, size, flags, &oldProtect);
-}
-
-#endif // defined(NON_WRITABLE_JIT_CODE)
-
-/* static */ unsigned
-ExecutableAllocator::initialProtectionFlags(ProtectionSetting protection)
-{
-#ifdef NON_WRITABLE_JIT_CODE
-    return (protection == Writable) ? PAGE_READWRITE : PAGE_EXECUTE_READ;
-#else
-    return PAGE_EXECUTE_READWRITE;
-#endif
-}
new file mode 100644
--- /dev/null
+++ b/js/src/jit/ProcessExecutableMemory.cpp
@@ -0,0 +1,656 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "jit/ProcessExecutableMemory.h"
+
+#include "mozilla/Array.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/TaggedAnonymousMemory.h"
+#include "mozilla/XorShift128PlusRNG.h"
+
+#include "jsfriendapi.h"
+#include "jsmath.h"
+#include "jsutil.h"
+#include "jswin.h"
+
+#include <errno.h>
+
+#include "gc/Memory.h"
+#include "threading/LockGuard.h"
+#include "threading/Mutex.h"
+#include "vm/MutexIDs.h"
+
+#ifdef XP_WIN
+# include "mozilla/StackWalk_windows.h"
+# include "mozilla/WindowsVersion.h"
+#else
+# include <sys/mman.h>
+# include <unistd.h>
+#endif
+
+using namespace js;
+using namespace js::jit;
+
+#ifdef XP_WIN
+static void*
+ComputeRandomAllocationAddress()
+{
+    /*
+     * Inspiration is V8's OS::Allocate in platform-win32.cc.
+     *
+     * VirtualAlloc takes 64K chunks out of the virtual address space, so we
+     * keep 16b alignment.
+     *
+     * x86: V8 comments say that keeping addresses in the [64MiB, 1GiB) range
+     * tries to avoid system default DLL mapping space. In the end, we get 13
+     * bits of randomness in our selection.
+     * x64: [2GiB, 4TiB), with 25 bits of randomness.
+     */
+# ifdef HAVE_64BIT_BUILD
+    static const uintptr_t base = 0x0000000080000000;
+    static const uintptr_t mask = 0x000003ffffff0000;
+# elif defined(_M_IX86) || defined(__i386__)
+    static const uintptr_t base = 0x04000000;
+    static const uintptr_t mask = 0x3fff0000;
+# else
+#  error "Unsupported architecture"
+# endif
+
+    uint64_t rand = js::GenerateRandomSeed();
+    return (void*) (base | (rand & mask));
+}
+
+# ifdef HAVE_64BIT_BUILD
+static js::JitExceptionHandler sJitExceptionHandler;
+
+JS_FRIEND_API(void)
+js::SetJitExceptionHandler(JitExceptionHandler handler)
+{
+    MOZ_ASSERT(!sJitExceptionHandler);
+    sJitExceptionHandler = handler;
+}
+
+// From documentation for UNWIND_INFO on
+// http://msdn.microsoft.com/en-us/library/ddssxxy8.aspx
+struct UnwindInfo
+{
+    uint8_t version : 3;
+    uint8_t flags : 5;
+    uint8_t sizeOfPrologue;
+    uint8_t countOfUnwindCodes;
+    uint8_t frameRegister : 4;
+    uint8_t frameOffset : 4;
+    ULONG exceptionHandler;
+};
+
+static const unsigned ThunkLength = 12;
+
+struct ExceptionHandlerRecord
+{
+    RUNTIME_FUNCTION runtimeFunction;
+    UnwindInfo unwindInfo;
+    uint8_t thunk[ThunkLength];
+};
+
+// This function must match the function pointer type PEXCEPTION_HANDLER
+// mentioned in:
+//   http://msdn.microsoft.com/en-us/library/ssa62fwe.aspx.
+// This type is rather elusive in documentation; Wine is the best I've found:
+//   http://source.winehq.org/source/include/winnt.h
+static DWORD
+ExceptionHandler(PEXCEPTION_RECORD exceptionRecord, _EXCEPTION_REGISTRATION_RECORD*,
+                 PCONTEXT context, _EXCEPTION_REGISTRATION_RECORD**)
+{
+    return sJitExceptionHandler(exceptionRecord, context);
+}
+
+// For an explanation of the problem being solved here, see
+// SetJitExceptionFilter in jsfriendapi.h.
+static bool
+RegisterExecutableMemory(void* p, size_t bytes, size_t pageSize)
+{
+    if (!VirtualAlloc(p, pageSize, MEM_COMMIT, PAGE_READWRITE))
+        MOZ_CRASH();
+
+    ExceptionHandlerRecord* r = reinterpret_cast<ExceptionHandlerRecord*>(p);
+
+    // All these fields are specified to be offsets from the base of the
+    // executable code (which is 'p'), even if they have 'Address' in their
+    // names. In particular, exceptionHandler is a ULONG offset which is a
+    // 32-bit integer. Since 'p' can be farther than INT32_MAX away from
+    // sJitExceptionHandler, we must generate a little thunk inside the
+    // record. The record is put on its own page so that we can take away write
+    // access to protect against accidental clobbering.
+
+    r->runtimeFunction.BeginAddress = pageSize;
+    r->runtimeFunction.EndAddress = (DWORD)bytes;
+    r->runtimeFunction.UnwindData = offsetof(ExceptionHandlerRecord, unwindInfo);
+
+    r->unwindInfo.version = 1;
+    r->unwindInfo.flags = UNW_FLAG_EHANDLER;
+    r->unwindInfo.sizeOfPrologue = 0;
+    r->unwindInfo.countOfUnwindCodes = 0;
+    r->unwindInfo.frameRegister = 0;
+    r->unwindInfo.frameOffset = 0;
+    r->unwindInfo.exceptionHandler = offsetof(ExceptionHandlerRecord, thunk);
+
+    // mov imm64, rax
+    r->thunk[0]  = 0x48;
+    r->thunk[1]  = 0xb8;
+    void* handler = JS_FUNC_TO_DATA_PTR(void*, ExceptionHandler);
+    memcpy(&r->thunk[2], &handler, 8);
+
+    // jmp rax
+    r->thunk[10] = 0xff;
+    r->thunk[11] = 0xe0;
+
+    DWORD oldProtect;
+    if (!VirtualProtect(p, pageSize, PAGE_EXECUTE_READ, &oldProtect))
+        MOZ_CRASH();
+
+    // XXX NB: The profiler believes this function is only called from the main
+    // thread. If that ever becomes untrue, the profiler must be updated
+    // immediately.
+    AcquireStackWalkWorkaroundLock();
+
+    bool success = RtlAddFunctionTable(&r->runtimeFunction, 1, reinterpret_cast<DWORD64>(p));
+
+    ReleaseStackWalkWorkaroundLock();
+
+    return success;
+}
+
+static void
+UnregisterExecutableMemory(void* p, size_t bytes, size_t pageSize)
+{
+    ExceptionHandlerRecord* r = reinterpret_cast<ExceptionHandlerRecord*>(p);
+
+    // XXX NB: The profiler believes this function is only called from the main
+    // thread. If that ever becomes untrue, the profiler must be updated
+    // immediately.
+    AcquireStackWalkWorkaroundLock();
+
+    RtlDeleteFunctionTable(&r->runtimeFunction);
+
+    ReleaseStackWalkWorkaroundLock();
+}
+# endif
+
+static void*
+ReserveProcessExecutableMemory(size_t bytes)
+{
+# ifdef HAVE_64BIT_BUILD
+    size_t pageSize = gc::SystemPageSize();
+    if (sJitExceptionHandler)
+        bytes += pageSize;
+# endif
+
+    void* p = nullptr;
+    for (size_t i = 0; i < 10; i++) {
+        void* randomAddr = ComputeRandomAllocationAddress();
+        p = VirtualAlloc(randomAddr, bytes, MEM_RESERVE, PAGE_NOACCESS);
+        if (p)
+            break;
+    }
+
+    if (!p) {
+        // Try again without randomization.
+        p = VirtualAlloc(nullptr, bytes, MEM_RESERVE, PAGE_NOACCESS);
+        if (!p)
+            return nullptr;
+    }
+
+# ifdef HAVE_64BIT_BUILD
+    if (sJitExceptionHandler) {
+        if (!RegisterExecutableMemory(p, bytes, pageSize)) {
+            VirtualFree(p, 0, MEM_RELEASE);
+            return nullptr;
+        }
+
+        p = (uint8_t*)p + pageSize;
+    }
+# endif
+
+    return p;
+}
+
+static void
+DeallocateProcessExecutableMemory(void* addr, size_t bytes)
+{
+# ifdef HAVE_64BIT_BUILD
+    if (sJitExceptionHandler) {
+        size_t pageSize = gc::SystemPageSize();
+        addr = (uint8_t*)addr - pageSize;
+        UnregisterExecutableMemory(addr, bytes, pageSize);
+    }
+# endif
+
+    VirtualFree(addr, 0, MEM_RELEASE);
+}
+
+static DWORD
+ProtectionSettingToFlags(ProtectionSetting protection)
+{
+    switch (protection) {
+      case ProtectionSetting::Protected:  return PAGE_NOACCESS;
+      case ProtectionSetting::Writable:   return PAGE_READWRITE;
+      case ProtectionSetting::Executable: return PAGE_EXECUTE_READ;
+    }
+    MOZ_CRASH();
+}
+
+static void
+CommitPages(void* addr, size_t bytes, ProtectionSetting protection)
+{
+    if (!VirtualAlloc(addr, bytes, MEM_COMMIT, ProtectionSettingToFlags(protection)))
+        MOZ_CRASH("CommitPages failed");
+}
+
+static void
+DecommitPages(void* addr, size_t bytes)
+{
+    if (!VirtualFree(addr, bytes, MEM_DECOMMIT))
+        MOZ_CRASH("DecommitPages failed");
+}
+#else // !XP_WIN
+static void*
+ComputeRandomAllocationAddress()
+{
+    uint64_t rand = js::GenerateRandomSeed();
+
+# ifdef HAVE_64BIT_BUILD
+    // x64 CPUs have a 48-bit address space and on some platforms the OS will
+    // give us access to 47 bits, so to be safe we right shift by 18 to leave
+    // 46 bits.
+    rand >>= 18;
+# else
+    // On 32-bit, right shift by 34 to leave 30 bits, range [0, 1GiB). Then add
+    // 512MiB to get range [512MiB, 1.5GiB), or [0x20000000, 0x60000000). This
+    // is based on V8 comments in platform-posix.cc saying this range is
+    // relatively unpopulated across a variety of kernels.
+    rand >>= 34;
+    rand += 512 * 1024 * 1024;
+# endif
+
+    // Ensure page alignment.
+    uintptr_t mask = ~uintptr_t(gc::SystemPageSize() - 1);
+    return (void*) uintptr_t(rand & mask);
+}
+
+static void*
+ReserveProcessExecutableMemory(size_t bytes)
+{
+    // Note that randomAddr is just a hint: if the address is not available
+    // mmap will pick a different address.
+    void* randomAddr = ComputeRandomAllocationAddress();
+    void* p = MozTaggedAnonymousMmap(randomAddr, bytes, PROT_NONE, MAP_PRIVATE | MAP_ANON,
+                                     -1, 0, "js-executable-memory");
+    if (p == MAP_FAILED)
+        return nullptr;
+    return p;
+}
+
+static void
+DeallocateProcessExecutableMemory(void* addr, size_t bytes)
+{
+    mozilla::DebugOnly<int> result = munmap(addr, bytes);
+    MOZ_ASSERT(!result || errno == ENOMEM);
+}
+
+static unsigned
+ProtectionSettingToFlags(ProtectionSetting protection)
+{
+    switch (protection) {
+      case ProtectionSetting::Protected:  return PROT_NONE;
+      case ProtectionSetting::Writable:   return PROT_READ | PROT_WRITE;
+      case ProtectionSetting::Executable: return PROT_READ | PROT_EXEC;
+    }
+    MOZ_CRASH();
+}
+
+static void
+CommitPages(void* addr, size_t bytes, ProtectionSetting protection)
+{
+    void* p = MozTaggedAnonymousMmap(addr, bytes, ProtectionSettingToFlags(protection),
+                                     MAP_FIXED | MAP_PRIVATE | MAP_ANON,
+                                     -1, 0, "js-executable-memory");
+    MOZ_RELEASE_ASSERT(addr == p);
+}
+
+static void
+DecommitPages(void* addr, size_t bytes)
+{
+    // Use mmap with MAP_FIXED and PROT_NONE. Inspired by jemalloc's
+    // pages_decommit.
+    void* p = MozTaggedAnonymousMmap(addr, bytes, PROT_NONE,
+                                     MAP_FIXED | MAP_PRIVATE | MAP_ANON,
+                                     -1, 0, "js-executable-memory");
+    MOZ_RELEASE_ASSERT(addr == p);
+}
+#endif
+
+template <size_t NumBits>
+class PageBitSet
+{
+    using WordType = uint32_t;
+    static const size_t BitsPerWord = sizeof(WordType) * 8;
+
+    static_assert((NumBits % BitsPerWord) == 0,
+                  "NumBits must be a multiple of BitsPerWord");
+    static const size_t NumWords = NumBits / BitsPerWord;
+
+    mozilla::Array<WordType, NumWords> words_;
+
+    uint32_t indexToWord(uint32_t index) const {
+        MOZ_ASSERT(index < NumBits);
+        return index / BitsPerWord;
+    }
+    WordType indexToBit(uint32_t index) const {
+        MOZ_ASSERT(index < NumBits);
+        return WordType(1) << (index % BitsPerWord);
+    }
+
+  public:
+    void init() {
+        mozilla::PodArrayZero(words_);
+    }
+    bool contains(size_t index) const {
+        uint32_t word = indexToWord(index);
+        return words_[word] & indexToBit(index);
+    }
+    void insert(size_t index) {
+        MOZ_ASSERT(!contains(index));
+        uint32_t word = indexToWord(index);
+        words_[word] |= indexToBit(index);
+    }
+    void remove(size_t index) {
+        MOZ_ASSERT(contains(index));
+        uint32_t word = indexToWord(index);
+        words_[word] &= ~indexToBit(index);
+    }
+
+#ifdef DEBUG
+    bool empty() const {
+        for (size_t i = 0; i < NumWords; i++) {
+            if (words_[i] != 0)
+                return false;
+        }
+        return true;
+    }
+#endif
+};
+
+// Limit on the number of bytes of executable memory to prevent JIT spraying
+// attacks.
+#if JS_BITS_PER_WORD == 32
+static const size_t MaxCodeBytesPerProcess = 128 * 1024 * 1024;
+#else
+static const size_t MaxCodeBytesPerProcess = 640 * 1024 * 1024;
+#endif
+
+// Per-process executable memory allocator. It reserves a block of memory of
+// MaxCodeBytesPerProcess bytes, then allocates/deallocates pages from that.
+//
+// This has a number of benefits compared to raw mmap/VirtualAlloc:
+//
+// * More resillient against certain attacks.
+//
+// * Behaves more consistently across platforms: it avoids the 64K granularity
+//   issues on Windows, for instance.
+//
+// * On x64, near jumps can be used for jumps to other JIT pages.
+//
+// * On Win64, we have to register the exception handler only once (at process
+//   startup). This saves some memory and avoids RtlAddFunctionTable profiler
+//   deadlocks.
+class ProcessExecutableMemory
+{
+    static_assert((MaxCodeBytesPerProcess % ExecutableCodePageSize) == 0,
+                  "MaxCodeBytesPerProcess must be a multiple of ExecutableCodePageSize");
+    static const size_t MaxCodePages = MaxCodeBytesPerProcess / ExecutableCodePageSize;
+
+    // Start of the MaxCodeBytesPerProcess memory block or nullptr if
+    // uninitialized. Note that this is NOT guaranteed to be aligned to
+    // ExecutableCodePageSize.
+    uint8_t* base_;
+
+    // The fields below should only be accessed while we hold the lock.
+    Mutex lock_;
+
+    // pagesAllocated_ is an Atomic so that bytesAllocated does not have to
+    // take the lock.
+    mozilla::Atomic<size_t, mozilla::ReleaseAcquire> pagesAllocated_;
+
+    // Page where we should try to allocate next.
+    size_t cursor_;
+
+    mozilla::Maybe<mozilla::non_crypto::XorShift128PlusRNG> rng_;
+    PageBitSet<MaxCodePages> pages_;
+
+  public:
+    ProcessExecutableMemory()
+      : base_(nullptr),
+        lock_(mutexid::ProcessExecutableRegion),
+        pagesAllocated_(0),
+        cursor_(0),
+        rng_(),
+        pages_()
+    {}
+
+    MOZ_MUST_USE bool init() {
+        pages_.init();
+
+        MOZ_RELEASE_ASSERT(!initialized());
+        MOZ_RELEASE_ASSERT(gc::SystemPageSize() <= ExecutableCodePageSize);
+
+        void* p = ReserveProcessExecutableMemory(MaxCodeBytesPerProcess);
+        if (!p)
+            return false;
+
+        base_ = static_cast<uint8_t*>(p);
+
+        mozilla::Array<uint64_t, 2> seed;
+        GenerateXorShift128PlusSeed(seed);
+        rng_.emplace(seed[0], seed[1]);
+        return true;
+    }
+
+    bool initialized() const {
+        return base_ != nullptr;
+    }
+
+    size_t bytesAllocated() const {
+        MOZ_ASSERT(pagesAllocated_ <= MaxCodePages);
+        return pagesAllocated_ * ExecutableCodePageSize;
+    }
+
+    void release() {
+        MOZ_ASSERT(initialized());
+        MOZ_ASSERT(pages_.empty());
+        MOZ_ASSERT(pagesAllocated_ == 0);
+        DeallocateProcessExecutableMemory(base_, MaxCodeBytesPerProcess);
+        base_ = nullptr;
+        rng_.reset();
+        MOZ_ASSERT(!initialized());
+    }
+
+    void assertValidAddress(void* p, size_t bytes) const {
+        MOZ_RELEASE_ASSERT(p >= base_ &&
+                           uintptr_t(p) + bytes <= uintptr_t(base_) + MaxCodeBytesPerProcess);
+    }
+
+    void* allocate(size_t bytes, ProtectionSetting protection);
+    void deallocate(void* addr, size_t bytes);
+};
+
+void*
+ProcessExecutableMemory::allocate(size_t bytes, ProtectionSetting protection)
+{
+    MOZ_ASSERT(initialized());
+    MOZ_ASSERT(bytes > 0);
+    MOZ_ASSERT((bytes % ExecutableCodePageSize) == 0);
+
+    size_t numPages = bytes / ExecutableCodePageSize;
+
+    // Take the lock and try to allocate.
+    void* p = nullptr;
+    {
+        LockGuard<Mutex> guard(lock_);
+        MOZ_ASSERT(pagesAllocated_ <= MaxCodePages);
+
+        // Check if we have enough pages available.
+        if (pagesAllocated_ + numPages >= MaxCodePages)
+            return nullptr;
+
+        MOZ_ASSERT(bytes <= MaxCodeBytesPerProcess);
+
+        // Maybe skip a page to make allocations less predictable.
+        size_t page = cursor_ + (rng_.ref().next() % 2);
+
+        for (size_t i = 0; i < MaxCodePages; i++) {
+            // Make sure page + numPages - 1 is a valid index.
+            if (page + numPages > MaxCodePages)
+                page = 0;
+
+            bool available = true;
+            for (size_t j = 0; j < numPages; j++) {
+                if (pages_.contains(page + j)) {
+                    available = false;
+                    break;
+                }
+            }
+            if (!available) {
+                page++;
+                continue;
+            }
+
+            // Mark the pages as unavailable.
+            for (size_t j = 0; j < numPages; j++)
+                pages_.insert(page + j);
+
+            pagesAllocated_ += numPages;
+            MOZ_ASSERT(pagesAllocated_ <= MaxCodePages);
+
+            // If we allocated a small number of pages, move cursor_ to the
+            // next page. We don't do this for larger allocations to avoid
+            // skipping a large number of small holes.
+            if (numPages <= 2)
+                cursor_ = page + numPages;
+
+            p = base_ + page * ExecutableCodePageSize;
+            break;
+        }
+        if (!p)
+            return nullptr;
+    }
+
+    // Commit the pages after releasing the lock.
+    CommitPages(p, bytes, protection);
+    return p;
+}
+
+void
+ProcessExecutableMemory::deallocate(void* addr, size_t bytes)
+{
+    MOZ_ASSERT(initialized());
+    MOZ_ASSERT(addr);
+    MOZ_ASSERT((uintptr_t(addr) % gc::SystemPageSize()) == 0);
+    MOZ_ASSERT(bytes > 0);
+    MOZ_ASSERT((bytes % ExecutableCodePageSize) == 0);
+
+    assertValidAddress(addr, bytes);
+
+    size_t firstPage = (static_cast<uint8_t*>(addr) - base_) / ExecutableCodePageSize;
+    size_t numPages = bytes / ExecutableCodePageSize;
+
+    // Decommit before taking the lock.
+    DecommitPages(addr, bytes);
+
+    LockGuard<Mutex> guard(lock_);
+    MOZ_ASSERT(numPages <= pagesAllocated_);
+    pagesAllocated_ -= numPages;
+
+    for (size_t i = 0; i < numPages; i++)
+        pages_.remove(firstPage + i);
+
+    // Move the cursor back so we can reuse pages instead of fragmenting the
+    // whole region.
+    if (firstPage < cursor_)
+        cursor_ = firstPage;
+}
+
+static ProcessExecutableMemory execMemory;
+
+void*
+js::jit::AllocateExecutableMemory(size_t bytes, ProtectionSetting protection)
+{
+    return execMemory.allocate(bytes, protection);
+}
+
+void
+js::jit::DeallocateExecutableMemory(void* addr, size_t bytes)
+{
+    execMemory.deallocate(addr, bytes);
+}
+
+bool
+js::jit::InitProcessExecutableMemory()
+{
+    return execMemory.init();
+}
+
+void
+js::jit::ReleaseProcessExecutableMemory()
+{
+    execMemory.release();
+}
+
+bool
+js::jit::CanLikelyAllocateMoreExecutableMemory()
+{
+    // Use a 16 MB buffer.
+    static const size_t BufferSize = 16 * 1024 * 1024;
+
+    MOZ_ASSERT(execMemory.bytesAllocated() <= MaxCodeBytesPerProcess);
+
+    return execMemory.bytesAllocated() + BufferSize <= MaxCodeBytesPerProcess;
+}
+
+bool
+js::jit::ReprotectRegion(void* start, size_t size, ProtectionSetting protection)
+{
+    // Calculate the start of the page containing this region,
+    // and account for this extra memory within size.
+    size_t pageSize = gc::SystemPageSize();
+    intptr_t startPtr = reinterpret_cast<intptr_t>(start);
+    intptr_t pageStartPtr = startPtr & ~(pageSize - 1);
+    void* pageStart = reinterpret_cast<void*>(pageStartPtr);
+    size += (startPtr - pageStartPtr);
+
+    // Round size up
+    size += (pageSize - 1);
+    size &= ~(pageSize - 1);
+
+    MOZ_ASSERT((uintptr_t(pageStart) % pageSize) == 0);
+
+    execMemory.assertValidAddress(pageStart, size);
+
+#ifdef XP_WIN
+    DWORD oldProtect;
+    DWORD flags = ProtectionSettingToFlags(protection);
+    if (!VirtualProtect(pageStart, size, flags, &oldProtect))
+        return false;
+#else
+    unsigned flags = ProtectionSettingToFlags(protection);
+    if (mprotect(pageStart, size, flags))
+        return false;
+#endif
+
+    execMemory.assertValidAddress(pageStart, size);
+    return true;
+}
new file mode 100644
--- /dev/null
+++ b/js/src/jit/ProcessExecutableMemory.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef jit_ProcessExecutableMemory_h
+#define jit_ProcessExecutableMemory_h
+
+#include "mozilla/Attributes.h"
+
+namespace js {
+namespace jit {
+
+// Executable code is allocated in 64K chunks. ExecutableAllocator uses pools
+// that are at least this big. Code we allocate does not necessarily have 64K
+// alignment though.
+static const size_t ExecutableCodePageSize = 64 * 1024;
+
+enum class ProtectionSetting {
+    Protected, // Not readable, writable, or executable.
+    Writable,
+    Executable,
+};
+
+extern MOZ_MUST_USE bool ReprotectRegion(void* start, size_t size, ProtectionSetting protection);
+
+// Functions called at process start-up/shutdown to initialize/release the
+// executable memory region.
+extern MOZ_MUST_USE bool InitProcessExecutableMemory();
+extern void ReleaseProcessExecutableMemory();
+
+// Allocate/deallocate executable pages.
+extern void* AllocateExecutableMemory(size_t bytes, ProtectionSetting protection);
+extern void DeallocateExecutableMemory(void* addr, size_t bytes);
+
+// Returns true if we can allocate a few more MB of executable code without
+// hitting our code limit. This function can be used to stop compiling things
+// that are optional (like Baseline and Ion code) when we're about to reach the
+// limit, so we are less likely to OOM or crash. Note that the limit is
+// per-process, so other threads can also allocate code after we call this
+// function.
+extern bool CanLikelyAllocateMoreExecutableMemory();
+
+} // namespace jit
+} // namespace js
+
+#endif // jit_ProcessExecutableMemory_h
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -3587,20 +3587,22 @@ GCRuntime::purgeRuntime(AutoLockForExclu
     if (auto cache = rt->maybeThisRuntimeSharedImmutableStrings())
         cache->purge();
 
     rt->promiseTasksToDestroy.lock()->clear();
 }
 
 bool
 GCRuntime::shouldPreserveJITCode(JSCompartment* comp, int64_t currentTime,
-                                 JS::gcreason::Reason reason)
+                                 JS::gcreason::Reason reason, bool canAllocateMoreCode)
 {
     if (cleanUpEverything)
         return false;
+    if (!canAllocateMoreCode)
+        return false;
 
     if (alwaysPreserveCode)
         return true;
     if (comp->preserveJitCode())
         return true;
     if (comp->lastAnimationTime + PRMJ_USEC_PER_SEC >= currentTime)
         return true;
     if (reason == JS::gcreason::DEBUG_GC)
@@ -3767,25 +3769,29 @@ GCRuntime::beginMarkPhase(JS::gcreason::
             }
         } else {
             isFull = false;
         }
 
         zone->setPreservingCode(false);
     }
 
+    // Discard JIT code more aggressively if the process is approaching its
+    // executable code limit.
+    bool canAllocateMoreCode = jit::CanLikelyAllocateMoreExecutableMemory();
+
     for (CompartmentsIter c(rt, WithAtoms); !c.done(); c.next()) {
         c->marked = false;
         c->scheduledForDestruction = false;
         c->maybeAlive = c->hasBeenEntered() || !c->zone()->isGCScheduled();
-        if (shouldPreserveJITCode(c, currentTime, reason))
+        if (shouldPreserveJITCode(c, currentTime, reason, canAllocateMoreCode))
             c->zone()->setPreservingCode(true);
     }
 
-    if (!rt->gc.cleanUpEverything) {
+    if (!rt->gc.cleanUpEverything && canAllocateMoreCode) {
         if (JSCompartment* comp = jit::TopmostIonActivationCompartment(rt))
             comp->zone()->setPreservingCode(true);
     }
 
     /*
      * Atoms are not in the cross-compartment map. If there are any zones that
      * are not being collected then we cannot collect the atoms zone, otherwise
      * the non-collected zones could contain pointers to atoms that we would
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -253,16 +253,17 @@ UNIFIED_SOURCES += [
     'jit/Lowering.cpp',
     'jit/MacroAssembler.cpp',
     'jit/MCallOptimize.cpp',
     'jit/MIR.cpp',
     'jit/MIRGraph.cpp',
     'jit/MoveResolver.cpp',
     'jit/OptimizationTracking.cpp',
     'jit/PerfSpewer.cpp',
+    'jit/ProcessExecutableMemory.cpp',
     'jit/RangeAnalysis.cpp',
     'jit/Recover.cpp',
     'jit/RegisterAllocator.cpp',
     'jit/RematerializedFrame.cpp',
     'jit/Safepoints.cpp',
     'jit/ScalarReplacement.cpp',
     'jit/shared/BaselineCompiler-shared.cpp',
     'jit/shared/CodeGenerator-shared.cpp',
@@ -580,26 +581,24 @@ elif CONFIG['JS_CODEGEN_MIPS32'] or CONF
         ]
         if CONFIG['JS_SIMULATOR_MIPS64']:
             UNIFIED_SOURCES += [
                 'jit/mips64/Simulator-mips64.cpp'
             ]
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     SOURCES += [
-        'jit/ExecutableAllocatorWin.cpp',
         'threading/windows/ConditionVariable.cpp',
         'threading/windows/MutexImpl.cpp',
         'threading/windows/Thread.cpp',
     ]
     # _CRT_RAND_S must be #defined before #including stdlib.h to get rand_s()
     DEFINES['_CRT_RAND_S'] = True
 else:
     SOURCES += [
-        'jit/ExecutableAllocatorPosix.cpp',
         'threading/posix/ConditionVariable.cpp',
         'threading/posix/MutexImpl.cpp',
         'threading/posix/Thread.cpp',
     ]
 
 if CONFIG['JS_HAS_CTYPES']:
     SOURCES += [
         'ctypes/CTypes.cpp',
--- a/js/src/vm/Initialization.cpp
+++ b/js/src/vm/Initialization.cpp
@@ -99,17 +99,18 @@ JS::detail::InitWithFailureDiagnostic(bo
     RETURN_IF_FAIL(js::oom::InitThreadType());
     js::oom::SetThreadType(js::oom::THREAD_TYPE_MAIN);
 #endif
 
     RETURN_IF_FAIL(js::Mutex::Init());
 
     RETURN_IF_FAIL(js::wasm::InitInstanceStaticData());
 
-    js::jit::ExecutableAllocator::initStatic();
+    js::gc::InitMemorySubsystem(); // Ensure gc::SystemPageSize() works.
+    RETURN_IF_FAIL(js::jit::InitProcessExecutableMemory());
 
     MOZ_ALWAYS_TRUE(js::MemoryProtectionExceptionHandler::install());
 
     RETURN_IF_FAIL(js::jit::InitializeIon());
 
     RETURN_IF_FAIL(js::InitDateTimeState());
 
 #if EXPOSE_INTL_API
@@ -172,17 +173,17 @@ JS_ShutDown(void)
 
 #if EXPOSE_INTL_API
     u_cleanup();
 #endif // EXPOSE_INTL_API
 
     js::FinishDateTimeState();
 
     if (!JSRuntime::hasLiveRuntimes())
-        js::jit::AssertAllocatedExecutableBytesIsZero();
+        js::jit::ReleaseProcessExecutableMemory();
 
     libraryInitState = InitState::ShutDown;
 }
 
 JS_PUBLIC_API(bool)
 JS_SetICUMemoryFunctions(JS_ICUAllocFn allocFn, JS_ICUReallocFn reallocFn, JS_ICUFreeFn freeFn)
 {
     MOZ_ASSERT(libraryInitState == InitState::Uninitialized,
--- a/js/src/vm/MutexIDs.h
+++ b/js/src/vm/MutexIDs.h
@@ -34,16 +34,17 @@
   _(ShellOffThreadState,         500) \
   _(SimulatorCacheLock,          500) \
   _(Arm64SimulatorLock,          500) \
   _(IonSpewer,                   500) \
   _(PerfSpewer,                  500) \
   _(TraceLoggerThreadState,      500) \
   _(DateTimeInfoMutex,           500) \
   _(IcuTimeZoneStateMutex,       500) \
+  _(ProcessExecutableRegion,     500) \
                                       \
   _(TraceLoggerGraphState,       600)
 
 namespace js {
 namespace mutexid {
 
 #define DEFINE_MUTEX_ID(name, order)  \
 static const MutexId name { #name, order };
--- a/js/src/wasm/WasmCode.cpp
+++ b/js/src/wasm/WasmCode.cpp
@@ -58,22 +58,21 @@ static Atomic<uint32_t> wasmCodeAllocati
 static const uint32_t MaxWasmCodeAllocations = 16384;
 
 static uint8_t*
 AllocateCodeSegment(ExclusiveContext* cx, uint32_t totalLength)
 {
     if (wasmCodeAllocations >= MaxWasmCodeAllocations)
         return nullptr;
 
-    // Allocate RW memory. DynamicallyLinkModule will reprotect the code as RX.
-    unsigned permissions =
-        ExecutableAllocator::initialProtectionFlags(ExecutableAllocator::Writable);
+    // codeLength is a multiple of the system's page size, but not necessarily
+    // a multiple of ExecutableCodePageSize.
+    totalLength = JS_ROUNDUP(totalLength, ExecutableCodePageSize);
 
-    void* p = AllocateExecutableMemory(totalLength, permissions, "wasm-code-segment",
-                                       gc::SystemPageSize());
+    void* p = AllocateExecutableMemory(totalLength, ProtectionSetting::Writable);
     if (!p) {
         ReportOutOfMemory(cx);
         return nullptr;
     }
 
     wasmCodeAllocations++;
     return (uint8_t*)p;
 }
@@ -256,17 +255,20 @@ CodeSegment::~CodeSegment()
 {
     if (!bytes_)
         return;
 
     MOZ_ASSERT(wasmCodeAllocations > 0);
     wasmCodeAllocations--;
 
     MOZ_ASSERT(totalLength() > 0);
-    DeallocateExecutableMemory(bytes_, totalLength(), gc::SystemPageSize());
+
+    // Match AllocateCodeSegment.
+    uint32_t size = JS_ROUNDUP(totalLength(), ExecutableCodePageSize);
+    DeallocateExecutableMemory(bytes_, size);
 }
 
 void
 CodeSegment::onMovingGrow(uint8_t* prevMemoryBase, const Metadata& metadata, ArrayBufferObject& buffer)
 {
     AutoWritableJitCode awjc(base(), codeLength());
     AutoFlushICache afc("CodeSegment::onMovingGrow");
     AutoFlushICache::setRange(uintptr_t(base()), codeLength());