Bug 1334933 - Allocate executable pages from a pre-reserved range. r=luke, a=gchang
authorJan de Mooij <jdemooij@mozilla.com>
Sat, 04 Feb 2017 11:03:58 +0100
changeset 312664 347c10e4d6d189d9d26e6e8c8fa3e360e6928d53
parent 312663 3ed841c8862c3f3100856871cc1d4b66503fdbf6
child 312665 fe4a2cda54adf386f7e29807d400806185d8b6e0
push id420
push userryanvm@gmail.com
push dateFri, 10 Feb 2017 23:01:50 +0000
treeherdermozilla-esr45@fe4a2cda54ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke, gchang
bugs1334933, 1337561
milestone45.7.1
Bug 1334933 - Allocate executable pages from a pre-reserved range. r=luke, a=gchang * * * 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/asmjs/AsmJSModule.cpp
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/Lowering.cpp
js/src/jit/ProcessExecutableMemory.cpp
js/src/jit/ProcessExecutableMemory.h
js/src/jsgc.cpp
js/src/moz.build
js/src/shell/js.cpp
js/src/vm/Initialization.cpp
--- a/js/src/asmjs/AsmJSModule.cpp
+++ b/js/src/asmjs/AsmJSModule.cpp
@@ -59,22 +59,21 @@ using mozilla::PodCopy;
 using mozilla::PodEqual;
 using mozilla::PodZero;
 using mozilla::Swap;
 using JS::GenericNaN;
 
 static uint8_t*
 AllocateExecutableMemory(ExclusiveContext* cx, size_t bytes)
 {
-    // On most platforms, this will allocate RWX memory. On iOS, or when
-    // --non-writable-jitcode is used, this will allocate RW memory. In this
-    // case, DynamicallyLinkModule will reprotect the code as RX.
-    unsigned permissions =
-        ExecutableAllocator::initialProtectionFlags(ExecutableAllocator::Writable);
-    void* p = AllocateExecutableMemory(bytes, permissions, "asm-js-code", AsmJSPageSize);
+    // bytes is a multiple of the system's page size, but not necessarily
+    // a multiple of ExecutableCodePageSize.
+    bytes = JS_ROUNDUP(bytes, ExecutableCodePageSize);
+
+    void* p = AllocateExecutableMemory(bytes, ProtectionSetting::Writable);
     if (!p)
         ReportOutOfMemory(cx);
     return (uint8_t*)p;
 }
 
 AsmJSModule::AsmJSModule(ScriptSource* scriptSource, uint32_t srcStart, uint32_t srcBodyStart,
                          bool strict, bool canUseSignalHandlers)
   : srcStart_(srcStart),
@@ -117,17 +116,18 @@ AsmJSModule::~AsmJSModule()
             AsmJSModule::ExitDatum& exitDatum = exit(i).datum(*this);
             if (!exitDatum.baselineScript)
                 continue;
 
             jit::DependentAsmJSModuleExit exit(this, i);
             exitDatum.baselineScript->removeDependentAsmJSModule(exit);
         }
 
-        DeallocateExecutableMemory(code_, pod.totalBytes_, AsmJSPageSize);
+        uint32_t size = JS_ROUNDUP(pod.totalBytes_, ExecutableCodePageSize);
+        DeallocateExecutableMemory(code_, size);
     }
 
     if (prevLinked_)
         *prevLinked_ = nextLinked_;
     if (nextLinked_)
         nextLinked_->prevLinked_ = prevLinked_;
 }
 
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -918,17 +918,17 @@ class GCRuntime
     void collect(bool nonincrementalByAPI, SliceBudget budget, JS::gcreason::Reason reason);
     bool gcCycle(bool nonincrementalByAPI, SliceBudget& budget, JS::gcreason::Reason reason);
     void incrementalCollectSlice(SliceBudget& budget, JS::gcreason::Reason reason);
 
     void pushZealSelectedObjects();
     void purgeRuntime();
     bool beginMarkPhase(JS::gcreason::Reason reason);
     bool shouldPreserveJITCode(JSCompartment* comp, int64_t currentTime,
-                               JS::gcreason::Reason reason);
+                               JS::gcreason::Reason reason, bool canAllocateMoreCode);
     void bufferGrayRoots();
     void markCompartments();
     IncrementalProgress drainMarkStack(SliceBudget& sliceBudget, gcstats::Phase phase);
     template <class CompartmentIterT> void markWeakReferences(gcstats::Phase phase);
     void markWeakReferencesInCurrentGroup(gcstats::Phase phase);
     template <class ZoneIterT, class CompartmentIterT> void markGrayReferences(gcstats::Phase phase);
     void markBufferedGrayRoots(JS::Zone* zone);
     void markGrayReferencesInCurrentGroup(gcstats::Phase phase);
