Bug 1031529 part 1 - Add a --no-threads shell flag. r=bhackett
authorJan de Mooij <jdemooij@mozilla.com>
Thu, 24 Jul 2014 11:56:41 +0200
changeset 195996 35038c3324ee08b29924059da9b117940e740bd7
parent 195995 634d33dc9d3ed1fbd09098b7d76d9a754cc80f69
child 195997 a0dd5a83ba367e022caa857e7c76de66bf2e9d5e
push id27205
push userkwierso@gmail.com
push dateFri, 25 Jul 2014 22:59:38 +0000
treeherdermozilla-central@e07264876182 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbhackett
bugs1031529
milestone34.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 1031529 part 1 - Add a --no-threads shell flag. r=bhackett
js/src/builtin/TestingFunctions.cpp
js/src/jit-test/tests/basic/offThreadCompileScript-02.js
js/src/jit-test/tests/debug/Source-element-03.js
js/src/jit-test/tests/debug/Source-introductionType.js
js/src/jit/AsmJS.cpp
js/src/jit/Ion.cpp
js/src/jsapi.cpp
js/src/jsgc.cpp
js/src/jsscript.cpp
js/src/shell/js.cpp
js/src/tests/lib/tests.py
js/src/vm/ForkJoin.cpp
js/src/vm/HelperThreads.cpp
js/src/vm/Runtime.cpp
js/src/vm/Runtime.h
js/src/vm/ThreadPool.cpp
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -1601,17 +1601,20 @@ Neuter(JSContext *cx, unsigned argc, jsv
     return true;
 }
 
 static bool
 HelperThreadCount(JSContext *cx, unsigned argc, jsval *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 #ifdef JS_THREADSAFE
-    args.rval().setInt32(HelperThreadState().threadCount);
+    if (CanUseExtraThreads())
+        args.rval().setInt32(HelperThreadState().threadCount);
+    else
+        args.rval().setInt32(0);
 #else
     args.rval().setInt32(0);
 #endif
     return true;
 }
 
 static bool
 TimesAccessed(JSContext *cx, unsigned argc, jsval *vp)
--- a/js/src/jit-test/tests/basic/offThreadCompileScript-02.js
+++ b/js/src/jit-test/tests/basic/offThreadCompileScript-02.js
@@ -1,10 +1,13 @@
 // Test offThreadCompileScript option handling.
 
+if (helperThreadCount() === 0)
+  quit(0);
+
 offThreadCompileScript('Error()');
 assertEq(!!runOffThreadScript().stack.match(/^@<string>:1:1\n/), true);
 
 offThreadCompileScript('Error()',
                        { fileName: "candelabra", lineNumber: 6502 });
 assertEq(!!runOffThreadScript().stack.match(/^@candelabra:6502:1\n/), true);
 
 var element = {};
--- a/js/src/jit-test/tests/debug/Source-element-03.js
+++ b/js/src/jit-test/tests/debug/Source-element-03.js
@@ -1,11 +1,14 @@
 // Owning elements and attribute names are attached to scripts compiled
 // off-thread.
 
+if (helperThreadCount() === 0)
+  quit(0);
+
 var g = newGlobal();
 var dbg = new Debugger;
 var gDO = dbg.addDebuggee(g);
 
 var elt = new g.Object;
 var eltDO = gDO.makeDebuggeeValue(elt);
 
 var log = '';
--- a/js/src/jit-test/tests/debug/Source-introductionType.js
+++ b/js/src/jit-test/tests/debug/Source-introductionType.js
@@ -1,10 +1,13 @@
 // Check that scripts' introduction types are properly marked.
 
