Bug 1410132 - Use a separate jemalloc arena for all SpiderMonkey malloc allocations r=njn r=nbp
authorJon Coppeard <jcoppeard@mozilla.com>
Thu, 02 Nov 2017 14:51:27 +0000
changeset 443979 cdabc4808a0fc6bc99475a7011b07d0f2df22727
parent 443978 69aedd61b682f6086a6a47b5616bbc9e1bbf1487
child 443980 919528a717da922e95f7788053af72c468db5efb
push id1618
push userCallek@gmail.com
push dateThu, 11 Jan 2018 17:45:48 +0000
treeherdermozilla-release@882ca853e05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnjn, nbp
bugs1410132
milestone58.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1410132 - Use a separate jemalloc arena for all SpiderMonkey malloc allocations r=njn r=nbp
config/check_spidermonkey_style.py
js/public/Utility.h
js/src/jsapi-tests/testHashTable.cpp
js/src/jsapi-tests/tests.cpp
js/src/jsapi-tests/tests.h
js/src/jsstr.cpp
js/src/jsutil.cpp
js/src/shell/js.cpp
js/src/vm/Initialization.cpp
js/src/wasm/WasmProcess.cpp
js/src/wasm/WasmProcess.h
--- a/config/check_spidermonkey_style.py
+++ b/config/check_spidermonkey_style.py
@@ -64,16 +64,17 @@ included_inclnames_to_ignore = set([
     'double-conversion.h',      # strange MFBT case
     'javascript-trace.h',       # generated in $OBJDIR if HAVE_DTRACE is defined
     'frontend/ReservedWordsGenerated.h', # generated in $OBJDIR
     'gc/StatsPhasesGenerated.h',         # generated in $OBJDIR
     'gc/StatsPhasesGenerated.cpp',       # generated in $OBJDIR
     'jscustomallocator.h',      # provided by embedders;  allowed to be missing
     'js-config.h',              # generated in $OBJDIR
     'fdlibm.h',                 # fdlibm
+    'mozmemory.h',              # included without a path
     'pratom.h',                 # NSPR
     'prcvar.h',                 # NSPR
     'prerror.h',                # NSPR
     'prinit.h',                 # NSPR
     'prio.h',                   # NSPR
     'private/pprio.h',          # NSPR
     'prlink.h',                 # NSPR
     'prlock.h',                 # NSPR
--- a/js/public/Utility.h
+++ b/js/public/Utility.h
@@ -21,16 +21,18 @@
 
 #ifdef JS_OOM_DO_BACKTRACES
 #include <execinfo.h>
 #include <stdio.h>
 #endif
 
 #include "jstypes.h"
 
+#include "mozmemory.h"
+
 /* The public JS engine namespace. */
 namespace JS {}
 
 /* The mozilla-shared reusable template/utility namespace. */
 namespace mozilla {}
 
 /* The private JS engine namespace. */
 namespace js {}
@@ -359,55 +361,65 @@ struct MOZ_RAII JS_PUBLIC_DATA(AutoEnter
 
     bool oomEnabled_;
     int64_t oomAfter_;
 #endif
 };
 
 } /* namespace js */
 
+// Malloc allocation.
+
+namespace js {
+
+extern JS_PUBLIC_DATA(arena_id_t) MallocArena;
+
+extern void InitMallocAllocator();
+extern void ShutDownMallocAllocator();
+
+} /* namespace js */
+
 static inline void* js_malloc(size_t bytes)
 {
     JS_OOM_POSSIBLY_FAIL();
-    return malloc(bytes);
+    return moz_arena_malloc(js::MallocArena, bytes);
 }
 
 static inline void* js_calloc(size_t bytes)
 {
     JS_OOM_POSSIBLY_FAIL();
-    return calloc(bytes, 1);
+    return moz_arena_calloc(js::MallocArena, bytes, 1);
 }
 
 static inline void* js_calloc(size_t nmemb, size_t size)
 {
     JS_OOM_POSSIBLY_FAIL();
-    return calloc(nmemb, size);
+    return moz_arena_calloc(js::MallocArena, nmemb, size);
 }
 
 static inline void* js_realloc(void* p, size_t bytes)
 {
     // realloc() with zero size is not portable, as some implementations may
     // return nullptr on success and free |p| for this.  We assume nullptr
     // indicates failure and that |p| is still valid.
     MOZ_ASSERT(bytes != 0);
 
     JS_OOM_POSSIBLY_FAIL();
-    return realloc(p, bytes);
+    return moz_arena_realloc(js::MallocArena, p, bytes);
 }
 
 static inline void js_free(void* p)
 {
+    // TODO: This should call |moz_arena_free(js::MallocArena, p)| but we
+    // currently can't enforce that all memory freed here was allocated by
+    // js_malloc().
     free(p);
 }
 
-static inline char* js_strdup(const char* s)
-{
-    JS_OOM_POSSIBLY_FAIL();
-    return strdup(s);
-}
+JS_PUBLIC_API(char*) js_strdup(const char* s);
 #endif/* JS_USE_CUSTOM_ALLOCATOR */
 
 #include <new>
 
 /*
  * Low-level memory management in SpiderMonkey:
  *
  *  ** Do not use the standard malloc/free/realloc: SpiderMonkey allows these
--- a/js/src/jsapi-tests/testHashTable.cpp
+++ b/js/src/jsapi-tests/testHashTable.cpp
@@ -389,22 +389,23 @@ BEGIN_TEST(testHashMapLookupWithDefaultO
     return true;
 }
 
 END_TEST(testHashMapLookupWithDefaultOOM)
 #endif // defined(DEBUG)
 
 BEGIN_TEST(testHashTableMovableEnum)
 {
+    IntSet set;
     CHECK(set.init());
 
     // Exercise returning a hash table Enum object from a function.
 
     CHECK(set.put(1));
-    for (auto e = enumerateSet(); !e.empty(); e.popFront())
+    for (auto e = enumerateSet(set); !e.empty(); e.popFront())
         e.removeFront();
     CHECK(set.count() == 0);
 
     // Test moving an Enum object explicitly.
 
     CHECK(set.put(1));
     CHECK(set.put(2));
     CHECK(set.put(3));
@@ -420,16 +421,14 @@ BEGIN_TEST(testHashTableMovableEnum)
         e2.removeFront();
         e2.popFront();
     }
 
     CHECK(set.count() == 1);
     return true;
 }
 
-IntSet set;
-
-IntSet::Enum enumerateSet()
+IntSet::Enum enumerateSet(IntSet& set)
 {
     return IntSet::Enum(set);
 }
 
 END_TEST(testHashTableMovableEnum)
--- a/js/src/jsapi-tests/tests.cpp
+++ b/js/src/jsapi-tests/tests.cpp
@@ -40,16 +40,17 @@ void JSAPITest::uninit()
         JS_LeaveCompartment(cx, nullptr);
         global = nullptr;
     }
     if (cx) {
         JS_EndRequest(cx);
         destroyContext();
         cx = nullptr;
     }
+    msgs.clear();
 }
 
 bool JSAPITest::exec(const char* bytes, const char* filename, int lineno)
 {
     JS::RootedValue v(cx);
     JS::CompileOptions opts(cx);
     opts.setFileAndLine(filename, lineno);
     return JS::Evaluate(cx, opts, bytes, strlen(bytes), &v) ||
--- a/js/src/jsapi-tests/tests.h
+++ b/js/src/jsapi-tests/tests.h
@@ -28,16 +28,17 @@ class JSAPITestString {
   public:
     JSAPITestString() {}
     explicit JSAPITestString(const char* s) { *this += s; }
     JSAPITestString(const JSAPITestString& s) { *this += s; }
 
     const char* begin() const { return chars.begin(); }
     const char* end() const { return chars.end(); }
     size_t length() const { return chars.length(); }
+    void clear() { chars.clearAndFree(); }
 
     JSAPITestString& operator +=(const char* s) {
         if (!chars.append(s, strlen(s)))
             abort();
         return *this;
     }
 
     JSAPITestString& operator +=(const JSAPITestString& s) {
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -3863,17 +3863,22 @@ js::DuplicateString(JSContext* cx, const
         return ret;
     PodCopy(ret.get(), s, n);
     return ret;
 }
 
 UniqueChars
 js::DuplicateString(const char* s)
 {
-    return UniqueChars(js_strdup(s));
+    size_t n = strlen(s) + 1;
+    UniqueChars ret(js_pod_malloc<char>(n));
+    if (!ret)
+        return ret;
+    PodCopy(ret.get(), s, n);
+    return ret;
 }
 
 UniqueChars
 js::DuplicateString(const char* s, size_t n)
 {
     UniqueChars ret(js_pod_malloc<char>(n + 1));
     if (!ret)
         return nullptr;
@@ -3894,16 +3899,22 @@ js::DuplicateString(const char16_t* s, s
     UniqueTwoByteChars ret(js_pod_malloc<char16_t>(n + 1));
     if (!ret)
         return nullptr;
     PodCopy(ret.get(), s, n);
     ret[n] = 0;
     return ret;
 }
 
+JS_PUBLIC_API(char*)
+js_strdup(const char* s)
+{
+    return DuplicateString(s).release();
+}
+
 template <typename CharT>
 const CharT*
 js_strchr_limit(const CharT* s, char16_t c, const CharT* limit)
 {
     while (s < limit) {
         if (*s == c)
             return s;
         s++;
--- a/js/src/jsutil.cpp
+++ b/js/src/jsutil.cpp
@@ -165,16 +165,30 @@ ResetSimulatedInterrupt()
     maxInterruptChecks = UINT64_MAX;
     interruptCheckFailAlways = false;
 }
 
 } // namespace oom
 } // namespace js
 #endif // defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
 
+JS_PUBLIC_DATA(arena_id_t) js::MallocArena;
+
+void
+js::InitMallocAllocator()
+{
+    MallocArena = moz_create_arena();
+}
+
+void
+js::ShutDownMallocAllocator()
+{
+    moz_dispose_arena(MallocArena);
+}
+
 JS_PUBLIC_API(void)
 JS_Assert(const char* s, const char* file, int ln)
 {
     MOZ_ReportAssertionFailure(s, file, ln);
     MOZ_CRASH();
 }
 
 #ifdef __linux__
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -3988,16 +3988,18 @@ KillWorkerThreads(JSContext* cx)
             AutoLockWorkerThreads alwt;
             if (workerThreads.empty())
                 break;
             thread = workerThreads.popCopy();
         }
         thread->join();
     }
 
+    workerThreads.clearAndFree();
+
     js_delete(workerThreadsLock);
     workerThreadsLock = nullptr;
 
     js_delete(cooperationState);
     cooperationState = nullptr;
 }
 
 static void
@@ -4924,22 +4926,22 @@ NestedShell(JSContext* cx, unsigned argc
     AutoCStringVector argv(cx);
 
     // The first argument to the shell is its path, which we assume is our own
     // argv[0].
     if (sArgc < 1) {
         JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_NESTED_FAIL);
         return false;
     }
-    if (!argv.append(strdup(sArgv[0])))
+    if (!argv.append(js_strdup(sArgv[0])))
         return false;
 
     // Propagate selected flags from the current shell
     for (unsigned i = 0; i < sPropagatedFlags.length(); i++) {
-        char* cstr = strdup(sPropagatedFlags[i]);
+        char* cstr = js_strdup(sPropagatedFlags[i]);
         if (!cstr || !argv.append(cstr))
             return false;
     }
 
     // The arguments to nestedShell are stringified and append to argv.
     RootedString str(cx);
     for (unsigned i = 0; i < args.length(); i++) {
         str = ToString(cx, args[i]);
@@ -8626,16 +8628,17 @@ main(int argc, char** argv, char** envp)
 {
     PreInit();
 
     auto shutdownBufferStreams = MakeScopeExit([] {
         auto state = bufferStreamState.lock();
         state->shutdown = true;
         while (!state->jobs.empty())
             state.wait(/* jobs empty */);
