Bug 942984 - Set native stack limit for JS worker threads, r=billm a=lsblakk.
authorBrian Hackett <bhackett1024@gmail.com>
Wed, 04 Dec 2013 12:41:36 -0800
changeset 166663 d1a206d2c6b3993868deb37afae5a6f7b6fccc64
parent 166662 45db33cf269ba7195316994595f7f729b88b1106
child 166664 21ef58290c6a6869e02ff78a80b159984ed98cf0
push id3066
push userakeybl@mozilla.com
push dateMon, 09 Dec 2013 19:58:46 +0000
treeherdermozilla-beta@a31a0dce83aa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbillm, lsblakk
bugs942984
milestone27.0a2
Bug 942984 - Set native stack limit for JS worker threads, r=billm a=lsblakk.
js/src/jscntxt.cpp
js/src/jscntxt.h
js/src/jsworkers.cpp
js/src/jsworkers.h
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -418,17 +418,20 @@ js_ReportOverRecursed(JSContext *maybecx
 #endif
     if (maybecx)
         JS_ReportErrorNumber(maybecx, js_GetErrorMessage, nullptr, JSMSG_OVER_RECURSED);
 }
 
 void
 js_ReportOverRecursed(ThreadSafeContext *cx)
 {
-    js_ReportOverRecursed(cx->maybeJSContext());
+    if (cx->isJSContext())
+        js_ReportOverRecursed(cx->asJSContext());
+    else if (cx->isExclusiveContext())
+        cx->asExclusiveContext()->addPendingOverRecursed();
 }
 
 void
 js_ReportAllocationOverflow(ThreadSafeContext *cxArg)
 {
     if (!cxArg)
         return;
 
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -397,16 +397,17 @@ class ExclusiveContext : public ThreadSa
     // possibility for a race read/writing it.
     WorkerThreadState *workerThreadState() {
         return runtime_->workerThreadState;
     }
 #endif
 
     // Methods specific to any WorkerThread for the context.
     frontend::CompileError &addPendingCompileError();
+    void addPendingOverRecursed();
 };
 
 inline void
 MaybeCheckStackRoots(ExclusiveContext *cx)
 {
     MaybeCheckStackRoots(cx->maybeJSContext());
 }
 
--- a/js/src/jsworkers.cpp
+++ b/js/src/jsworkers.cpp
@@ -4,29 +4,31 @@
  * 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 "jsworkers.h"
 
 #ifdef JS_WORKER_THREADS
 #include "mozilla/DebugOnly.h"
 
+#include "jsnativestack.h"
 #include "prmjtime.h"
 
 #include "frontend/BytecodeCompiler.h"
 #include "jit/ExecutionModeInlines.h"
 #include "jit/IonBuilder.h"
 #include "vm/Debugger.h"
 
 #include "jscntxtinlines.h"
 #include "jscompartmentinlines.h"
 #include "jsobjinlines.h"
 
 using namespace js;
 
+using mozilla::ArrayLength;
 using mozilla::DebugOnly;
 
 bool
 js::EnsureWorkerThreadsInitialized(ExclusiveContext *cx)
 {
     // If 'cx' is not a JSContext, we are already off the main thread and the
     // worker threads would have already been initialized.
     if (!cx->isJSContext()) {
@@ -174,17 +176,18 @@ static const JSClass workerGlobalClass =
     JS_ConvertStub,   nullptr
 };
 
 ParseTask::ParseTask(ExclusiveContext *cx, const CompileOptions &options,
                      const jschar *chars, size_t length, JSObject *scopeChain,
                      JS::OffThreadCompileCallback callback, void *callbackData)
   : cx(cx), options(options), chars(chars), length(length),
     alloc(JSRuntime::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE), scopeChain(scopeChain),
-    callback(callback), callbackData(callbackData), script(nullptr), errors(cx)
+    callback(callback), callbackData(callbackData), script(nullptr),
+    errors(cx), overRecursed(false)
 {
     JSRuntime *rt = scopeChain->runtimeFromMainThread();
 
     if (options.principals())
         JS_HoldPrincipals(options.principals());
     if (options.originPrincipals())
         JS_HoldPrincipals(options.originPrincipals());
     if (!AddObjectRoot(rt, &this->scopeChain, "ParseTask::scopeChain"))
@@ -305,16 +308,19 @@ js::WaitForOffThreadParsingToFinish(JSRu
                 parseInProgress |= !!state.threads[i].parseTask;
             if (!parseInProgress)
                 break;
         }
         state.wait(WorkerThreadState::CONSUMER);
     }
 }
 