+if (helperThreadCount() === 0)
+  quit(0);
+
 var g = newGlobal();
 var dbg = new Debugger();
 var gDO = dbg.addDebuggee(g);
 var log;
 
 // (Indirect) eval.
 dbg.onDebuggerStatement = function (frame) {
   log += 'd';
--- a/js/src/jit/AsmJS.cpp
+++ b/js/src/jit/AsmJS.cpp
@@ -5516,17 +5516,17 @@ static bool
 ParallelCompilationEnabled(ExclusiveContext *cx)
 {
     // If 'cx' isn't a JSContext, then we are already off the main thread so
     // off-thread compilation must be enabled. However, since there are a fixed
     // number of helper threads and one is already being consumed by this
     // parsing task, ensure that there another free thread to avoid deadlock.
     // (Note: there is at most one thread used for parsing so we don't have to
     // worry about general dining philosophers.)
-    if (HelperThreadState().threadCount <= 1)
+    if (HelperThreadState().threadCount <= 1 || !CanUseExtraThreads())
         return false;
 
     if (!cx->isJSContext())
         return true;
     return cx->asJSContext()->runtime()->canUseOffthreadIonCompilation();
 }
 
 // State of compilation as tracked and updated by the main thread.
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -1798,17 +1798,18 @@ OffThreadCompilationAvailable(JSContext 
 {
 #ifdef JS_THREADSAFE
     // Even if off thread compilation is enabled, compilation must still occur
     // on the main thread in some cases.
     //
     // Require cpuCount > 1 so that Ion compilation jobs and main-thread
     // execution are not competing for the same resources.
     return cx->runtime()->canUseOffthreadIonCompilation()
-        && HelperThreadState().cpuCount > 1;
+        && HelperThreadState().cpuCount > 1
+        && CanUseExtraThreads();
 #else
     return false;
 #endif
 }
 
 static void
 TrackAllProperties(JSContext *cx, JSObject *obj)
 {
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4570,17 +4570,17 @@ JS::CanCompileOffThread(JSContext *cx, c
         // If the parsing task would have to wait for GC to complete, it'll probably
         // be faster to just start it synchronously on the main thread unless the
         // script is huge.
         if (OffThreadParsingMustWaitForGC(cx->runtime()) && length < HUGE_LENGTH)
             return false;
 #endif // JS_THREADSAFE
     }
 
-    return cx->runtime()->canUseParallelParsing();
+    return cx->runtime()->canUseParallelParsing() && CanUseExtraThreads();
 }
 
 JS_PUBLIC_API(bool)
 JS::CompileOffThread(JSContext *cx, const ReadOnlyCompileOptions &options,
                      const jschar *chars, size_t length,
                      OffThreadCompileCallback callback, void *callbackData)
 {
     JS_ASSERT(CanCompileOffThread(cx, options, length));
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -2672,19 +2672,22 @@ js::GetCPUCount()
 
 bool
 GCHelperState::init()
 {
 #ifdef JS_THREADSAFE
     if (!(done = PR_NewCondVar(rt->gc.lock)))
         return false;
 
-    backgroundAllocation = (GetCPUCount() >= 2);
-
-    HelperThreadState().ensureInitialized();
+    if (CanUseExtraThreads()) {
+        backgroundAllocation = (GetCPUCount() >= 2);
+        HelperThreadState().ensureInitialized();
+    } else {
+        backgroundAllocation = false;
+    }
 #else
     backgroundAllocation = false;
 #endif /* JS_THREADSAFE */
 
     return true;
 }
 
 void
@@ -2750,16 +2753,18 @@ GCHelperState::waitForBackgroundThread()
     MOZ_CRASH();
 #endif
 }
 
 void
 GCHelperState::work()
 {
 #ifdef JS_THREADSAFE
+    JS_ASSERT(CanUseExtraThreads());
+
     AutoLockGC lock(rt);
 
     JS_ASSERT(!thread);
     thread = PR_GetCurrentThread();
 
     TraceLogger *logger = TraceLoggerForCurrentThread();
 
     switch (state()) {
@@ -2807,31 +2812,34 @@ GCHelperState::work()
     MOZ_CRASH();
 #endif
 }
 
 void
 GCHelperState::startBackgroundSweep(bool shouldShrink)
 {
 #ifdef JS_THREADSAFE
+    JS_ASSERT(CanUseExtraThreads());
+
     AutoLockHelperThreadState helperLock;
     AutoLockGC lock(rt);
     JS_ASSERT(state() == IDLE);
     JS_ASSERT(!sweepFlag);
     sweepFlag = true;
     shrinkFlag = shouldShrink;
     startBackgroundThread(SWEEPING);
 #endif /* JS_THREADSAFE */
 }
 
 /* Must be called with the GC lock taken. */
 void
 GCHelperState::startBackgroundShrink()
 {
 #ifdef JS_THREADSAFE
+    JS_ASSERT(CanUseExtraThreads());
     switch (state()) {
       case IDLE:
         JS_ASSERT(!sweepFlag);
         shrinkFlag = true;
         startBackgroundThread(SWEEPING);
         break;
       case SWEEPING:
         shrinkFlag = true;
@@ -4328,17 +4336,17 @@ GCRuntime::beginSweepPhase(bool lastGC)
 
     JS_ASSERT(!abortSweepAfterCurrentGroup);
 
     computeNonIncrementalMarkingForValidation();
 
     gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP);
 
 #ifdef JS_THREADSAFE
-    sweepOnBackgroundThread = !lastGC && !TraceEnabled();
+    sweepOnBackgroundThread = !lastGC && !TraceEnabled() && CanUseExtraThreads();
 #endif
 
 #ifdef DEBUG
     for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
         JS_ASSERT(!c->gcIncomingGrayPointers);
         for (JSCompartment::WrapperEnum e(c); !e.empty(); e.popFront()) {
             if (e.front().key().kind != CrossCompartmentKey::StringWrapper)
                 AssertNotOnGrayList(&e.front().value().get().toObject());
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -1647,17 +1647,18 @@ ScriptSource::setSourceCopy(ExclusiveCon
     //  - If we are on the main thread, there must be at least two helper
     //    threads since at most one helper thread can be blocking on the main
     //    thread (see HelperThreadState::canStartParseTask) which would cause a
     //    deadlock if there wasn't a second helper thread that could make
     //    progress on our compression task.
 #if defined(JS_THREADSAFE)
     bool canCompressOffThread =
         HelperThreadState().cpuCount > 1 &&
-        HelperThreadState().threadCount >= 2;
+        HelperThreadState().threadCount >= 2 &&
+        CanUseExtraThreads();
 #else
     bool canCompressOffThread = false;
 #endif
     const size_t TINY_SCRIPT = 256;
     const size_t HUGE_SCRIPT = 5 * 1024 * 1024;
     if (TINY_SCRIPT <= srcBuf.length() && srcBuf.length() < HUGE_SCRIPT && canCompressOffThread) {
         task->ss = this;
         if (!StartOffThreadCompression(cx, task))
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -2857,16 +2857,21 @@ WorkerMain(void *arg)
     js_delete(input);
 }
 
 Vector<PRThread *, 0, SystemAllocPolicy> workerThreads;
 
 static bool
 EvalInWorker(JSContext *cx, unsigned argc, jsval *vp)
 {
+    if (!CanUseExtraThreads()) {
+        JS_ReportError(cx, "Can't create worker threads with --no-threads");
+        return false;
+    }
+
     CallArgs args = CallArgsFromVp(argc, vp);
     if (!args.get(0).isString()) {
         JS_ReportError(cx, "Invalid arguments to evalInWorker");
         return false;
     }
 
     if (!args[0].toString()->ensureLinear(cx))
         return false;
@@ -3667,16 +3672,21 @@ static void
 OffThreadCompileScriptCallback(void *token, void *callbackData)
 {
     offThreadState.markDone(token);
 }
 
 static bool
 OffThreadCompileScript(JSContext *cx, unsigned argc, jsval *vp)
 {
+    if (!CanUseExtraThreads()) {
+        JS_ReportError(cx, "Can't use offThreadCompileScript with --no-threads");
+        return false;
+    }
+
     CallArgs args = CallArgsFromVp(argc, vp);
 
     if (args.length() < 1) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
                              "offThreadCompileScript", "0", "s");
         return false;
     }
     if (!args[0].isString()) {
@@ -6300,16 +6310,17 @@ main(int argc, char **argv, char **envp)
                              "to test JIT codegen (no-op on platforms other than x86).")
         || !op.addBoolOption('\0', "no-sse3", "Pretend CPU does not support SSE3 instructions and above "
                              "to test JIT codegen (no-op on platforms other than x86 and x64).")
         || !op.addBoolOption('\0', "no-sse4", "Pretend CPU does not support SSE4 instructions"
                              "to test JIT codegen (no-op on platforms other than x86 and x64).")
         || !op.addBoolOption('\0', "fuzzing-safe", "Don't expose functions that aren't safe for "
                              "fuzzers to call")
         || !op.addBoolOption('\0', "latin1-strings", "Enable Latin1 strings (default: on)")
+        || !op.addBoolOption('\0', "no-threads", "Disable helper threads and PJS threads")
 #ifdef DEBUG
         || !op.addBoolOption('\0', "dump-entrained-variables", "Print variables which are "
                              "unnecessarily entrained by inner functions")
 #endif
 #ifdef JSGC_GENERATIONAL
         || !op.addBoolOption('\0', "no-ggc", "Disable Generational GC")
 #endif
         || !op.addIntOption('\0', "available-memory", "SIZE",
@@ -6379,16 +6390,19 @@ main(int argc, char **argv, char **envp)
     if (op.getBoolOption("no-sse4")) {
         JSC::MacroAssembler::SetSSE4Disabled();
         PropagateFlagToNestedShells("--no-sse4");
     }
 #endif
 
 #endif // DEBUG
 
+    if (op.getBoolOption("no-threads"))
+        js::DisableExtraThreads();
+
 #ifdef JS_THREADSAFE
     // The fake thread count must be set before initializing the Runtime,
     // which spins up the thread pool.
     int32_t threadCount = op.getIntOption("thread-count");
     if (threadCount >= 0)
         SetFakeCPUCount(threadCount);
 #endif /* JS_THREADSAFE */
 
@@ -6458,16 +6472,17 @@ main(int argc, char **argv, char **envp)
 
     DestroyContext(cx, true);
 
     KillWatchdog();
 
     gInterruptFunc.destroy();
 
 #ifdef JS_THREADSAFE
+    MOZ_ASSERT_IF(!CanUseExtraThreads(), workerThreads.empty());
     for (size_t i = 0; i < workerThreads.length(); i++)
         PR_JoinThread(workerThreads[i]);
 #endif
 
 #ifdef JSGC_GENERATIONAL
     if (!noggc.empty())
         noggc.destroy();
 #endif
--- a/js/src/tests/lib/tests.py
+++ b/js/src/tests/lib/tests.py
@@ -8,17 +8,17 @@ from subprocess import Popen, PIPE
 from threading import Thread
 
 from results import TestOutput
 
 # When run on tbpl, we run each test multiple times with the following arguments.
 TBPL_FLAGS = [
     [], # no flags, normal baseline and ion
     ['--ion-eager', '--ion-offthread-compile=off'], # implies --baseline-eager
-    ['--ion-eager', '--ion-offthread-compile=off', '--ion-check-range-analysis', '--no-sse3'],
+    ['--ion-eager', '--ion-offthread-compile=off', '--ion-check-range-analysis', '--no-sse3', '--no-threads'],
     ['--baseline-eager'],
     ['--baseline-eager', '--no-fpu'],
     ['--no-baseline', '--no-ion'],
 ]
 
 def do_run_cmd(cmd):
     l = [ None, None ]
     th_run_cmd(cmd, l)
--- a/js/src/vm/ForkJoin.cpp
+++ b/js/src/vm/ForkJoin.cpp
@@ -51,29 +51,35 @@ using mozilla::ThreadLocal;
 // When JS_THREADSAFE or JS_ION is not defined, we simply run the
 // |func| callback sequentially.  We also forego the feedback
 // altogether.
 
 static bool
 ExecuteSequentially(JSContext *cx_, HandleValue funVal, uint16_t *sliceStart,
                     uint16_t sliceEnd);
 
-#if !defined(JS_THREADSAFE) || !defined(JS_ION)
-bool
-js::ForkJoin(JSContext *cx, CallArgs &args)
+static bool
+ForkJoinSequentially(JSContext *cx, CallArgs &args)
 {
     RootedValue argZero(cx, args[0]);
     uint16_t sliceStart = uint16_t(args[1].toInt32());
     uint16_t sliceEnd = uint16_t(args[2].toInt32());
     if (!ExecuteSequentially(cx, argZero, &sliceStart, sliceEnd))
         return false;
     MOZ_ASSERT(sliceStart == sliceEnd);
     return true;
 }
 
+#if !defined(JS_THREADSAFE) || !defined(JS_ION)
+bool
+js::ForkJoin(JSContext *cx, CallArgs &args)
+{
+    return ForkJoinSequentially(cx, args);
+}
+
 JSContext *
 ForkJoinContext::acquireJSContext()
 {
     return nullptr;
 }
 
 void
 ForkJoinContext::releaseJSContext()
@@ -498,16 +504,19 @@ js::ForkJoin(JSContext *cx, CallArgs &ar
     JS_ASSERT(args[0].isObject());
     JS_ASSERT(args[0].toObject().is<JSFunction>());
     JS_ASSERT(args[1].isInt32());
     JS_ASSERT(args[2].isInt32());
     JS_ASSERT(args[3].isInt32());
     JS_ASSERT(args[3].toInt32() < NumForkJoinModes);
     JS_ASSERT(args[4].isObjectOrNull());
 
+    if (!CanUseExtraThreads())
+        return ForkJoinSequentially(cx, args);
+
     RootedFunction fun(cx, &args[0].toObject().as<JSFunction>());
     uint16_t sliceStart = (uint16_t)(args[1].toInt32());
     uint16_t sliceEnd = (uint16_t)(args[2].toInt32());
     ForkJoinMode mode = (ForkJoinMode)(args[3].toInt32());
     RootedObject updatable(cx, args[4].toObjectOrNull());
 
     MOZ_ASSERT(sliceStart == args[1].toInt32());
     MOZ_ASSERT(sliceEnd == args[2].toInt32());
--- a/js/src/vm/HelperThreads.cpp
+++ b/js/src/vm/HelperThreads.cpp
@@ -412,16 +412,18 @@ js::EnqueuePendingParseTasksAfterGC(JSRu
 }
 
 static const uint32_t HELPER_STACK_SIZE = 512 * 1024;
 static const uint32_t HELPER_STACK_QUOTA = 450 * 1024;
 
 void
 GlobalHelperThreadState::ensureInitialized()
 {
+    JS_ASSERT(CanUseExtraThreads());
+
     JS_ASSERT(this == &HelperThreadState());
     AutoLockHelperThreadState lock;
 
     if (threads)
         return;
 
     threads = js_pod_calloc<HelperThread>(threadCount);
     if (!threads)
@@ -454,16 +456,17 @@ GlobalHelperThreadState::GlobalHelperThr
     producerWakeup = PR_NewCondVar(helperLock);
     pauseWakeup = PR_NewCondVar(helperLock);
 }
 
 void
 GlobalHelperThreadState::finish()
 {
     if (threads) {
+        MOZ_ASSERT(CanUseExtraThreads());
         for (size_t i = 0; i < threadCount; i++)
             threads[i].destroy();
         js_free(threads);
     }
 
     PR_DestroyCondVar(consumerWakeup);
     PR_DestroyCondVar(producerWakeup);
     PR_DestroyCondVar(pauseWakeup);
@@ -1185,16 +1188,18 @@ HelperThread::handleGCHelperWorkload()
     }
 
     gcHelperState = nullptr;
 }
 
 void
 HelperThread::threadLoop()
 {
+    JS_ASSERT(CanUseExtraThreads());
+
     JS::AutoSuppressGCAnalysis nogc;
     AutoLockHelperThreadState lock;
 
     js::TlsPerThreadData.set(threadData.addr());
 
     // Compute the thread's stack limit, for over-recursed checks.
     uintptr_t stackLimit = GetNativeStackBase();
 #if JS_STACK_GROWTH_DIRECTION > 0
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -59,16 +59,26 @@ using JS::DoubleNaNValue;
 /* static */ ThreadLocal<PerThreadData*> js::TlsPerThreadData;
 
 #ifdef JS_THREADSAFE
 /* static */ Atomic<size_t> JSRuntime::liveRuntimesCount;
 #else
 /* static */ size_t JSRuntime::liveRuntimesCount;
 #endif
 
+namespace js {
+    bool gCanUseExtraThreads = true;
+};
+
+void
+js::DisableExtraThreads()
+{
+    gCanUseExtraThreads = false;
+}
+
 const JSSecurityCallbacks js::NullSecurityCallbacks = { };
 
 PerThreadData::PerThreadData(JSRuntime *runtime)
   : PerThreadDataFriendFields(),
     runtime_(runtime),
     jitTop(nullptr),
     jitJSContext(nullptr),
     jitStackLimit(0),
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -478,16 +478,25 @@ enum RuntimeLock {
 };
 
 #ifdef DEBUG
 void AssertCurrentThreadCanLock(RuntimeLock which);
 #else
 inline void AssertCurrentThreadCanLock(RuntimeLock which) {}
 #endif
 
+inline bool
+CanUseExtraThreads()
+{
+    extern bool gCanUseExtraThreads;
+    return gCanUseExtraThreads;
+}
+
+void DisableExtraThreads();
+
 /*
  * Encapsulates portions of the runtime/context that are tied to a
  * single active thread.  Instances of this structure can occur for
  * the main thread as |JSRuntime::mainThread|, for select operations
  * performed off thread, such as parsing, and for Parallel JS worker
  * threads.
  */
 class PerThreadData : public PerThreadDataFriendFields
--- a/js/src/vm/ThreadPool.cpp
+++ b/js/src/vm/ThreadPool.cpp
@@ -136,16 +136,18 @@ ThreadPoolWorker::start()
     if (isMainThread())
         return true;
 
     MOZ_ASSERT(state_ == CREATED);
 
     // Set state to active now, *before* the thread starts:
     state_ = ACTIVE;
 
+    MOZ_ASSERT(CanUseExtraThreads());
+
     return PR_CreateThread(PR_USER_THREAD,
                            HelperThreadMain, this,
                            PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
                            PR_UNJOINABLE_THREAD,
                            WORKER_THREAD_STACK_SIZE);
 #endif
 }
 
@@ -174,16 +176,17 @@ ThreadPoolWorker::HelperThreadMain(void 
 
     worker->helperLoop();
 }
 
 void
 ThreadPoolWorker::helperLoop()
 {
     MOZ_ASSERT(!isMainThread());
+    MOZ_ASSERT(CanUseExtraThreads());
 
     // This is hokey in the extreme.  To compute the stack limit,
     // subtract the size of the stack from the address of a local
     // variable and give a 100k buffer.  Is there a better way?
     // (Note: 2k proved to be fine on Mac, but too little on Linux)
     uintptr_t stackLimitOffset = WORKER_THREAD_STACK_SIZE - 100*1024;
     uintptr_t stackLimit = (((uintptr_t)&stackLimitOffset) +
                              stackLimitOffset * JS_STACK_GROWTH_DIRECTION);