diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp index 095cc41cc47..7963b57fddd 100644 --- a/dom/base/nsJSEnvironment.cpp +++ b/dom/base/nsJSEnvironment.cpp @@ -2971,6 +2971,16 @@ AsmJSCacheOpenEntryForWrite(JS::Handle aGlobal, aHandle); } +static void +OnLargeAllocationFailure() +{ + nsCOMPtr os = + mozilla::services::GetObserverService(); + if (os) { + os->NotifyObservers(nullptr, "memory-pressure", MOZ_UTF16("heap-minimize")); + } +} + static NS_DEFINE_CID(kDOMScriptObjectFactoryCID, NS_DOM_SCRIPT_OBJECT_FACTORY_CID); void @@ -3027,6 +3037,8 @@ nsJSContext::EnsureStatics() }; JS::SetAsmJSCacheOps(sRuntime, &asmJSCacheOps); + JS::SetLargeAllocationFailureCallback(sRuntime, OnLargeAllocationFailure); + // Set these global xpconnect options... Preferences::RegisterCallbackAndCall(ReportAllJSExceptionsPrefChangedCallback, "dom.report_all_js_exceptions"); diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 293f9f5447e..a9c5ab3c8c1 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -6297,3 +6297,9 @@ JSAutoByteString::encodeLatin1(ExclusiveContext *cx, JSString *str) mBytes = LossyTwoByteCharsToNewLatin1CharsZ(cx, linear->range()).c_str(); return mBytes; } + +JS_PUBLIC_API(void) +JS::SetLargeAllocationFailureCallback(JSRuntime *rt, JS::LargeAllocationFailureCallback lafc) +{ + rt->largeAllocationFailureCallback = lafc; +} diff --git a/js/src/jsapi.h b/js/src/jsapi.h index eceb4f4c734..aec0a2fc5f5 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -4919,6 +4919,20 @@ class MOZ_STACK_CLASS JS_PUBLIC_API(ForOfIterator) { bool materializeArrayIterator(); }; + +/* + * If a large allocation fails, the JS engine may call the large-allocation- + * failure callback, if set, to allow the embedding to flush caches, possibly + * perform shrinking GCs, etc. to make some room so that the allocation will + * succeed if retried. + */ + +typedef void +(* LargeAllocationFailureCallback)(); + +extern JS_PUBLIC_API(void) +SetLargeAllocationFailureCallback(JSRuntime *rt, LargeAllocationFailureCallback afc); + } /* namespace JS */ #endif /* jsapi_h */ diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp index 1ef33f30af4..232df034282 100644 --- a/js/src/vm/Runtime.cpp +++ b/js/src/vm/Runtime.cpp @@ -302,10 +302,11 @@ JSRuntime::JSRuntime(JSUseHelperThreads useHelperThreads) useHelperThreads_(useHelperThreads), parallelIonCompilationEnabled_(true), parallelParsingEnabled_(true), - isWorkerRuntime_(false) + isWorkerRuntime_(false), #ifdef DEBUG - , enteredPolicy(nullptr) + enteredPolicy(nullptr), #endif + largeAllocationFailureCallback(nullptr) { liveRuntimesCount++; diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h index bc62d712b8d..6031436ca4e 100644 --- a/js/src/vm/Runtime.h +++ b/js/src/vm/Runtime.h @@ -1788,6 +1788,34 @@ struct JSRuntime : public JS::shadow::Runtime, public: js::AutoEnterPolicy *enteredPolicy; #endif + + /* + * These variations of malloc/calloc/realloc will call the + * large-allocation-failure callback on OOM and retry the allocation. + */ + JS::LargeAllocationFailureCallback largeAllocationFailureCallback; + + static const unsigned LARGE_ALLOCATION = 25 * 1024 * 1024; + + void *callocCanGC(size_t bytes) { + void *p = calloc_(bytes); + if (MOZ_LIKELY(!!p)) + return p; + if (!largeAllocationFailureCallback || bytes < LARGE_ALLOCATION) + return nullptr; + largeAllocationFailureCallback(); + return js_calloc(bytes); + } + + void *reallocCanGC(void *p, size_t bytes) { + void *p2 = realloc_(p, bytes); + if (MOZ_LIKELY(!!p2)) + return p2; + if (!largeAllocationFailureCallback || bytes < LARGE_ALLOCATION) + return nullptr; + largeAllocationFailureCallback(); + return js_realloc(p, bytes); + } }; namespace js { diff --git a/js/src/vm/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp index 7b0632f41c5..210ba706a3b 100644 --- a/js/src/vm/TypedArrayObject.cpp +++ b/js/src/vm/TypedArrayObject.cpp @@ -241,14 +241,14 @@ AllocateArrayBufferContents(JSContext *maybecx, uint32_t nbytes, void *oldptr = ObjectElements *oldheader = static_cast(oldptr); uint32_t oldnbytes = ArrayBufferObject::headerInitializedLength(oldheader); - void *p = maybecx ? maybecx->realloc_(oldptr, size) : js_realloc(oldptr, size); + void *p = maybecx ? maybecx->runtime()->reallocCanGC(oldptr, size) : js_realloc(oldptr, size); newheader = static_cast(p); // if we grew the array, we need to set the new bytes to 0 if (newheader && nbytes > oldnbytes) memset(reinterpret_cast(newheader->elements()) + oldnbytes, 0, nbytes - oldnbytes); } else { - void *p = maybecx ? maybecx->calloc_(size) : js_calloc(size); + void *p = maybecx ? maybecx->runtime()->callocCanGC(size) : js_calloc(size); newheader = static_cast(p); } if (!newheader) { @@ -257,7 +257,6 @@ AllocateArrayBufferContents(JSContext *maybecx, uint32_t nbytes, void *oldptr = return nullptr; } - // we rely on this being correct ArrayBufferObject::updateElementsHeader(newheader, nbytes); return newheader;