+        state->jobs.clearAndFree();
     });
 
     sArgc = argc;
     sArgv = argv;
 
     int result;
 
 #ifdef HAVE_SETLOCALE
@@ -8647,16 +8650,22 @@ main(int argc, char** argv, char** envp)
     RCFile rcStdout(stdout);
     rcStdout.acquire();
     RCFile rcStderr(stderr);
     rcStderr.acquire();
 
     SetOutputFile("JS_STDOUT", &rcStdout, &gOutFile);
     SetOutputFile("JS_STDERR", &rcStderr, &gErrFile);
 
+    // Start the engine.
+    if (!JS_Init())
+        return 1;
+
+    auto shutdownEngine = MakeScopeExit([]() { JS_ShutDown(); });
+
     OptionParser op("Usage: {progname} [options] [[script] scriptArgs*]");
 
     op.setDescription("The SpiderMonkey shell provides a command line interface to the "
         "JavaScript engine. Code and file options provided via the command line are "
         "run left to right. If provided, the optional script argument is run after "
         "all options have been processed. Just-In-Time compilation modes may be enabled via "
         "command line options.");
     op.setDescriptionWidth(72);
@@ -8870,20 +8879,16 @@ main(int argc, char** argv, char** envp)
         js::jit::CPUInfo::SetAVXEnabled();
         PropagateFlagToNestedShells("--enable-avx");
     }
 #endif
 
     if (op.getBoolOption("no-threads"))
         js::DisableExtraThreads();
 
