js/src/jsworkers.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
     2  * vim: set ts=8 sts=4 et sw=4 tw=99:
     3  * This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 #include "jsworkers.h"
     9 #ifdef JS_THREADSAFE
    11 #include "mozilla/DebugOnly.h"
    13 #include "jsnativestack.h"
    14 #include "prmjtime.h"
    16 #include "frontend/BytecodeCompiler.h"
    17 #include "jit/IonBuilder.h"
    18 #include "vm/Debugger.h"
    19 #include "vm/TraceLogging.h"
    21 #include "jscntxtinlines.h"
    22 #include "jscompartmentinlines.h"
    23 #include "jsobjinlines.h"
    24 #include "jsscriptinlines.h"
    26 using namespace js;
    28 using mozilla::ArrayLength;
    29 using mozilla::DebugOnly;
    31 namespace js {
    33 GlobalWorkerThreadState gWorkerThreadState;
    35 } // namespace js
    37 void
    38 js::EnsureWorkerThreadsInitialized(ExclusiveContext *cx)
    39 {
    40     // If 'cx' is not a JSContext, we are already off the main thread and the
    41     // worker threads would have already been initialized.
    42     if (!cx->isJSContext())
    43         return;
    45     WorkerThreadState().ensureInitialized();
    46 }
    48 static size_t
    49 ThreadCountForCPUCount(size_t cpuCount)
    50 {
    51     return Max(cpuCount, (size_t)2);
    52 }
    54 void
    55 js::SetFakeCPUCount(size_t count)
    56 {
    57     // This must be called before the threads have been initialized.
    58     JS_ASSERT(!WorkerThreadState().threads);
    60     WorkerThreadState().cpuCount = count;
    61     WorkerThreadState().threadCount = ThreadCountForCPUCount(count);
    62 }
    64 #ifdef JS_ION
    66 bool
    67 js::StartOffThreadAsmJSCompile(ExclusiveContext *cx, AsmJSParallelTask *asmData)
    68 {
    69     // Threads already initialized by the AsmJS compiler.
    70     JS_ASSERT(asmData->mir);
    71     JS_ASSERT(asmData->lir == nullptr);
    73     AutoLockWorkerThreadState lock;
    75     // Don't append this task if another failed.
    76     if (WorkerThreadState().asmJSWorkerFailed())
    77         return false;
    79     if (!WorkerThreadState().asmJSWorklist().append(asmData))
    80         return false;
    82     WorkerThreadState().notifyOne(GlobalWorkerThreadState::PRODUCER);
    83     return true;
    84 }
    86 bool
    87 js::StartOffThreadIonCompile(JSContext *cx, jit::IonBuilder *builder)
    88 {
    89     EnsureWorkerThreadsInitialized(cx);
    91     AutoLockWorkerThreadState lock;
    93     if (!WorkerThreadState().ionWorklist().append(builder))
    94         return false;
    96     WorkerThreadState().notifyOne(GlobalWorkerThreadState::PRODUCER);
    97     return true;
    98 }
   100 /*
   101  * Move an IonBuilder for which compilation has either finished, failed, or
   102  * been cancelled into the global finished compilation list. All off thread
   103  * compilations which are started must eventually be finished.
   104  */
   105 static void
   106 FinishOffThreadIonCompile(jit::IonBuilder *builder)
   107 {
   108     WorkerThreadState().ionFinishedList().append(builder);
   109 }
   111 #endif // JS_ION
   113 static inline bool
   114 CompiledScriptMatches(JSCompartment *compartment, JSScript *script, JSScript *target)
   115 {
   116     if (script)
   117         return target == script;
   118     return target->compartment() == compartment;
   119 }
   121 void
   122 js::CancelOffThreadIonCompile(JSCompartment *compartment, JSScript *script)
   123 {
   124 #ifdef JS_ION
   125     jit::JitCompartment *jitComp = compartment->jitCompartment();
   126     if (!jitComp)
   127         return;
   129     AutoLockWorkerThreadState lock;
   131     if (!WorkerThreadState().threads)
   132         return;
   134     /* Cancel any pending entries for which processing hasn't started. */
   135     GlobalWorkerThreadState::IonBuilderVector &worklist = WorkerThreadState().ionWorklist();
   136     for (size_t i = 0; i < worklist.length(); i++) {
   137         jit::IonBuilder *builder = worklist[i];
   138         if (CompiledScriptMatches(compartment, script, builder->script())) {
   139             FinishOffThreadIonCompile(builder);
   140             WorkerThreadState().remove(worklist, &i);
   141         }
   142     }
   144     /* Wait for in progress entries to finish up. */
   145     for (size_t i = 0; i < WorkerThreadState().threadCount; i++) {
   146         const WorkerThread &helper = WorkerThreadState().threads[i];
   147         while (helper.ionBuilder &&
   148                CompiledScriptMatches(compartment, script, helper.ionBuilder->script()))
   149         {
   150             helper.ionBuilder->cancel();
   151             WorkerThreadState().wait(GlobalWorkerThreadState::CONSUMER);
   152         }
   153     }
   155     /* Cancel code generation for any completed entries. */
   156     GlobalWorkerThreadState::IonBuilderVector &finished = WorkerThreadState().ionFinishedList();
   157     for (size_t i = 0; i < finished.length(); i++) {
   158         jit::IonBuilder *builder = finished[i];
   159         if (CompiledScriptMatches(compartment, script, builder->script())) {
   160             jit::FinishOffThreadBuilder(builder);
   161             WorkerThreadState().remove(finished, &i);
   162         }
   163     }
   164 #endif // JS_ION
   165 }
   167 static const JSClass workerGlobalClass = {
   168     "internal-worker-global", JSCLASS_GLOBAL_FLAGS,
   169     JS_PropertyStub,  JS_DeletePropertyStub,
   170     JS_PropertyStub,  JS_StrictPropertyStub,
   171     JS_EnumerateStub, JS_ResolveStub,
   172     JS_ConvertStub,   nullptr,
   173     nullptr, nullptr, nullptr,
   174     JS_GlobalObjectTraceHook
   175 };
   177 ParseTask::ParseTask(ExclusiveContext *cx, JSObject *exclusiveContextGlobal, JSContext *initCx,
   178                      const jschar *chars, size_t length,
   179                      JS::OffThreadCompileCallback callback, void *callbackData)
   180   : cx(cx), options(initCx), chars(chars), length(length),
   181     alloc(JSRuntime::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
   182     exclusiveContextGlobal(initCx, exclusiveContextGlobal), optionsElement(initCx),
   183     optionsIntroductionScript(initCx), callback(callback), callbackData(callbackData),
   184     script(nullptr), errors(cx), overRecursed(false)
   185 {
   186 }
   188 bool
   189 ParseTask::init(JSContext *cx, const ReadOnlyCompileOptions &options)
   190 {
   191     if (!this->options.copy(cx, options))
   192         return false;
   194     // Save those compilation options that the ScriptSourceObject can't
   195     // point at while it's in the compilation's temporary compartment.
   196     optionsElement = this->options.element();
   197     this->options.setElement(nullptr);
   198     optionsIntroductionScript = this->options.introductionScript();
   199     this->options.setIntroductionScript(nullptr);
   201     return true;
   202 }
   204 void
   205 ParseTask::activate(JSRuntime *rt)
   206 {
   207     rt->setUsedByExclusiveThread(exclusiveContextGlobal->zone());
   208     cx->enterCompartment(exclusiveContextGlobal->compartment());
   209 }
   211 void
   212 ParseTask::finish()
   213 {
   214     if (script) {
   215         // Initialize the ScriptSourceObject slots that we couldn't while the SSO
   216         // was in the temporary compartment.
   217         ScriptSourceObject &sso = script->sourceObject()->as<ScriptSourceObject>();
   218         sso.initElement(optionsElement);
   219         sso.initIntroductionScript(optionsIntroductionScript);
   220     }
   221 }
   223 ParseTask::~ParseTask()
   224 {
   225     // ParseTask takes over ownership of its input exclusive context.
   226     js_delete(cx);
   228     for (size_t i = 0; i < errors.length(); i++)
   229         js_delete(errors[i]);
   230 }
   232 void
   233 js::CancelOffThreadParses(JSRuntime *rt)
   234 {
   235     AutoLockWorkerThreadState lock;
   237     if (!WorkerThreadState().threads)
   238         return;
   240     // Instead of forcibly canceling pending parse tasks, just wait for all scheduled
   241     // and in progress ones to complete. Otherwise the final GC may not collect
   242     // everything due to zones being used off thread.
   243     while (true) {
   244         bool pending = false;
   245         GlobalWorkerThreadState::ParseTaskVector &worklist = WorkerThreadState().parseWorklist();
   246         for (size_t i = 0; i < worklist.length(); i++) {
   247             ParseTask *task = worklist[i];
   248             if (task->runtimeMatches(rt))
   249                 pending = true;
   250         }
   251         if (!pending) {
   252             bool inProgress = false;
   253             for (size_t i = 0; i < WorkerThreadState().threadCount; i++) {
   254                 ParseTask *task = WorkerThreadState().threads[i].parseTask;
   255                 if (task && task->runtimeMatches(rt))
   256                     inProgress = true;
   257             }
   258             if (!inProgress)
   259                 break;
   260         }
   261         WorkerThreadState().wait(GlobalWorkerThreadState::CONSUMER);
   262     }
   264     // Clean up any parse tasks which haven't been finished by the main thread.
   265     GlobalWorkerThreadState::ParseTaskVector &finished = WorkerThreadState().parseFinishedList();
   266     while (true) {
   267         bool found = false;
   268         for (size_t i = 0; i < finished.length(); i++) {
   269             ParseTask *task = finished[i];
   270             if (task->runtimeMatches(rt)) {
   271                 found = true;
   272                 AutoUnlockWorkerThreadState unlock;
   273                 WorkerThreadState().finishParseTask(/* maybecx = */ nullptr, rt, task);
   274             }
   275         }
   276         if (!found)
   277             break;
   278     }
   279 }
   281 bool
   282 js::OffThreadParsingMustWaitForGC(JSRuntime *rt)
   283 {
   284     // Off thread parsing can't occur during incremental collections on the
   285     // atoms compartment, to avoid triggering barriers. (Outside the atoms
   286     // compartment, the compilation will use a new zone that is never
   287     // collected.) If an atoms-zone GC is in progress, hold off on executing the
   288     // parse task until the atoms-zone GC completes (see
   289     // EnqueuePendingParseTasksAfterGC).
   290     return rt->activeGCInAtomsZone();
   291 }
   293 bool
   294 js::StartOffThreadParseScript(JSContext *cx, const ReadOnlyCompileOptions &options,
   295                               const jschar *chars, size_t length,
   296                               JS::OffThreadCompileCallback callback, void *callbackData)
   297 {
   298     // Suppress GC so that calls below do not trigger a new incremental GC
   299     // which could require barriers on the atoms compartment.
   300     gc::AutoSuppressGC suppress(cx);
   302     SourceBufferHolder srcBuf(chars, length, SourceBufferHolder::NoOwnership);
   303     frontend::MaybeCallSourceHandler(cx, options, srcBuf);
   305     EnsureWorkerThreadsInitialized(cx);
   307     JS::CompartmentOptions compartmentOptions(cx->compartment()->options());
   308     compartmentOptions.setZone(JS::FreshZone);
   309     compartmentOptions.setInvisibleToDebugger(true);
   310     compartmentOptions.setMergeable(true);
   312     // Don't falsely inherit the host's global trace hook.
   313     compartmentOptions.setTrace(nullptr);
   315     JSObject *global = JS_NewGlobalObject(cx, &workerGlobalClass, nullptr,
   316                                           JS::FireOnNewGlobalHook, compartmentOptions);
   317     if (!global)
   318         return false;
   320     JS_SetCompartmentPrincipals(global->compartment(), cx->compartment()->principals);
   322     RootedObject obj(cx);
   324     // Initialize all classes needed for parsing while we are still on the main
   325     // thread. Do this for both the target and the new global so that prototype
   326     // pointers can be changed infallibly after parsing finishes.
   327     if (!GetBuiltinConstructor(cx, JSProto_Function, &obj) ||
   328         !GetBuiltinConstructor(cx, JSProto_Array, &obj) ||
   329         !GetBuiltinConstructor(cx, JSProto_RegExp, &obj) ||
   330         !GetBuiltinConstructor(cx, JSProto_Iterator, &obj))
   331     {
   332         return false;
   333     }
   334     {
   335         AutoCompartment ac(cx, global);
   336         if (!GetBuiltinConstructor(cx, JSProto_Function, &obj) ||
   337             !GetBuiltinConstructor(cx, JSProto_Array, &obj) ||
   338             !GetBuiltinConstructor(cx, JSProto_RegExp, &obj) ||
   339             !GetBuiltinConstructor(cx, JSProto_Iterator, &obj))
   340         {
   341             return false;
   342         }
   343     }
   345     ScopedJSDeletePtr<ExclusiveContext> workercx(
   346         cx->new_<ExclusiveContext>(cx->runtime(), (PerThreadData *) nullptr,
   347                                    ThreadSafeContext::Context_Exclusive));
   348     if (!workercx)
   349         return false;
   351     ScopedJSDeletePtr<ParseTask> task(
   352         cx->new_<ParseTask>(workercx.get(), global, cx, chars, length,
   353                             callback, callbackData));
   354     if (!task)
   355         return false;
   357     workercx.forget();
   359     if (!task->init(cx, options))
   360         return false;
   362     if (OffThreadParsingMustWaitForGC(cx->runtime())) {
   363         AutoLockWorkerThreadState lock;
   364         if (!WorkerThreadState().parseWaitingOnGC().append(task.get()))
   365             return false;
   366     } else {
   367         task->activate(cx->runtime());
   369         AutoLockWorkerThreadState lock;
   371         if (!WorkerThreadState().parseWorklist().append(task.get()))
   372             return false;
   374         WorkerThreadState().notifyOne(GlobalWorkerThreadState::PRODUCER);
   375     }
   377     task.forget();
   379     return true;
   380 }
   382 void
   383 js::EnqueuePendingParseTasksAfterGC(JSRuntime *rt)
   384 {
   385     JS_ASSERT(!OffThreadParsingMustWaitForGC(rt));
   387     GlobalWorkerThreadState::ParseTaskVector newTasks;
   388     {
   389         AutoLockWorkerThreadState lock;
   390         GlobalWorkerThreadState::ParseTaskVector &waiting = WorkerThreadState().parseWaitingOnGC();
   392         for (size_t i = 0; i < waiting.length(); i++) {
   393             ParseTask *task = waiting[i];
   394             if (task->runtimeMatches(rt)) {
   395                 newTasks.append(task);
   396                 WorkerThreadState().remove(waiting, &i);
   397             }
   398         }
   399     }
   401     if (newTasks.empty())
   402         return;
   404     // This logic should mirror the contents of the !activeGCInAtomsZone()
   405     // branch in StartOffThreadParseScript:
   407     for (size_t i = 0; i < newTasks.length(); i++)
   408         newTasks[i]->activate(rt);
   410     AutoLockWorkerThreadState lock;
   412     for (size_t i = 0; i < newTasks.length(); i++)
   413         WorkerThreadState().parseWorklist().append(newTasks[i]);
   415     WorkerThreadState().notifyAll(GlobalWorkerThreadState::PRODUCER);
   416 }
   418 static const uint32_t WORKER_STACK_SIZE = 512 * 1024;
   419 static const uint32_t WORKER_STACK_QUOTA = 450 * 1024;
   421 void
   422 GlobalWorkerThreadState::ensureInitialized()
   423 {
   424     JS_ASSERT(this == &WorkerThreadState());
   425     AutoLockWorkerThreadState lock;
   427     if (threads)
   428         return;
   430     threads = js_pod_calloc<WorkerThread>(threadCount);
   431     if (!threads)
   432         CrashAtUnhandlableOOM("GlobalWorkerThreadState::ensureInitialized");
   434     for (size_t i = 0; i < threadCount; i++) {
   435         WorkerThread &helper = threads[i];
   436         helper.threadData.construct(static_cast<JSRuntime *>(nullptr));
   437         helper.thread = PR_CreateThread(PR_USER_THREAD,
   438                                         WorkerThread::ThreadMain, &helper,
   439                                         PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, WORKER_STACK_SIZE);
   440         if (!helper.thread || !helper.threadData.ref().init())
   441             CrashAtUnhandlableOOM("GlobalWorkerThreadState::ensureInitialized");
   442     }
   444     resetAsmJSFailureState();
   445 }
   447 GlobalWorkerThreadState::GlobalWorkerThreadState()
   448 {
   449     mozilla::PodZero(this);
   451     cpuCount = GetCPUCount();
   452     threadCount = ThreadCountForCPUCount(cpuCount);
   454     MOZ_ASSERT(cpuCount > 0, "GetCPUCount() seems broken");
   456     workerLock = PR_NewLock();
   457     consumerWakeup = PR_NewCondVar(workerLock);
   458     producerWakeup = PR_NewCondVar(workerLock);
   459 }
   461 void
   462 GlobalWorkerThreadState::finish()
   463 {
   464     if (threads) {
   465         for (size_t i = 0; i < threadCount; i++)
   466             threads[i].destroy();
   467         js_free(threads);
   468     }
   470     PR_DestroyCondVar(consumerWakeup);
   471     PR_DestroyCondVar(producerWakeup);
   472     PR_DestroyLock(workerLock);
   473 }
   475 void
   476 GlobalWorkerThreadState::lock()
   477 {
   478     JS_ASSERT(!isLocked());
   479     AssertCurrentThreadCanLock(WorkerThreadStateLock);
   480     PR_Lock(workerLock);
   481 #ifdef DEBUG
   482     lockOwner = PR_GetCurrentThread();
   483 #endif
   484 }
   486 void
   487 GlobalWorkerThreadState::unlock()
   488 {
   489     JS_ASSERT(isLocked());
   490 #ifdef DEBUG
   491     lockOwner = nullptr;
   492 #endif
   493     PR_Unlock(workerLock);
   494 }
   496 #ifdef DEBUG
   497 bool
   498 GlobalWorkerThreadState::isLocked()
   499 {
   500     return lockOwner == PR_GetCurrentThread();
   501 }
   502 #endif
   504 void
   505 GlobalWorkerThreadState::wait(CondVar which, uint32_t millis)
   506 {
   507     JS_ASSERT(isLocked());
   508 #ifdef DEBUG
   509     lockOwner = nullptr;
   510 #endif
   511     DebugOnly<PRStatus> status =
   512         PR_WaitCondVar((which == CONSUMER) ? consumerWakeup : producerWakeup,
   513                        millis ? PR_MillisecondsToInterval(millis) : PR_INTERVAL_NO_TIMEOUT);
   514     JS_ASSERT(status == PR_SUCCESS);
   515 #ifdef DEBUG
   516     lockOwner = PR_GetCurrentThread();
   517 #endif
   518 }
   520 void
   521 GlobalWorkerThreadState::notifyAll(CondVar which)
   522 {
   523     JS_ASSERT(isLocked());
   524     PR_NotifyAllCondVar((which == CONSUMER) ? consumerWakeup : producerWakeup);
   525 }
   527 void
   528 GlobalWorkerThreadState::notifyOne(CondVar which)
   529 {
   530     JS_ASSERT(isLocked());
   531     PR_NotifyCondVar((which == CONSUMER) ? consumerWakeup : producerWakeup);
   532 }
   534 bool
   535 GlobalWorkerThreadState::canStartAsmJSCompile()
   536 {
   537     // Don't execute an AsmJS job if an earlier one failed.
   538     JS_ASSERT(isLocked());
   539     return !asmJSWorklist().empty() && !numAsmJSFailedJobs;
   540 }
   542 bool
   543 GlobalWorkerThreadState::canStartIonCompile()
   544 {
   545     // A worker thread can begin an Ion compilation if (a) there is some script
   546     // which is waiting to be compiled, and (b) no other worker thread is
   547     // currently compiling a script. The latter condition ensures that two
   548     // compilations cannot simultaneously occur.
   549     if (ionWorklist().empty())
   550         return false;
   551     for (size_t i = 0; i < threadCount; i++) {
   552         if (threads[i].ionBuilder)
   553             return false;
   554     }
   555     return true;
   556 }
   558 bool
   559 GlobalWorkerThreadState::canStartParseTask()
   560 {
   561     // Don't allow simultaneous off thread parses, to reduce contention on the
   562     // atoms table. Note that asm.js compilation depends on this to avoid
   563     // stalling the worker thread, as off thread parse tasks can trigger and
   564     // block on other off thread asm.js compilation tasks.
   565     JS_ASSERT(isLocked());
   566     if (parseWorklist().empty())
   567         return false;
   568     for (size_t i = 0; i < threadCount; i++) {
   569         if (threads[i].parseTask)
   570             return false;
   571     }
   572     return true;
   573 }
   575 bool
   576 GlobalWorkerThreadState::canStartCompressionTask()
   577 {
   578     return !compressionWorklist().empty();
   579 }
   581 static void
   582 CallNewScriptHookForAllScripts(JSContext *cx, HandleScript script)
   583 {
   584     // We should never hit this, since nested scripts are also constructed via
   585     // BytecodeEmitter instances on the stack.
   586     JS_CHECK_RECURSION(cx, return);
   588     // Recurse to any nested scripts.
   589     if (script->hasObjects()) {
   590         ObjectArray *objects = script->objects();
   591         for (size_t i = 0; i < objects->length; i++) {
   592             JSObject *obj = objects->vector[i];
   593             if (obj->is<JSFunction>()) {
   594                 JSFunction *fun = &obj->as<JSFunction>();
   595                 if (fun->hasScript()) {
   596                     RootedScript nested(cx, fun->nonLazyScript());
   597                     CallNewScriptHookForAllScripts(cx, nested);
   598                 }
   599             }
   600         }
   601     }
   603     // The global new script hook is called on every script that was compiled.
   604     RootedFunction function(cx, script->functionNonDelazifying());
   605     CallNewScriptHook(cx, script, function);
   606 }
   608 static void
   609 LeaveParseTaskZone(JSRuntime *rt, ParseTask *task)
   610 {
   611     // Mark the zone as no longer in use by an ExclusiveContext, and available
   612     // to be collected by the GC.
   613     rt->clearUsedByExclusiveThread(task->cx->zone());
   614 }
   616 JSScript *
   617 GlobalWorkerThreadState::finishParseTask(JSContext *maybecx, JSRuntime *rt, void *token)
   618 {
   619     ScopedJSDeletePtr<ParseTask> parseTask;
   621     // The token is a ParseTask* which should be in the finished list.
   622     // Find and remove its entry.
   623     {
   624         AutoLockWorkerThreadState lock;
   625         ParseTaskVector &finished = parseFinishedList();
   626         for (size_t i = 0; i < finished.length(); i++) {
   627             if (finished[i] == token) {
   628                 parseTask = finished[i];
   629                 remove(finished, &i);
   630                 break;
   631             }
   632         }
   633     }
   634     JS_ASSERT(parseTask);
   636     if (!maybecx) {
   637         LeaveParseTaskZone(rt, parseTask);
   638         return nullptr;
   639     }
   641     JSContext *cx = maybecx;
   642     JS_ASSERT(cx->compartment());
   644     // Make sure we have all the constructors we need for the prototype
   645     // remapping below, since we can't GC while that's happening.
   646     Rooted<GlobalObject*> global(cx, &cx->global()->as<GlobalObject>());
   647     if (!GlobalObject::ensureConstructor(cx, global, JSProto_Object) ||
   648         !GlobalObject::ensureConstructor(cx, global, JSProto_Array) ||
   649         !GlobalObject::ensureConstructor(cx, global, JSProto_Function) ||
   650         !GlobalObject::ensureConstructor(cx, global, JSProto_RegExp) ||
   651         !GlobalObject::ensureConstructor(cx, global, JSProto_Iterator))
   652     {
   653         LeaveParseTaskZone(rt, parseTask);
   654         return nullptr;
   655     }
   657     LeaveParseTaskZone(rt, parseTask);
   659     // Point the prototypes of any objects in the script's compartment to refer
   660     // to the corresponding prototype in the new compartment. This will briefly
   661     // create cross compartment pointers, which will be fixed by the
   662     // MergeCompartments call below.
   663     for (gc::CellIter iter(parseTask->cx->zone(), gc::FINALIZE_TYPE_OBJECT);
   664          !iter.done();
   665          iter.next())
   666     {
   667         types::TypeObject *object = iter.get<types::TypeObject>();
   668         TaggedProto proto(object->proto());
   669         if (!proto.isObject())
   670             continue;
   672         JSProtoKey key = JS::IdentifyStandardPrototype(proto.toObject());
   673         if (key == JSProto_Null)
   674             continue;
   675         JS_ASSERT(key == JSProto_Object || key == JSProto_Array ||
   676                   key == JSProto_Function || key == JSProto_RegExp ||
   677                   key == JSProto_Iterator);
   679         JSObject *newProto = GetBuiltinPrototypePure(global, key);
   680         JS_ASSERT(newProto);
   682         object->setProtoUnchecked(newProto);
   683     }
   685     // Move the parsed script and all its contents into the desired compartment.
   686     gc::MergeCompartments(parseTask->cx->compartment(), cx->compartment());
   687     parseTask->finish();
   689     RootedScript script(rt, parseTask->script);
   690     assertSameCompartment(cx, script);
   692     // Report any error or warnings generated during the parse, and inform the
   693     // debugger about the compiled scripts.
   694     for (size_t i = 0; i < parseTask->errors.length(); i++)
   695         parseTask->errors[i]->throwError(cx);
   696     if (parseTask->overRecursed)
   697         js_ReportOverRecursed(cx);
   699     if (script) {
   700         // The Debugger only needs to be told about the topmost script that was compiled.
   701         GlobalObject *compileAndGoGlobal = nullptr;
   702         if (script->compileAndGo())
   703             compileAndGoGlobal = &script->global();
   704         Debugger::onNewScript(cx, script, compileAndGoGlobal);
   706         // The NewScript hook needs to be called for all compiled scripts.
   707         CallNewScriptHookForAllScripts(cx, script);
   708     }
   710     return script;
   711 }
   713 void
   714 WorkerThread::destroy()
   715 {
   716     if (thread) {
   717         {
   718             AutoLockWorkerThreadState lock;
   719             terminate = true;
   721             /* Notify all workers, to ensure that this thread wakes up. */
   722             WorkerThreadState().notifyAll(GlobalWorkerThreadState::PRODUCER);
   723         }
   725         PR_JoinThread(thread);
   726     }
   728     if (!threadData.empty())
   729         threadData.destroy();
   730 }
   732 /* static */
   733 void
   734 WorkerThread::ThreadMain(void *arg)
   735 {
   736     PR_SetCurrentThreadName("Analysis Helper");
   737     static_cast<WorkerThread *>(arg)->threadLoop();
   738 }
   740 void
   741 WorkerThread::handleAsmJSWorkload()
   742 {
   743 #ifdef JS_ION
   744     JS_ASSERT(WorkerThreadState().isLocked());
   745     JS_ASSERT(WorkerThreadState().canStartAsmJSCompile());
   746     JS_ASSERT(idle());
   748     asmData = WorkerThreadState().asmJSWorklist().popCopy();
   749     bool success = false;
   751     do {
   752         AutoUnlockWorkerThreadState unlock;
   753         PerThreadData::AutoEnterRuntime enter(threadData.addr(), asmData->runtime);
   755         jit::IonContext icx(asmData->mir->compartment->runtime(),
   756                             asmData->mir->compartment,
   757                             &asmData->mir->alloc());
   759         int64_t before = PRMJ_Now();
   761         if (!OptimizeMIR(asmData->mir))
   762             break;
   764         asmData->lir = GenerateLIR(asmData->mir);
   765         if (!asmData->lir)
   766             break;
   768         int64_t after = PRMJ_Now();
   769         asmData->compileTime = (after - before) / PRMJ_USEC_PER_MSEC;
   771         success = true;
   772     } while(0);
   774     // On failure, signal parent for harvesting in CancelOutstandingJobs().
   775     if (!success) {
   776         WorkerThreadState().noteAsmJSFailure(asmData->func);
   777         WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER);
   778         asmData = nullptr;
   779         return;
   780     }
   782     // On success, move work to the finished list.
   783     WorkerThreadState().asmJSFinishedList().append(asmData);
   784     asmData = nullptr;
   786     // Notify the main thread in case it's blocked waiting for a LifoAlloc.
   787     WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER);
   788 #else
   789     MOZ_CRASH();
   790 #endif // JS_ION
   791 }
   793 void
   794 WorkerThread::handleIonWorkload()
   795 {
   796 #ifdef JS_ION
   797     JS_ASSERT(WorkerThreadState().isLocked());
   798     JS_ASSERT(WorkerThreadState().canStartIonCompile());
   799     JS_ASSERT(idle());
   801     ionBuilder = WorkerThreadState().ionWorklist().popCopy();
   803     TraceLogger *logger = TraceLoggerForCurrentThread();
   804     AutoTraceLog logScript(logger, TraceLogCreateTextId(logger, ionBuilder->script()));
   805     AutoTraceLog logCompile(logger, TraceLogger::IonCompilation);
   807     JSRuntime *rt = ionBuilder->script()->compartment()->runtimeFromAnyThread();
   809     {
   810         AutoUnlockWorkerThreadState unlock;
   811         PerThreadData::AutoEnterRuntime enter(threadData.addr(),
   812                                               ionBuilder->script()->runtimeFromAnyThread());
   813         jit::IonContext ictx(jit::CompileRuntime::get(rt),
   814                              jit::CompileCompartment::get(ionBuilder->script()->compartment()),
   815                              &ionBuilder->alloc());
   816         ionBuilder->setBackgroundCodegen(jit::CompileBackEnd(ionBuilder));
   817     }
   819     FinishOffThreadIonCompile(ionBuilder);
   820     ionBuilder = nullptr;
   822     // Ping the main thread so that the compiled code can be incorporated
   823     // at the next interrupt callback. Don't interrupt Ion code for this, as
   824     // this incorporation can be delayed indefinitely without affecting
   825     // performance as long as the main thread is actually executing Ion code.
   826     rt->requestInterrupt(JSRuntime::RequestInterruptAnyThreadDontStopIon);
   828     // Notify the main thread in case it is waiting for the compilation to finish.
   829     WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER);
   830 #else
   831     MOZ_CRASH();
   832 #endif // JS_ION
   833 }
   835 void
   836 ExclusiveContext::setWorkerThread(WorkerThread *workerThread)
   837 {
   838     workerThread_ = workerThread;
   839     perThreadData = workerThread->threadData.addr();
   840 }
   842 frontend::CompileError &
   843 ExclusiveContext::addPendingCompileError()
   844 {
   845     frontend::CompileError *error = js_new<frontend::CompileError>();
   846     if (!error)
   847         MOZ_CRASH();
   848     if (!workerThread()->parseTask->errors.append(error))
   849         MOZ_CRASH();
   850     return *error;
   851 }
   853 void
   854 ExclusiveContext::addPendingOverRecursed()
   855 {
   856     if (workerThread()->parseTask)
   857         workerThread()->parseTask->overRecursed = true;
   858 }
   860 void
   861 WorkerThread::handleParseWorkload()
   862 {
   863     JS_ASSERT(WorkerThreadState().isLocked());
   864     JS_ASSERT(WorkerThreadState().canStartParseTask());
   865     JS_ASSERT(idle());
   867     parseTask = WorkerThreadState().parseWorklist().popCopy();
   868     parseTask->cx->setWorkerThread(this);
   870     {
   871         AutoUnlockWorkerThreadState unlock;
   872         PerThreadData::AutoEnterRuntime enter(threadData.addr(),
   873                                               parseTask->exclusiveContextGlobal->runtimeFromAnyThread());
   874         SourceBufferHolder srcBuf(parseTask->chars, parseTask->length,
   875                                   SourceBufferHolder::NoOwnership);
   876         parseTask->script = frontend::CompileScript(parseTask->cx, &parseTask->alloc,
   877                                                     NullPtr(), NullPtr(),
   878                                                     parseTask->options,
   879                                                     srcBuf);
   880     }
   882     // The callback is invoked while we are still off the main thread.
   883     parseTask->callback(parseTask, parseTask->callbackData);
   885     // FinishOffThreadScript will need to be called on the script to
   886     // migrate it into the correct compartment.
   887     WorkerThreadState().parseFinishedList().append(parseTask);
   889     parseTask = nullptr;
   891     // Notify the main thread in case it is waiting for the parse/emit to finish.
   892     WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER);
   893 }
   895 void
   896 WorkerThread::handleCompressionWorkload()
   897 {
   898     JS_ASSERT(WorkerThreadState().isLocked());
   899     JS_ASSERT(WorkerThreadState().canStartCompressionTask());
   900     JS_ASSERT(idle());
   902     compressionTask = WorkerThreadState().compressionWorklist().popCopy();
   903     compressionTask->workerThread = this;
   905     {
   906         AutoUnlockWorkerThreadState unlock;
   907         if (!compressionTask->work())
   908             compressionTask->setOOM();
   909     }
   911     compressionTask->workerThread = nullptr;
   912     compressionTask = nullptr;
   914     // Notify the main thread in case it is waiting for the compression to finish.
   915     WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER);
   916 }
   918 bool
   919 js::StartOffThreadCompression(ExclusiveContext *cx, SourceCompressionTask *task)
   920 {
   921     EnsureWorkerThreadsInitialized(cx);
   923     AutoLockWorkerThreadState lock;
   925     if (!WorkerThreadState().compressionWorklist().append(task))
   926         return false;
   928     WorkerThreadState().notifyOne(GlobalWorkerThreadState::PRODUCER);
   929     return true;
   930 }
   932 bool
   933 GlobalWorkerThreadState::compressionInProgress(SourceCompressionTask *task)
   934 {
   935     JS_ASSERT(isLocked());
   936     for (size_t i = 0; i < compressionWorklist().length(); i++) {
   937         if (compressionWorklist()[i] == task)
   938             return true;
   939     }
   940     for (size_t i = 0; i < threadCount; i++) {
   941         if (threads[i].compressionTask == task)
   942             return true;
   943     }
   944     return false;
   945 }
   947 bool
   948 SourceCompressionTask::complete()
   949 {
   950     JS_ASSERT_IF(!ss, !chars);
   951     if (active()) {
   952         AutoLockWorkerThreadState lock;
   954         while (WorkerThreadState().compressionInProgress(this))
   955             WorkerThreadState().wait(GlobalWorkerThreadState::CONSUMER);
   957         ss->ready_ = true;
   959         // Update memory accounting.
   960         if (!oom)
   961             cx->updateMallocCounter(ss->computedSizeOfData());
   963         ss = nullptr;
   964         chars = nullptr;
   965     }
   966     if (oom) {
   967         js_ReportOutOfMemory(cx);
   968         return false;
   969     }
   970     return true;
   971 }
   973 SourceCompressionTask *
   974 GlobalWorkerThreadState::compressionTaskForSource(ScriptSource *ss)
   975 {
   976     JS_ASSERT(isLocked());
   977     for (size_t i = 0; i < compressionWorklist().length(); i++) {
   978         SourceCompressionTask *task = compressionWorklist()[i];
   979         if (task->source() == ss)
   980             return task;
   981     }
   982     for (size_t i = 0; i < threadCount; i++) {
   983         SourceCompressionTask *task = threads[i].compressionTask;
   984         if (task && task->source() == ss)
   985             return task;
   986     }
   987     return nullptr;
   988 }
   990 const jschar *
   991 ScriptSource::getOffThreadCompressionChars(ExclusiveContext *cx)
   992 {
   993     // If this is being compressed off thread, return its uncompressed chars.
   995     if (ready()) {
   996         // Compression has already finished on the source.
   997         return nullptr;
   998     }
  1000     AutoLockWorkerThreadState lock;
  1002     // Look for a token that hasn't finished compressing and whose source is
  1003     // the given ScriptSource.
  1004     if (SourceCompressionTask *task = WorkerThreadState().compressionTaskForSource(this))
  1005         return task->uncompressedChars();
  1007     // Compressing has finished, so this ScriptSource is ready. Avoid future
  1008     // queries on the worker thread state when getting the chars.
  1009     ready_ = true;
  1011     return nullptr;
  1014 void
  1015 WorkerThread::threadLoop()
  1017     JS::AutoAssertNoGC nogc;
  1018     AutoLockWorkerThreadState lock;
  1020     js::TlsPerThreadData.set(threadData.addr());
  1022     // Compute the thread's stack limit, for over-recursed checks.
  1023     uintptr_t stackLimit = GetNativeStackBase();
  1024 #if JS_STACK_GROWTH_DIRECTION > 0
  1025     stackLimit += WORKER_STACK_QUOTA;
  1026 #else
  1027     stackLimit -= WORKER_STACK_QUOTA;
  1028 #endif
  1029     for (size_t i = 0; i < ArrayLength(threadData.ref().nativeStackLimit); i++)
  1030         threadData.ref().nativeStackLimit[i] = stackLimit;
  1032     while (true) {
  1033         JS_ASSERT(!ionBuilder && !asmData);
  1035         // Block until a task is available.
  1036         while (true) {
  1037             if (terminate)
  1038                 return;
  1039             if (WorkerThreadState().canStartIonCompile() ||
  1040                 WorkerThreadState().canStartAsmJSCompile() ||
  1041                 WorkerThreadState().canStartParseTask() ||
  1042                 WorkerThreadState().canStartCompressionTask())
  1044                 break;
  1046             WorkerThreadState().wait(GlobalWorkerThreadState::PRODUCER);
  1049         // Dispatch tasks, prioritizing AsmJS work.
  1050         if (WorkerThreadState().canStartAsmJSCompile())
  1051             handleAsmJSWorkload();
  1052         else if (WorkerThreadState().canStartIonCompile())
  1053             handleIonWorkload();
  1054         else if (WorkerThreadState().canStartParseTask())
  1055             handleParseWorkload();
  1056         else if (WorkerThreadState().canStartCompressionTask())
  1057             handleCompressionWorkload();
  1058         else
  1059             MOZ_ASSUME_UNREACHABLE("No task to perform");
  1063 #else /* JS_THREADSAFE */
  1065 using namespace js;
  1067 #ifdef JS_ION
  1069 bool
  1070 js::StartOffThreadAsmJSCompile(ExclusiveContext *cx, AsmJSParallelTask *asmData)
  1072     MOZ_ASSUME_UNREACHABLE("Off thread compilation not available in non-THREADSAFE builds");
  1075 bool
  1076 js::StartOffThreadIonCompile(JSContext *cx, jit::IonBuilder *builder)
  1078     MOZ_ASSUME_UNREACHABLE("Off thread compilation not available in non-THREADSAFE builds");
  1081 #endif // JS_ION
  1083 void
  1084 js::CancelOffThreadIonCompile(JSCompartment *compartment, JSScript *script)
  1088 void
  1089 js::CancelOffThreadParses(JSRuntime *rt)
  1093 bool
  1094 js::StartOffThreadParseScript(JSContext *cx, const ReadOnlyCompileOptions &options,
  1095                               const jschar *chars, size_t length,
  1096                               JS::OffThreadCompileCallback callback, void *callbackData)
  1098     MOZ_ASSUME_UNREACHABLE("Off thread compilation not available in non-THREADSAFE builds");
  1101 bool
  1102 js::StartOffThreadCompression(ExclusiveContext *cx, SourceCompressionTask *task)
  1104     MOZ_ASSUME_UNREACHABLE("Off thread compression not available");
  1107 bool
  1108 SourceCompressionTask::complete()
  1110     JS_ASSERT(!active() && !oom);
  1111     return true;
  1114 const jschar *
  1115 ScriptSource::getOffThreadCompressionChars(ExclusiveContext *cx)
  1117     JS_ASSERT(ready());
  1118     return nullptr;
  1121 frontend::CompileError &
  1122 ExclusiveContext::addPendingCompileError()
  1124     MOZ_ASSUME_UNREACHABLE("Off thread compilation not available.");
  1127 void
  1128 ExclusiveContext::addPendingOverRecursed()
  1130     MOZ_ASSUME_UNREACHABLE("Off thread compilation not available.");
  1133 #endif /* JS_THREADSAFE */

mercurial