--- a/js/src/irregexp/RegExpEngine.cpp
+++ b/js/src/irregexp/RegExpEngine.cpp
@@ -1728,17 +1728,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,61 +22,30 @@
  * 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 "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);
 
     m_allocator->releasePoolPages(this);
 }
 
-/* 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(JS_CPU_X64) && defined(XP_WIN)
-        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;
@@ -85,67 +54,21 @@ ExecutableAllocator::addSizeOfCode(JS::C
             sizes->unused   += pool->m_allocation.size - pool->m_ionCodeBytes
                                                        - pool->m_baselineCodeBytes
                                                        - pool->m_regexpCodeBytes
                                                        - pool->m_otherCodeBytes;
         }
     }
 }
 
-#if TARGET_OS_IPHONE
-bool ExecutableAllocator::nonWritableJitCode = true;
-#else
-bool ExecutableAllocator::nonWritableJitCode = false;
-#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 = 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
@@ -34,16 +34,17 @@
 #include <limits>
 #include <stddef.h> // for ptrdiff_t
 
 #include "jsalloc.h"
 
 #include "jit/arm/Simulator-arm.h"
 #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"
 
 #ifdef JS_CPU_SPARC
 #ifdef __linux__  // bugzilla 502369
 static void sync_instruction_memory(caddr_t v, u_int len)
 {
@@ -182,18 +183,16 @@ namespace jit {
 };
 
 class ExecutableAllocator
 {
     typedef void (*DestroyCallback)(void* addr, size_t size);
     DestroyCallback destroyCallback;
 
   public:
-    enum ProtectionSetting { Writable, Executable };
-
     ExecutableAllocator()
       : destroyCallback(nullptr)
     {
         MOZ_ASSERT(m_smallPools.empty());
     }
 
     ~ExecutableAllocator()
     {
@@ -254,24 +253,17 @@ class ExecutableAllocator
     }
 
     void addSizeOfCode(JS::CodeSizes* sizes) const;
 
     void setDestroyCallback(DestroyCallback destroyCallback) {
         this->destroyCallback = destroyCallback;
     }
 
-    static void initStatic();
-
-    static bool nonWritableJitCode;
-
   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)
     {
         // Something included via windows.h defines a macro with this name,
         // which causes the function below to fail to compile.
         #ifdef _MSC_VER
         # undef max
@@ -288,17 +280,17 @@ class ExecutableAllocator
     }
 
     // On OOM, this will return an Allocation where pages is nullptr.
     ExecutablePool::Allocation systemAlloc(size_t n);
     static void systemRelease(const ExecutablePool::Allocation& alloc);
 
     ExecutablePool* createPool(size_t n)
     {
-        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)
@@ -334,21 +326,21 @@ class ExecutableAllocator
                 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))
@@ -375,28 +367,22 @@ class ExecutableAllocator
         }
 
         // Pass ownership to the caller.
         return pool;
     }
 
     static void makeWritable(void* start, size_t size)
     {
-        if (nonWritableJitCode)
-            reprotectRegion(start, size, Writable);
     }
 
     static void makeExecutable(void* start, size_t size)
     {
-        if (nonWritableJitCode)
-            reprotectRegion(start, size, Executable);
     }
 
-    static unsigned initialProtectionFlags(ProtectionSetting protection);
-
 #if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
     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)
     {
         js::jit::Simulator::FlushICache(code, size);
@@ -459,58 +445,25 @@ class ExecutableAllocator
         sync_instruction_memory((caddr_t)code, size);
     }
 #endif
 
   private:
     ExecutableAllocator(const ExecutableAllocator&) = delete;
     void operator=(const ExecutableAllocator&) = delete;
 
-    static void reprotectRegion(void*, size_t, 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,122 +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;
-
-void
-ExecutableAllocator::reprotectRegion(void* start, size_t size, ProtectionSetting setting)
-{
-    MOZ_ASSERT(nonWritableJitCode);
-    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);
-
-    mprotect(pageStart, size, (setting == Writable) ? FLAGS_RW : FLAGS_RX);
-}
-
-/* static */ unsigned
-ExecutableAllocator::initialProtectionFlags(ProtectionSetting protection)
-{
-    if (!nonWritableJitCode)
-        return FLAGS_RW | FLAGS_RX;
-
-    return (protection == Writable) ? FLAGS_RW : FLAGS_RX;
-}
deleted file mode 100644
--- a/js/src/jit/ExecutableAllocatorWin.cpp
+++ /dev/null
@@ -1,281 +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/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 JS_CPU_X64
-    static const uintptr_t base = 0x0000000080000000;
-    static const uintptr_t mask = 0x000003ffffff0000;
-#elif defined(JS_CPU_X86)
-    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 JS_CPU_X64
-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)
-{
-    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))
-        return false;
-
-    return RtlAddFunctionTable(&r->runtimeFunction, 1, reinterpret_cast<DWORD64>(p));
-}
-
-static void
-UnregisterExecutableMemory(void* p, size_t bytes, size_t pageSize)
-{
-    ExceptionHandlerRecord* r = reinterpret_cast<ExceptionHandlerRecord*>(p);
-    RtlDeleteFunctionTable(&r->runtimeFunction);
-}
-#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 JS_CPU_X64
-    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 JS_CPU_X64
-    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 JS_CPU_X64
-    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);
-}
-
-void
-ExecutableAllocator::reprotectRegion(void* start, size_t size, ProtectionSetting setting)
-{
-    MOZ_ASSERT(nonWritableJitCode);
-    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;
-    if (!VirtualProtect(pageStart, size, flags, &oldProtect))
-        MOZ_CRASH();
-}
-
-/* static */ unsigned
-ExecutableAllocator::initialProtectionFlags(ProtectionSetting protection)
-{
-    if (!nonWritableJitCode)
-        return PAGE_EXECUTE_READWRITE;
-
-    return (protection == Writable) ? PAGE_READWRITE : PAGE_EXECUTE_READ;
-}
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -2305,19 +2305,17 @@ void
 LIRGenerator::visitInterruptCheck(MInterruptCheck* ins)
 {
     // Implicit interrupt checks require asm.js signal handlers to be installed.
     // They also require writable JIT code: reprotecting in patchIonBackedges
     // would be expensive and using AutoWritableJitCode in the signal handler
     // is complicated because there could be another AutoWritableJitCode on the
     // stack.
     LInstructionHelper<0, 0, 0>* lir;
-    if (GetJitContext()->runtime->canUseSignalHandlers() &&
-        !ExecutableAllocator::nonWritableJitCode)
-    {
+    if (GetJitContext()->runtime->canUseSignalHandlers()) {
         lir = new(alloc()) LInterruptCheckImplicit();
     } else {
         lir = new(alloc()) LInterruptCheck();
     }
     add(lir, ins);
     assignSafepoint(lir, ins);
 }
 
new file mode 100644
--- /dev/null
+++ b/js/src/jit/ProcessExecutableMemory.cpp
@@ -0,0 +1,607 @@
+/* -*- 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 "jslock.h"
+#include "jsmath.h"
+#include "jsutil.h"
+#include "jswin.h"
+
+#include <errno.h>
+
+#include "gc/Memory.h"
+
+#ifdef XP_WIN
+# 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();
+
+    bool success = RtlAddFunctionTable(&r->runtimeFunction, 1, reinterpret_cast<DWORD64>(p));
+
+    return success;
+}
+
+static void
+UnregisterExecutableMemory(void* p, size_t bytes, size_t pageSize)
+{
+    ExceptionHandlerRecord* r = reinterpret_cast<ExceptionHandlerRecord*>(p);
+
+    RtlDeleteFunctionTable(&r->runtimeFunction);
+}
+# 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)
+{
+    return PAGE_EXECUTE_READWRITE;
+}
+
+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)
+{
+    return PROT_READ | PROT_WRITE | PROT_EXEC;
+}
+
+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.
+    PRLock* 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_(nullptr),
+        pagesAllocated_(0),
+        cursor_(0),
+        rng_(),
+        pages_()
+    {}
+
+    MOZ_MUST_USE bool init() {
+        pages_.init();
+
+        MOZ_RELEASE_ASSERT(!initialized());
+        MOZ_RELEASE_ASSERT(gc::SystemPageSize() <= ExecutableCodePageSize);
+
+        lock_ = PR_NewLock();
+        if (!lock_)
+            return false;
+
+        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(lock_);
+        PR_DestroyLock(lock_);
+        lock_ = nullptr;
+        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;
+    {
+        PR_Lock(lock_);
+        MOZ_ASSERT(pagesAllocated_ <= MaxCodePages);
+
+        // Check if we have enough pages available.
+        if (pagesAllocated_ + numPages >= MaxCodePages) {
+            PR_Unlock(lock_);
+            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;
+        }
+        PR_Unlock(lock_);
+        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);
+
+    PR_Lock(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;
+
+    PR_Unlock(lock_);
+}
+
+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;
+}
new file mode 100644
--- /dev/null
+++ b/js/src/jit/ProcessExecutableMemory.h
@@ -0,0 +1,46 @@
+/* -*- 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,
+};
+
+// 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
@@ -3791,20 +3791,22 @@ GCRuntime::purgeRuntime()
     rt->evalCache.clear();
 
     if (!rt->hasActiveCompilations())
         rt->parseMapPool().purgeAll();
 }
 
 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)
@@ -3941,25 +3943,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 = false;
-        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. So if there are any
      * zones that are not being collected, we are not allowed to collect
      * atoms. Otherwise, the non-collected zones could contain pointers
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -231,16 +231,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',
@@ -525,25 +526,18 @@ elif CONFIG['JS_CODEGEN_MIPS32'] or CONF
             'jit/mips64/Trampoline-mips64.cpp',
         ]
         if CONFIG['JS_SIMULATOR_MIPS64']:
             UNIFIED_SOURCES += [
                 'jit/mips64/Simulator-mips64.cpp'
             ]
 
 if CONFIG['OS_ARCH'] == 'WINNT':
-    SOURCES += [
-        'jit/ExecutableAllocatorWin.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',
-    ]
 
 if CONFIG['JS_HAS_CTYPES']:
     SOURCES += [
         'ctypes/CTypes.cpp',
         'ctypes/Library.cpp',
     ]
     if not CONFIG['MOZ_NATIVE_FFI']:
         LOCAL_INCLUDES += [
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -6762,21 +6762,16 @@ main(int argc, char** argv, char** envp)
 #ifdef DEBUG
     /*
      * Process OOM options as early as possible so that we can observe as many
      * allocations as possible.
      */
     OOM_printAllocationCount = op.getBoolOption('O');
 #endif
 
-    if (op.getBoolOption("non-writable-jitcode")) {
-        js::jit::ExecutableAllocator::nonWritableJitCode = true;
-        PropagateFlagToNestedShells("--non-writable-jitcode");
-    }
-
 #ifdef JS_CODEGEN_X86
     if (op.getBoolOption("no-fpu"))
         js::jit::CPUInfo::SetFloatingPointDisabled();
 #endif
 
 #if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
     if (op.getBoolOption("no-sse3")) {
         js::jit::CPUInfo::SetSSE3Disabled();
--- a/js/src/vm/Initialization.cpp
+++ b/js/src/vm/Initialization.cpp
@@ -78,17 +78,19 @@ JS_Init(void)
         return false;
 
 #if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
     if (!js::oom::InitThreadType())
         return false;
     js::oom::SetThreadType(js::oom::THREAD_TYPE_MAIN);
 #endif
 
-    js::jit::ExecutableAllocator::initStatic();
+    js::gc::InitMemorySubsystem(); // Ensure gc::SystemPageSize() works.
+    if (!js::jit::InitProcessExecutableMemory())
+        return false;
 
     if (!js::jit::InitializeIon())
         return false;
 
     js::DateTimeInfo::init();
 
 #if EXPOSE_INTL_API
     UErrorCode err = U_ZERO_ERROR;
@@ -142,17 +144,17 @@ JS_ShutDown(void)
     // to do it only when PRMJ_Now is eventually called.
     PRMJ_NowShutdown();
 
 #if EXPOSE_INTL_API
     u_cleanup();
 #endif // EXPOSE_INTL_API
 
     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,