+static const uint32_t WORKER_STACK_SIZE = 512 * 1024;
+static const uint32_t WORKER_STACK_QUOTA = 450 * 1024;
+
 bool
 WorkerThreadState::init(JSRuntime *rt)
 {
     if (!rt->useHelperThreads()) {
         numThreads = 0;
         return true;
     }
 
@@ -340,17 +346,17 @@ WorkerThreadState::init(JSRuntime *rt)
 
     for (size_t i = 0; i < numThreads; i++) {
         WorkerThread &helper = threads[i];
         helper.runtime = rt;
         helper.threadData.construct(rt);
         helper.threadData.ref().addToThreadList();
         helper.thread = PR_CreateThread(PR_USER_THREAD,
                                         WorkerThread::ThreadMain, &helper,
-                                        PR_PRIORITY_NORMAL, PR_LOCAL_THREAD, PR_JOINABLE_THREAD, 0);
+                                        PR_PRIORITY_NORMAL, PR_LOCAL_THREAD, PR_JOINABLE_THREAD, WORKER_STACK_SIZE);
         if (!helper.thread || !helper.threadData.ref().init()) {
             for (size_t j = 0; j < numThreads; j++)
                 threads[j].destroy();
             js_free(threads);
             threads = nullptr;
             numThreads = 0;
             return false;
         }
@@ -573,16 +579,18 @@ WorkerThreadState::finishParseTask(JSCon
     RootedScript script(rt, parseTask->script);
 
     // If we have a context, report any error or warnings generated during the
     // parse, and inform the debugger about the compiled scripts.
     if (maybecx) {
         AutoCompartment ac(maybecx, parseTask->scopeChain);
         for (size_t i = 0; i < parseTask->errors.length(); i++)
             parseTask->errors[i]->throwError(maybecx);
+        if (parseTask->overRecursed)
+            js_ReportOverRecursed(maybecx);
 
         if (script) {
             // The Debugger only needs to be told about the topmost script that was compiled.
             GlobalObject *compileAndGoGlobal = nullptr;
             if (script->compileAndGo)
                 compileAndGoGlobal = &script->global();
             Debugger::onNewScript(maybecx, script, compileAndGoGlobal);
 
@@ -725,16 +733,23 @@ ExclusiveContext::addPendingCompileError
     if (!error)
         MOZ_CRASH();
     if (!workerThread()->parseTask->errors.append(error))
         MOZ_CRASH();
     return *error;
 }
 
 void
+ExclusiveContext::addPendingOverRecursed()
+{
+    if (workerThread()->parseTask)
+        workerThread()->parseTask->overRecursed = true;
+}
+
+void
 WorkerThread::handleParseWorkload(WorkerThreadState &state)
 {
     JS_ASSERT(state.isLocked());
     JS_ASSERT(state.canStartParseTask());
     JS_ASSERT(idle());
 
     parseTask = state.parseWorklist.popCopy();
     parseTask->cx->setWorkerThread(this);
@@ -889,16 +904,26 @@ ScriptSource::getOffThreadCompressionCha
 void
 WorkerThread::threadLoop()
 {
     WorkerThreadState &state = *runtime->workerThreadState;
     AutoLockWorkerThreadState lock(state);
 
     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
+    stackLimit += WORKER_STACK_QUOTA;
+#else
+    stackLimit -= WORKER_STACK_QUOTA;
+#endif
+    for (size_t i = 0; i < ArrayLength(threadData.ref().nativeStackLimit); i++)
+        threadData.ref().nativeStackLimit[i] = stackLimit;
+
     while (true) {
         JS_ASSERT(!ionBuilder && !asmData);
 
         // Block until a task is available.
         while (true) {
             if (state.shouldPause)
                 pause();
             if (terminate)
@@ -1108,9 +1133,15 @@ AutoPauseCurrentWorkerThread::~AutoPause
 }
 
 frontend::CompileError &
 ExclusiveContext::addPendingCompileError()
 {
     MOZ_ASSUME_UNREACHABLE("Off thread compilation not available.");
 }
 
+void
+ExclusiveContext::addPendingOverRecursed()
+{
+    MOZ_ASSUME_UNREACHABLE("Off thread compilation not available.");
+}
+
 #endif /* JS_WORKER_THREADS */
--- a/js/src/jsworkers.h
+++ b/js/src/jsworkers.h
@@ -408,16 +408,17 @@ struct ParseTask
     // Holds the final script between the invocation of the callback and the
     // point where FinishOffThreadScript is called, which will destroy the
     // ParseTask.
     JSScript *script;
 
     // Any errors or warnings produced during compilation. These are reported
     // when finishing the script.
     Vector<frontend::CompileError *> errors;
+    bool overRecursed;
 
     ParseTask(ExclusiveContext *cx, const CompileOptions &options,
               const jschar *chars, size_t length, JSObject *scopeChain,
               JS::OffThreadCompileCallback callback, void *callbackData);
 
     ~ParseTask();
 };