Bug 1331606 - Avoid OOM crashes when we reach the executable code limit. r=luke, a=ritu
CLOSED TREE
--- a/js/src/irregexp/RegExpEngine.cpp
+++ b/js/src/irregexp/RegExpEngine.cpp
@@ -27,16 +27,17 @@
// 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 "irregexp/RegExpEngine.h"
#include "irregexp/NativeRegExpMacroAssembler.h"
#include "irregexp/RegExpMacroAssembler.h"
+#include "jit/ExecutableAllocator.h"
#include "jit/JitCommon.h"
using namespace js;
using namespace js::irregexp;
using mozilla::ArrayLength;
using mozilla::DebugOnly;
using mozilla::Maybe;
@@ -1725,17 +1726,20 @@ irregexp::CompilePattern(JSContext* cx,
return RegExpCode();
}
Maybe<jit::JitContext> ctx;
Maybe<NativeRegExpMacroAssembler> native_assembler;
Maybe<InterpretedRegExpMacroAssembler> interpreted_assembler;
RegExpMacroAssembler* assembler;
- if (IsNativeRegExpEnabled(cx) && !force_bytecode) {
+ if (IsNativeRegExpEnabled(cx) &&
+ !force_bytecode &&
+ jit::CanLikelyAllocateMoreExecutableMemory())
+ {
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();
} else {
--- a/js/src/jit/BaselineJIT.cpp
+++ b/js/src/jit/BaselineJIT.cpp
@@ -309,22 +309,27 @@ CanEnterBaselineJIT(JSContext* cx, Handl
return Method_Skipped;
if (script->length() > BaselineScript::MAX_JSSCRIPT_LENGTH)
return Method_CantCompile;
if (script->nslots() > BaselineScript::MAX_JSSCRIPT_SLOTS)
return Method_CantCompile;
+ if (script->hasBaselineScript())
+ return Method_Compiled;
+
+ // Check this before calling ensureJitCompartmentExists, so we're less
+ // likely to report OOM in JSRuntime::createJitRuntime.
+ if (!CanLikelyAllocateMoreExecutableMemory())
+ return Method_Skipped;
+
if (!cx->compartment()->ensureJitCompartmentExists(cx))
return Method_Error;
- if (script->hasBaselineScript())
- return Method_Compiled;
-
// Check script warm-up counter.
if (script->incWarmUpCounter() <= JitOptions.baselineWarmUpThreshold)
return Method_Skipped;
// Frames can be marked as debuggee frames independently of its underlying
// script being a debuggee script, e.g., when performing
// Debugger.Frame.prototype.eval.
return BaselineCompile(cx, script, osrFrame && osrFrame->isDebuggee());
--- a/js/src/jit/ExecutableAllocator.cpp
+++ b/js/src/jit/ExecutableAllocator.cpp
@@ -94,19 +94,19 @@ ExecutableAllocator::addSizeOfCode(JS::C
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 = 128 * 1024 * 1024;
+static const size_t MaxCodeBytesPerProcess = 160 * 1024 * 1024;
#else
-static const size_t MaxCodeBytesPerProcess = 512 * 1024 * 1024;
+static const size_t MaxCodeBytesPerProcess = 640 * 1024 * 1024;
#endif
static mozilla::Atomic<size_t> allocatedExecutableBytes(0);
bool
js::jit::AddAllocatedExecutableBytes(size_t bytes)
{
MOZ_ASSERT(allocatedExecutableBytes <= MaxCodeBytesPerProcess);
@@ -134,8 +134,18 @@ js::jit::SubAllocatedExecutableBytes(siz
allocatedExecutableBytes -= bytes;
}
void
js::jit::AssertAllocatedExecutableBytesIsZero()
{
MOZ_ASSERT(allocatedExecutableBytes == 0);
}
+
+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
@@ -496,12 +496,21 @@ 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 */
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -2404,16 +2404,21 @@ Compile(JSContext* cx, HandleScript scri
return status;
}
bool recompile = false;
OptimizationLevel optimizationLevel = GetOptimizationLevel(script, osrPc);
if (optimizationLevel == Optimization_DontCompile)
return Method_Skipped;
+ if (!CanLikelyAllocateMoreExecutableMemory()) {
+ script->resetWarmUpCounter();
+ return Method_Skipped;
+ }
+
if (script->hasIonScript()) {
IonScript* scriptIon = script->ionScript();
if (!scriptIon->method())
return Method_CantCompile;
// Don't recompile/overwrite higher optimized code,
// with a lower optimization level.
if (optimizationLevel <= scriptIon->optimizationLevel() && !forceRecompile)
--- a/js/src/jit/IonAnalysis.cpp
+++ b/js/src/jit/IonAnalysis.cpp
@@ -3668,16 +3668,19 @@ jit::AnalyzeNewScriptDefiniteProperties(
return true;
Vector<PropertyName*> accessedProperties(cx);
LifoAlloc alloc(TempAllocator::PreferredLifoChunkSize);
TempAllocator temp(&alloc);
JitContext jctx(cx, &temp);
+ if (!jit::CanLikelyAllocateMoreExecutableMemory())
+ return true;
+
if (!cx->compartment()->ensureJitCompartmentExists(cx))
return false;
if (!script->hasBaselineScript()) {
MethodStatus status = BaselineCompile(cx, script);
if (status == Method_Error)
return false;
if (status != Method_Compiled)
@@ -3897,16 +3900,19 @@ jit::AnalyzeArgumentsUsage(JSContext* cx
if (!script->ensureHasTypes(cx))
return false;
LifoAlloc alloc(TempAllocator::PreferredLifoChunkSize);
TempAllocator temp(&alloc);
JitContext jctx(cx, &temp);
+ if (!jit::CanLikelyAllocateMoreExecutableMemory())
+ return true;
+
if (!cx->compartment()->ensureJitCompartmentExists(cx))
return false;
MIRGraph graph(&temp);
InlineScriptTree* inlineScriptTree = InlineScriptTree::New(&temp, nullptr, nullptr, script);
if (!inlineScriptTree) {
ReportOutOfMemory(cx);
return false;
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -154,16 +154,22 @@ jit::JitRuntime*
JSRuntime::createJitRuntime(JSContext* cx)
{
// The shared stubs are created in the atoms compartment, which may be
// accessed by other threads with an exclusive context.
AutoLockForExclusiveAccess atomsLock(cx);
MOZ_ASSERT(!jitRuntime_);
+ if (!CanLikelyAllocateMoreExecutableMemory()) {
+ // Report OOM instead of potentially hitting the MOZ_CRASH below.
+ ReportOutOfMemory(cx);
+ return nullptr;
+ }
+
jit::JitRuntime* jrt = cx->new_<jit::JitRuntime>();
if (!jrt)
return nullptr;
// Protect jitRuntime_ from being observed (by InterruptRunningJitCode)
// while it is being initialized. Unfortunately, initialization depends on
// jitRuntime_ being non-null, so we can't just wait to assign jitRuntime_.
JitRuntime::AutoMutateBackedges amb(jrt);
--- a/js/src/vm/UnboxedObject.cpp
+++ b/js/src/vm/UnboxedObject.cpp
@@ -2,16 +2,17 @@
* 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 "vm/UnboxedObject-inl.h"
#include "jit/BaselineIC.h"
+#include "jit/ExecutableAllocator.h"
#include "jit/JitCommon.h"
#include "jit/Linker.h"
#include "jsobjinlines.h"
#include "gc/Nursery-inl.h"
#include "jit/MacroAssembler-inl.h"
#include "vm/Shape-inl.h"
@@ -693,17 +694,18 @@ UnboxedPlainObject::createWithProperties
for (size_t i = 0; i < layout.properties().length(); i++) {
if (!obj->setValue(cx, layout.properties()[i], properties[i].value))
return NewPlainObjectWithProperties(cx, properties, layout.properties().length(), newKind);
}
#ifndef JS_CODEGEN_NONE
if (cx->isJSContext() &&
!layout.constructorCode() &&
- cx->asJSContext()->runtime()->jitSupportsFloatingPoint)
+ cx->asJSContext()->runtime()->jitSupportsFloatingPoint &&
+ jit::CanLikelyAllocateMoreExecutableMemory())
{
if (!UnboxedLayout::makeConstructorCode(cx->asJSContext(), group))
return nullptr;
}
#endif
return obj;
}