-    // Start the engine.
-    if (!JS_Init())
-        return 1;
-
     if (!InitSharedArrayBufferMailbox())
         return 1;
 
     // The fake CPU count must be set before initializing the Runtime,
     // which spins up the thread pool.
     int32_t cpuCount = op.getIntOption("cpu-count"); // What we're really setting
     if (cpuCount < 0)
         cpuCount = op.getIntOption("thread-count");  // Legacy name
@@ -8973,11 +8978,10 @@ main(int argc, char** argv, char** envp)
 
     KillWatchdog(cx);
 
     KillWorkerThreads(cx);
 
     DestructSharedArrayBufferMailbox();
 
     JS_DestroyContext(cx);
-    JS_ShutDown();
     return result;
 }
--- a/js/src/vm/Initialization.cpp
+++ b/js/src/vm/Initialization.cpp
@@ -95,16 +95,18 @@ JS::detail::InitWithFailureDiagnostic(bo
 #endif
 
     RETURN_IF_FAIL(js::TlsContext.init());
 
 #if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
     RETURN_IF_FAIL(js::oom::InitThreadType());
 #endif
 
+    js::InitMallocAllocator();
+
     RETURN_IF_FAIL(js::Mutex::Init());
 
     RETURN_IF_FAIL(js::wasm::InitInstanceStaticData());
 
     js::gc::InitMemorySubsystem(); // Ensure gc::SystemPageSize() works.
 
     RETURN_IF_FAIL(js::jit::InitProcessExecutableMemory());
 
@@ -165,16 +167,17 @@ JS_ShutDown(void)
 #ifdef JS_TRACE_LOGGING
     js::DestroyTraceLoggerThreadState();
     js::DestroyTraceLoggerGraphState();
 #endif
 
     js::MemoryProtectionExceptionHandler::uninstall();
 
     js::wasm::ShutDownInstanceStaticData();
+    js::wasm::ShutDownProcessStaticData();
 
     js::Mutex::ShutDown();
 
     // The only difficult-to-address reason for the restriction that you can't
     // call JS_Init/stuff/JS_ShutDown multiple times is the Windows PRMJ
     // NowInit initialization code, which uses PR_CallOnce to initialize the
     // PRMJ_Now subsystem.  (For reinitialization to be permitted, we'd need to
     // "reset" the called-once status -- doable, but more trouble than it's
@@ -194,16 +197,18 @@ JS_ShutDown(void)
 
     js::FinishDateTimeState();
 
     if (!JSRuntime::hasLiveRuntimes()) {
         js::wasm::ReleaseBuiltinThunks();
         js::jit::ReleaseProcessExecutableMemory();
     }
 
+    js::ShutDownMallocAllocator();
+
     libraryInitState = InitState::ShutDown;
 }
 
 JS_PUBLIC_API(bool)
 JS_SetICUMemoryFunctions(JS_ICUAllocFn allocFn, JS_ICUReallocFn reallocFn, JS_ICUFreeFn freeFn)
 {
     MOZ_ASSERT(libraryInitState == InitState::Uninitialized,
                "must call JS_SetICUMemoryFunctions before any other JSAPI "
--- a/js/src/wasm/WasmProcess.cpp
+++ b/js/src/wasm/WasmProcess.cpp
@@ -121,16 +121,23 @@ class ProcessCodeSegmentMap
     }
 
     ~ProcessCodeSegmentMap()
     {
         MOZ_ASSERT(segments1_.empty());
         MOZ_ASSERT(segments2_.empty());
     }
 
+    void freeAll() {
+        MOZ_ASSERT(segments1_.empty());
+        MOZ_ASSERT(segments2_.empty());
+        segments1_.clearAndFree();
+        segments2_.clearAndFree();
+    }
+
     bool insert(const CodeSegment* cs) {
         LockGuard<Mutex> lock(mutatorsMutex_);
 
         size_t index;
         MOZ_ALWAYS_FALSE(BinarySearchIf(*mutableCodeSegments_, 0, mutableCodeSegments_->length(),
                                         CodeSegmentPC(cs->base()), &index));
 
         if (!mutableCodeSegments_->insert(mutableCodeSegments_->begin() + index, cs))
@@ -225,8 +232,14 @@ wasm::LookupCodeSegment(const void* pc)
 }
 
 const Code*
 wasm::LookupCode(const void* pc)
 {
     const CodeSegment* found = LookupCodeSegment(pc);
     return found ? found->code() : nullptr;
 }
+
+void
+wasm::ShutDownProcessStaticData()
+{
+    processCodeSegmentMap.freeAll();
+}
--- a/js/src/wasm/WasmProcess.h
+++ b/js/src/wasm/WasmProcess.h
@@ -46,12 +46,15 @@ extern mozilla::Atomic<bool> CodeExists;
 // via pc in the methods described above.
 
 bool
 RegisterCodeSegment(const CodeSegment* cs);
 
 void
 UnregisterCodeSegment(const CodeSegment* cs);
 
+void
+ShutDownProcessStaticData();
+
 } // namespace wasm
 } // namespace js
 
 #endif // wasm_process_h