1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/js/src/jsworkers.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1133 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- 1.5 + * vim: set ts=8 sts=4 et sw=4 tw=99: 1.6 + * This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "jsworkers.h" 1.11 + 1.12 +#ifdef JS_THREADSAFE 1.13 + 1.14 +#include "mozilla/DebugOnly.h" 1.15 + 1.16 +#include "jsnativestack.h" 1.17 +#include "prmjtime.h" 1.18 + 1.19 +#include "frontend/BytecodeCompiler.h" 1.20 +#include "jit/IonBuilder.h" 1.21 +#include "vm/Debugger.h" 1.22 +#include "vm/TraceLogging.h" 1.23 + 1.24 +#include "jscntxtinlines.h" 1.25 +#include "jscompartmentinlines.h" 1.26 +#include "jsobjinlines.h" 1.27 +#include "jsscriptinlines.h" 1.28 + 1.29 +using namespace js; 1.30 + 1.31 +using mozilla::ArrayLength; 1.32 +using mozilla::DebugOnly; 1.33 + 1.34 +namespace js { 1.35 + 1.36 +GlobalWorkerThreadState gWorkerThreadState; 1.37 + 1.38 +} // namespace js 1.39 + 1.40 +void 1.41 +js::EnsureWorkerThreadsInitialized(ExclusiveContext *cx) 1.42 +{ 1.43 + // If 'cx' is not a JSContext, we are already off the main thread and the 1.44 + // worker threads would have already been initialized. 1.45 + if (!cx->isJSContext()) 1.46 + return; 1.47 + 1.48 + WorkerThreadState().ensureInitialized(); 1.49 +} 1.50 + 1.51 +static size_t 1.52 +ThreadCountForCPUCount(size_t cpuCount) 1.53 +{ 1.54 + return Max(cpuCount, (size_t)2); 1.55 +} 1.56 + 1.57 +void 1.58 +js::SetFakeCPUCount(size_t count) 1.59 +{ 1.60 + // This must be called before the threads have been initialized. 1.61 + JS_ASSERT(!WorkerThreadState().threads); 1.62 + 1.63 + WorkerThreadState().cpuCount = count; 1.64 + WorkerThreadState().threadCount = ThreadCountForCPUCount(count); 1.65 +} 1.66 + 1.67 +#ifdef JS_ION 1.68 + 1.69 +bool 1.70 +js::StartOffThreadAsmJSCompile(ExclusiveContext *cx, AsmJSParallelTask *asmData) 1.71 +{ 1.72 + // Threads already initialized by the AsmJS compiler. 1.73 + JS_ASSERT(asmData->mir); 1.74 + JS_ASSERT(asmData->lir == nullptr); 1.75 + 1.76 + AutoLockWorkerThreadState lock; 1.77 + 1.78 + // Don't append this task if another failed. 1.79 + if (WorkerThreadState().asmJSWorkerFailed()) 1.80 + return false; 1.81 + 1.82 + if (!WorkerThreadState().asmJSWorklist().append(asmData)) 1.83 + return false; 1.84 + 1.85 + WorkerThreadState().notifyOne(GlobalWorkerThreadState::PRODUCER); 1.86 + return true; 1.87 +} 1.88 + 1.89 +bool 1.90 +js::StartOffThreadIonCompile(JSContext *cx, jit::IonBuilder *builder) 1.91 +{ 1.92 + EnsureWorkerThreadsInitialized(cx); 1.93 + 1.94 + AutoLockWorkerThreadState lock; 1.95 + 1.96 + if (!WorkerThreadState().ionWorklist().append(builder)) 1.97 + return false; 1.98 + 1.99 + WorkerThreadState().notifyOne(GlobalWorkerThreadState::PRODUCER); 1.100 + return true; 1.101 +} 1.102 + 1.103 +/* 1.104 + * Move an IonBuilder for which compilation has either finished, failed, or 1.105 + * been cancelled into the global finished compilation list. All off thread 1.106 + * compilations which are started must eventually be finished. 1.107 + */ 1.108 +static void 1.109 +FinishOffThreadIonCompile(jit::IonBuilder *builder) 1.110 +{ 1.111 + WorkerThreadState().ionFinishedList().append(builder); 1.112 +} 1.113 + 1.114 +#endif // JS_ION 1.115 + 1.116 +static inline bool 1.117 +CompiledScriptMatches(JSCompartment *compartment, JSScript *script, JSScript *target) 1.118 +{ 1.119 + if (script) 1.120 + return target == script; 1.121 + return target->compartment() == compartment; 1.122 +} 1.123 + 1.124 +void 1.125 +js::CancelOffThreadIonCompile(JSCompartment *compartment, JSScript *script) 1.126 +{ 1.127 +#ifdef JS_ION 1.128 + jit::JitCompartment *jitComp = compartment->jitCompartment(); 1.129 + if (!jitComp) 1.130 + return; 1.131 + 1.132 + AutoLockWorkerThreadState lock; 1.133 + 1.134 + if (!WorkerThreadState().threads) 1.135 + return; 1.136 + 1.137 + /* Cancel any pending entries for which processing hasn't started. */ 1.138 + GlobalWorkerThreadState::IonBuilderVector &worklist = WorkerThreadState().ionWorklist(); 1.139 + for (size_t i = 0; i < worklist.length(); i++) { 1.140 + jit::IonBuilder *builder = worklist[i]; 1.141 + if (CompiledScriptMatches(compartment, script, builder->script())) { 1.142 + FinishOffThreadIonCompile(builder); 1.143 + WorkerThreadState().remove(worklist, &i); 1.144 + } 1.145 + } 1.146 + 1.147 + /* Wait for in progress entries to finish up. */ 1.148 + for (size_t i = 0; i < WorkerThreadState().threadCount; i++) { 1.149 + const WorkerThread &helper = WorkerThreadState().threads[i]; 1.150 + while (helper.ionBuilder && 1.151 + CompiledScriptMatches(compartment, script, helper.ionBuilder->script())) 1.152 + { 1.153 + helper.ionBuilder->cancel(); 1.154 + WorkerThreadState().wait(GlobalWorkerThreadState::CONSUMER); 1.155 + } 1.156 + } 1.157 + 1.158 + /* Cancel code generation for any completed entries. */ 1.159 + GlobalWorkerThreadState::IonBuilderVector &finished = WorkerThreadState().ionFinishedList(); 1.160 + for (size_t i = 0; i < finished.length(); i++) { 1.161 + jit::IonBuilder *builder = finished[i]; 1.162 + if (CompiledScriptMatches(compartment, script, builder->script())) { 1.163 + jit::FinishOffThreadBuilder(builder); 1.164 + WorkerThreadState().remove(finished, &i); 1.165 + } 1.166 + } 1.167 +#endif // JS_ION 1.168 +} 1.169 + 1.170 +static const JSClass workerGlobalClass = { 1.171 + "internal-worker-global", JSCLASS_GLOBAL_FLAGS, 1.172 + JS_PropertyStub, JS_DeletePropertyStub, 1.173 + JS_PropertyStub, JS_StrictPropertyStub, 1.174 + JS_EnumerateStub, JS_ResolveStub, 1.175 + JS_ConvertStub, nullptr, 1.176 + nullptr, nullptr, nullptr, 1.177 + JS_GlobalObjectTraceHook 1.178 +}; 1.179 + 1.180 +ParseTask::ParseTask(ExclusiveContext *cx, JSObject *exclusiveContextGlobal, JSContext *initCx, 1.181 + const jschar *chars, size_t length, 1.182 + JS::OffThreadCompileCallback callback, void *callbackData) 1.183 + : cx(cx), options(initCx), chars(chars), length(length), 1.184 + alloc(JSRuntime::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE), 1.185 + exclusiveContextGlobal(initCx, exclusiveContextGlobal), optionsElement(initCx), 1.186 + optionsIntroductionScript(initCx), callback(callback), callbackData(callbackData), 1.187 + script(nullptr), errors(cx), overRecursed(false) 1.188 +{ 1.189 +} 1.190 + 1.191 +bool 1.192 +ParseTask::init(JSContext *cx, const ReadOnlyCompileOptions &options) 1.193 +{ 1.194 + if (!this->options.copy(cx, options)) 1.195 + return false; 1.196 + 1.197 + // Save those compilation options that the ScriptSourceObject can't 1.198 + // point at while it's in the compilation's temporary compartment. 1.199 + optionsElement = this->options.element(); 1.200 + this->options.setElement(nullptr); 1.201 + optionsIntroductionScript = this->options.introductionScript(); 1.202 + this->options.setIntroductionScript(nullptr); 1.203 + 1.204 + return true; 1.205 +} 1.206 + 1.207 +void 1.208 +ParseTask::activate(JSRuntime *rt) 1.209 +{ 1.210 + rt->setUsedByExclusiveThread(exclusiveContextGlobal->zone()); 1.211 + cx->enterCompartment(exclusiveContextGlobal->compartment()); 1.212 +} 1.213 + 1.214 +void 1.215 +ParseTask::finish() 1.216 +{ 1.217 + if (script) { 1.218 + // Initialize the ScriptSourceObject slots that we couldn't while the SSO 1.219 + // was in the temporary compartment. 1.220 + ScriptSourceObject &sso = script->sourceObject()->as<ScriptSourceObject>(); 1.221 + sso.initElement(optionsElement); 1.222 + sso.initIntroductionScript(optionsIntroductionScript); 1.223 + } 1.224 +} 1.225 + 1.226 +ParseTask::~ParseTask() 1.227 +{ 1.228 + // ParseTask takes over ownership of its input exclusive context. 1.229 + js_delete(cx); 1.230 + 1.231 + for (size_t i = 0; i < errors.length(); i++) 1.232 + js_delete(errors[i]); 1.233 +} 1.234 + 1.235 +void 1.236 +js::CancelOffThreadParses(JSRuntime *rt) 1.237 +{ 1.238 + AutoLockWorkerThreadState lock; 1.239 + 1.240 + if (!WorkerThreadState().threads) 1.241 + return; 1.242 + 1.243 + // Instead of forcibly canceling pending parse tasks, just wait for all scheduled 1.244 + // and in progress ones to complete. Otherwise the final GC may not collect 1.245 + // everything due to zones being used off thread. 1.246 + while (true) { 1.247 + bool pending = false; 1.248 + GlobalWorkerThreadState::ParseTaskVector &worklist = WorkerThreadState().parseWorklist(); 1.249 + for (size_t i = 0; i < worklist.length(); i++) { 1.250 + ParseTask *task = worklist[i]; 1.251 + if (task->runtimeMatches(rt)) 1.252 + pending = true; 1.253 + } 1.254 + if (!pending) { 1.255 + bool inProgress = false; 1.256 + for (size_t i = 0; i < WorkerThreadState().threadCount; i++) { 1.257 + ParseTask *task = WorkerThreadState().threads[i].parseTask; 1.258 + if (task && task->runtimeMatches(rt)) 1.259 + inProgress = true; 1.260 + } 1.261 + if (!inProgress) 1.262 + break; 1.263 + } 1.264 + WorkerThreadState().wait(GlobalWorkerThreadState::CONSUMER); 1.265 + } 1.266 + 1.267 + // Clean up any parse tasks which haven't been finished by the main thread. 1.268 + GlobalWorkerThreadState::ParseTaskVector &finished = WorkerThreadState().parseFinishedList(); 1.269 + while (true) { 1.270 + bool found = false; 1.271 + for (size_t i = 0; i < finished.length(); i++) { 1.272 + ParseTask *task = finished[i]; 1.273 + if (task->runtimeMatches(rt)) { 1.274 + found = true; 1.275 + AutoUnlockWorkerThreadState unlock; 1.276 + WorkerThreadState().finishParseTask(/* maybecx = */ nullptr, rt, task); 1.277 + } 1.278 + } 1.279 + if (!found) 1.280 + break; 1.281 + } 1.282 +} 1.283 + 1.284 +bool 1.285 +js::OffThreadParsingMustWaitForGC(JSRuntime *rt) 1.286 +{ 1.287 + // Off thread parsing can't occur during incremental collections on the 1.288 + // atoms compartment, to avoid triggering barriers. (Outside the atoms 1.289 + // compartment, the compilation will use a new zone that is never 1.290 + // collected.) If an atoms-zone GC is in progress, hold off on executing the 1.291 + // parse task until the atoms-zone GC completes (see 1.292 + // EnqueuePendingParseTasksAfterGC). 1.293 + return rt->activeGCInAtomsZone(); 1.294 +} 1.295 + 1.296 +bool 1.297 +js::StartOffThreadParseScript(JSContext *cx, const ReadOnlyCompileOptions &options, 1.298 + const jschar *chars, size_t length, 1.299 + JS::OffThreadCompileCallback callback, void *callbackData) 1.300 +{ 1.301 + // Suppress GC so that calls below do not trigger a new incremental GC 1.302 + // which could require barriers on the atoms compartment. 1.303 + gc::AutoSuppressGC suppress(cx); 1.304 + 1.305 + SourceBufferHolder srcBuf(chars, length, SourceBufferHolder::NoOwnership); 1.306 + frontend::MaybeCallSourceHandler(cx, options, srcBuf); 1.307 + 1.308 + EnsureWorkerThreadsInitialized(cx); 1.309 + 1.310 + JS::CompartmentOptions compartmentOptions(cx->compartment()->options()); 1.311 + compartmentOptions.setZone(JS::FreshZone); 1.312 + compartmentOptions.setInvisibleToDebugger(true); 1.313 + compartmentOptions.setMergeable(true); 1.314 + 1.315 + // Don't falsely inherit the host's global trace hook. 1.316 + compartmentOptions.setTrace(nullptr); 1.317 + 1.318 + JSObject *global = JS_NewGlobalObject(cx, &workerGlobalClass, nullptr, 1.319 + JS::FireOnNewGlobalHook, compartmentOptions); 1.320 + if (!global) 1.321 + return false; 1.322 + 1.323 + JS_SetCompartmentPrincipals(global->compartment(), cx->compartment()->principals); 1.324 + 1.325 + RootedObject obj(cx); 1.326 + 1.327 + // Initialize all classes needed for parsing while we are still on the main 1.328 + // thread. Do this for both the target and the new global so that prototype 1.329 + // pointers can be changed infallibly after parsing finishes. 1.330 + if (!GetBuiltinConstructor(cx, JSProto_Function, &obj) || 1.331 + !GetBuiltinConstructor(cx, JSProto_Array, &obj) || 1.332 + !GetBuiltinConstructor(cx, JSProto_RegExp, &obj) || 1.333 + !GetBuiltinConstructor(cx, JSProto_Iterator, &obj)) 1.334 + { 1.335 + return false; 1.336 + } 1.337 + { 1.338 + AutoCompartment ac(cx, global); 1.339 + if (!GetBuiltinConstructor(cx, JSProto_Function, &obj) || 1.340 + !GetBuiltinConstructor(cx, JSProto_Array, &obj) || 1.341 + !GetBuiltinConstructor(cx, JSProto_RegExp, &obj) || 1.342 + !GetBuiltinConstructor(cx, JSProto_Iterator, &obj)) 1.343 + { 1.344 + return false; 1.345 + } 1.346 + } 1.347 + 1.348 + ScopedJSDeletePtr<ExclusiveContext> workercx( 1.349 + cx->new_<ExclusiveContext>(cx->runtime(), (PerThreadData *) nullptr, 1.350 + ThreadSafeContext::Context_Exclusive)); 1.351 + if (!workercx) 1.352 + return false; 1.353 + 1.354 + ScopedJSDeletePtr<ParseTask> task( 1.355 + cx->new_<ParseTask>(workercx.get(), global, cx, chars, length, 1.356 + callback, callbackData)); 1.357 + if (!task) 1.358 + return false; 1.359 + 1.360 + workercx.forget(); 1.361 + 1.362 + if (!task->init(cx, options)) 1.363 + return false; 1.364 + 1.365 + if (OffThreadParsingMustWaitForGC(cx->runtime())) { 1.366 + AutoLockWorkerThreadState lock; 1.367 + if (!WorkerThreadState().parseWaitingOnGC().append(task.get())) 1.368 + return false; 1.369 + } else { 1.370 + task->activate(cx->runtime()); 1.371 + 1.372 + AutoLockWorkerThreadState lock; 1.373 + 1.374 + if (!WorkerThreadState().parseWorklist().append(task.get())) 1.375 + return false; 1.376 + 1.377 + WorkerThreadState().notifyOne(GlobalWorkerThreadState::PRODUCER); 1.378 + } 1.379 + 1.380 + task.forget(); 1.381 + 1.382 + return true; 1.383 +} 1.384 + 1.385 +void 1.386 +js::EnqueuePendingParseTasksAfterGC(JSRuntime *rt) 1.387 +{ 1.388 + JS_ASSERT(!OffThreadParsingMustWaitForGC(rt)); 1.389 + 1.390 + GlobalWorkerThreadState::ParseTaskVector newTasks; 1.391 + { 1.392 + AutoLockWorkerThreadState lock; 1.393 + GlobalWorkerThreadState::ParseTaskVector &waiting = WorkerThreadState().parseWaitingOnGC(); 1.394 + 1.395 + for (size_t i = 0; i < waiting.length(); i++) { 1.396 + ParseTask *task = waiting[i]; 1.397 + if (task->runtimeMatches(rt)) { 1.398 + newTasks.append(task); 1.399 + WorkerThreadState().remove(waiting, &i); 1.400 + } 1.401 + } 1.402 + } 1.403 + 1.404 + if (newTasks.empty()) 1.405 + return; 1.406 + 1.407 + // This logic should mirror the contents of the !activeGCInAtomsZone() 1.408 + // branch in StartOffThreadParseScript: 1.409 + 1.410 + for (size_t i = 0; i < newTasks.length(); i++) 1.411 + newTasks[i]->activate(rt); 1.412 + 1.413 + AutoLockWorkerThreadState lock; 1.414 + 1.415 + for (size_t i = 0; i < newTasks.length(); i++) 1.416 + WorkerThreadState().parseWorklist().append(newTasks[i]); 1.417 + 1.418 + WorkerThreadState().notifyAll(GlobalWorkerThreadState::PRODUCER); 1.419 +} 1.420 + 1.421 +static const uint32_t WORKER_STACK_SIZE = 512 * 1024; 1.422 +static const uint32_t WORKER_STACK_QUOTA = 450 * 1024; 1.423 + 1.424 +void 1.425 +GlobalWorkerThreadState::ensureInitialized() 1.426 +{ 1.427 + JS_ASSERT(this == &WorkerThreadState()); 1.428 + AutoLockWorkerThreadState lock; 1.429 + 1.430 + if (threads) 1.431 + return; 1.432 + 1.433 + threads = js_pod_calloc<WorkerThread>(threadCount); 1.434 + if (!threads) 1.435 + CrashAtUnhandlableOOM("GlobalWorkerThreadState::ensureInitialized"); 1.436 + 1.437 + for (size_t i = 0; i < threadCount; i++) { 1.438 + WorkerThread &helper = threads[i]; 1.439 + helper.threadData.construct(static_cast<JSRuntime *>(nullptr)); 1.440 + helper.thread = PR_CreateThread(PR_USER_THREAD, 1.441 + WorkerThread::ThreadMain, &helper, 1.442 + PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, WORKER_STACK_SIZE); 1.443 + if (!helper.thread || !helper.threadData.ref().init()) 1.444 + CrashAtUnhandlableOOM("GlobalWorkerThreadState::ensureInitialized"); 1.445 + } 1.446 + 1.447 + resetAsmJSFailureState(); 1.448 +} 1.449 + 1.450 +GlobalWorkerThreadState::GlobalWorkerThreadState() 1.451 +{ 1.452 + mozilla::PodZero(this); 1.453 + 1.454 + cpuCount = GetCPUCount(); 1.455 + threadCount = ThreadCountForCPUCount(cpuCount); 1.456 + 1.457 + MOZ_ASSERT(cpuCount > 0, "GetCPUCount() seems broken"); 1.458 + 1.459 + workerLock = PR_NewLock(); 1.460 + consumerWakeup = PR_NewCondVar(workerLock); 1.461 + producerWakeup = PR_NewCondVar(workerLock); 1.462 +} 1.463 + 1.464 +void 1.465 +GlobalWorkerThreadState::finish() 1.466 +{ 1.467 + if (threads) { 1.468 + for (size_t i = 0; i < threadCount; i++) 1.469 + threads[i].destroy(); 1.470 + js_free(threads); 1.471 + } 1.472 + 1.473 + PR_DestroyCondVar(consumerWakeup); 1.474 + PR_DestroyCondVar(producerWakeup); 1.475 + PR_DestroyLock(workerLock); 1.476 +} 1.477 + 1.478 +void 1.479 +GlobalWorkerThreadState::lock() 1.480 +{ 1.481 + JS_ASSERT(!isLocked()); 1.482 + AssertCurrentThreadCanLock(WorkerThreadStateLock); 1.483 + PR_Lock(workerLock); 1.484 +#ifdef DEBUG 1.485 + lockOwner = PR_GetCurrentThread(); 1.486 +#endif 1.487 +} 1.488 + 1.489 +void 1.490 +GlobalWorkerThreadState::unlock() 1.491 +{ 1.492 + JS_ASSERT(isLocked()); 1.493 +#ifdef DEBUG 1.494 + lockOwner = nullptr; 1.495 +#endif 1.496 + PR_Unlock(workerLock); 1.497 +} 1.498 + 1.499 +#ifdef DEBUG 1.500 +bool 1.501 +GlobalWorkerThreadState::isLocked() 1.502 +{ 1.503 + return lockOwner == PR_GetCurrentThread(); 1.504 +} 1.505 +#endif 1.506 + 1.507 +void 1.508 +GlobalWorkerThreadState::wait(CondVar which, uint32_t millis) 1.509 +{ 1.510 + JS_ASSERT(isLocked()); 1.511 +#ifdef DEBUG 1.512 + lockOwner = nullptr; 1.513 +#endif 1.514 + DebugOnly<PRStatus> status = 1.515 + PR_WaitCondVar((which == CONSUMER) ? consumerWakeup : producerWakeup, 1.516 + millis ? PR_MillisecondsToInterval(millis) : PR_INTERVAL_NO_TIMEOUT); 1.517 + JS_ASSERT(status == PR_SUCCESS); 1.518 +#ifdef DEBUG 1.519 + lockOwner = PR_GetCurrentThread(); 1.520 +#endif 1.521 +} 1.522 + 1.523 +void 1.524 +GlobalWorkerThreadState::notifyAll(CondVar which) 1.525 +{ 1.526 + JS_ASSERT(isLocked()); 1.527 + PR_NotifyAllCondVar((which == CONSUMER) ? consumerWakeup : producerWakeup); 1.528 +} 1.529 + 1.530 +void 1.531 +GlobalWorkerThreadState::notifyOne(CondVar which) 1.532 +{ 1.533 + JS_ASSERT(isLocked()); 1.534 + PR_NotifyCondVar((which == CONSUMER) ? consumerWakeup : producerWakeup); 1.535 +} 1.536 + 1.537 +bool 1.538 +GlobalWorkerThreadState::canStartAsmJSCompile() 1.539 +{ 1.540 + // Don't execute an AsmJS job if an earlier one failed. 1.541 + JS_ASSERT(isLocked()); 1.542 + return !asmJSWorklist().empty() && !numAsmJSFailedJobs; 1.543 +} 1.544 + 1.545 +bool 1.546 +GlobalWorkerThreadState::canStartIonCompile() 1.547 +{ 1.548 + // A worker thread can begin an Ion compilation if (a) there is some script 1.549 + // which is waiting to be compiled, and (b) no other worker thread is 1.550 + // currently compiling a script. The latter condition ensures that two 1.551 + // compilations cannot simultaneously occur. 1.552 + if (ionWorklist().empty()) 1.553 + return false; 1.554 + for (size_t i = 0; i < threadCount; i++) { 1.555 + if (threads[i].ionBuilder) 1.556 + return false; 1.557 + } 1.558 + return true; 1.559 +} 1.560 + 1.561 +bool 1.562 +GlobalWorkerThreadState::canStartParseTask() 1.563 +{ 1.564 + // Don't allow simultaneous off thread parses, to reduce contention on the 1.565 + // atoms table. Note that asm.js compilation depends on this to avoid 1.566 + // stalling the worker thread, as off thread parse tasks can trigger and 1.567 + // block on other off thread asm.js compilation tasks. 1.568 + JS_ASSERT(isLocked()); 1.569 + if (parseWorklist().empty()) 1.570 + return false; 1.571 + for (size_t i = 0; i < threadCount; i++) { 1.572 + if (threads[i].parseTask) 1.573 + return false; 1.574 + } 1.575 + return true; 1.576 +} 1.577 + 1.578 +bool 1.579 +GlobalWorkerThreadState::canStartCompressionTask() 1.580 +{ 1.581 + return !compressionWorklist().empty(); 1.582 +} 1.583 + 1.584 +static void 1.585 +CallNewScriptHookForAllScripts(JSContext *cx, HandleScript script) 1.586 +{ 1.587 + // We should never hit this, since nested scripts are also constructed via 1.588 + // BytecodeEmitter instances on the stack. 1.589 + JS_CHECK_RECURSION(cx, return); 1.590 + 1.591 + // Recurse to any nested scripts. 1.592 + if (script->hasObjects()) { 1.593 + ObjectArray *objects = script->objects(); 1.594 + for (size_t i = 0; i < objects->length; i++) { 1.595 + JSObject *obj = objects->vector[i]; 1.596 + if (obj->is<JSFunction>()) { 1.597 + JSFunction *fun = &obj->as<JSFunction>(); 1.598 + if (fun->hasScript()) { 1.599 + RootedScript nested(cx, fun->nonLazyScript()); 1.600 + CallNewScriptHookForAllScripts(cx, nested); 1.601 + } 1.602 + } 1.603 + } 1.604 + } 1.605 + 1.606 + // The global new script hook is called on every script that was compiled. 1.607 + RootedFunction function(cx, script->functionNonDelazifying()); 1.608 + CallNewScriptHook(cx, script, function); 1.609 +} 1.610 + 1.611 +static void 1.612 +LeaveParseTaskZone(JSRuntime *rt, ParseTask *task) 1.613 +{ 1.614 + // Mark the zone as no longer in use by an ExclusiveContext, and available 1.615 + // to be collected by the GC. 1.616 + rt->clearUsedByExclusiveThread(task->cx->zone()); 1.617 +} 1.618 + 1.619 +JSScript * 1.620 +GlobalWorkerThreadState::finishParseTask(JSContext *maybecx, JSRuntime *rt, void *token) 1.621 +{ 1.622 + ScopedJSDeletePtr<ParseTask> parseTask; 1.623 + 1.624 + // The token is a ParseTask* which should be in the finished list. 1.625 + // Find and remove its entry. 1.626 + { 1.627 + AutoLockWorkerThreadState lock; 1.628 + ParseTaskVector &finished = parseFinishedList(); 1.629 + for (size_t i = 0; i < finished.length(); i++) { 1.630 + if (finished[i] == token) { 1.631 + parseTask = finished[i]; 1.632 + remove(finished, &i); 1.633 + break; 1.634 + } 1.635 + } 1.636 + } 1.637 + JS_ASSERT(parseTask); 1.638 + 1.639 + if (!maybecx) { 1.640 + LeaveParseTaskZone(rt, parseTask); 1.641 + return nullptr; 1.642 + } 1.643 + 1.644 + JSContext *cx = maybecx; 1.645 + JS_ASSERT(cx->compartment()); 1.646 + 1.647 + // Make sure we have all the constructors we need for the prototype 1.648 + // remapping below, since we can't GC while that's happening. 1.649 + Rooted<GlobalObject*> global(cx, &cx->global()->as<GlobalObject>()); 1.650 + if (!GlobalObject::ensureConstructor(cx, global, JSProto_Object) || 1.651 + !GlobalObject::ensureConstructor(cx, global, JSProto_Array) || 1.652 + !GlobalObject::ensureConstructor(cx, global, JSProto_Function) || 1.653 + !GlobalObject::ensureConstructor(cx, global, JSProto_RegExp) || 1.654 + !GlobalObject::ensureConstructor(cx, global, JSProto_Iterator)) 1.655 + { 1.656 + LeaveParseTaskZone(rt, parseTask); 1.657 + return nullptr; 1.658 + } 1.659 + 1.660 + LeaveParseTaskZone(rt, parseTask); 1.661 + 1.662 + // Point the prototypes of any objects in the script's compartment to refer 1.663 + // to the corresponding prototype in the new compartment. This will briefly 1.664 + // create cross compartment pointers, which will be fixed by the 1.665 + // MergeCompartments call below. 1.666 + for (gc::CellIter iter(parseTask->cx->zone(), gc::FINALIZE_TYPE_OBJECT); 1.667 + !iter.done(); 1.668 + iter.next()) 1.669 + { 1.670 + types::TypeObject *object = iter.get<types::TypeObject>(); 1.671 + TaggedProto proto(object->proto()); 1.672 + if (!proto.isObject()) 1.673 + continue; 1.674 + 1.675 + JSProtoKey key = JS::IdentifyStandardPrototype(proto.toObject()); 1.676 + if (key == JSProto_Null) 1.677 + continue; 1.678 + JS_ASSERT(key == JSProto_Object || key == JSProto_Array || 1.679 + key == JSProto_Function || key == JSProto_RegExp || 1.680 + key == JSProto_Iterator); 1.681 + 1.682 + JSObject *newProto = GetBuiltinPrototypePure(global, key); 1.683 + JS_ASSERT(newProto); 1.684 + 1.685 + object->setProtoUnchecked(newProto); 1.686 + } 1.687 + 1.688 + // Move the parsed script and all its contents into the desired compartment. 1.689 + gc::MergeCompartments(parseTask->cx->compartment(), cx->compartment()); 1.690 + parseTask->finish(); 1.691 + 1.692 + RootedScript script(rt, parseTask->script); 1.693 + assertSameCompartment(cx, script); 1.694 + 1.695 + // Report any error or warnings generated during the parse, and inform the 1.696 + // debugger about the compiled scripts. 1.697 + for (size_t i = 0; i < parseTask->errors.length(); i++) 1.698 + parseTask->errors[i]->throwError(cx); 1.699 + if (parseTask->overRecursed) 1.700 + js_ReportOverRecursed(cx); 1.701 + 1.702 + if (script) { 1.703 + // The Debugger only needs to be told about the topmost script that was compiled. 1.704 + GlobalObject *compileAndGoGlobal = nullptr; 1.705 + if (script->compileAndGo()) 1.706 + compileAndGoGlobal = &script->global(); 1.707 + Debugger::onNewScript(cx, script, compileAndGoGlobal); 1.708 + 1.709 + // The NewScript hook needs to be called for all compiled scripts. 1.710 + CallNewScriptHookForAllScripts(cx, script); 1.711 + } 1.712 + 1.713 + return script; 1.714 +} 1.715 + 1.716 +void 1.717 +WorkerThread::destroy() 1.718 +{ 1.719 + if (thread) { 1.720 + { 1.721 + AutoLockWorkerThreadState lock; 1.722 + terminate = true; 1.723 + 1.724 + /* Notify all workers, to ensure that this thread wakes up. */ 1.725 + WorkerThreadState().notifyAll(GlobalWorkerThreadState::PRODUCER); 1.726 + } 1.727 + 1.728 + PR_JoinThread(thread); 1.729 + } 1.730 + 1.731 + if (!threadData.empty()) 1.732 + threadData.destroy(); 1.733 +} 1.734 + 1.735 +/* static */ 1.736 +void 1.737 +WorkerThread::ThreadMain(void *arg) 1.738 +{ 1.739 + PR_SetCurrentThreadName("Analysis Helper"); 1.740 + static_cast<WorkerThread *>(arg)->threadLoop(); 1.741 +} 1.742 + 1.743 +void 1.744 +WorkerThread::handleAsmJSWorkload() 1.745 +{ 1.746 +#ifdef JS_ION 1.747 + JS_ASSERT(WorkerThreadState().isLocked()); 1.748 + JS_ASSERT(WorkerThreadState().canStartAsmJSCompile()); 1.749 + JS_ASSERT(idle()); 1.750 + 1.751 + asmData = WorkerThreadState().asmJSWorklist().popCopy(); 1.752 + bool success = false; 1.753 + 1.754 + do { 1.755 + AutoUnlockWorkerThreadState unlock; 1.756 + PerThreadData::AutoEnterRuntime enter(threadData.addr(), asmData->runtime); 1.757 + 1.758 + jit::IonContext icx(asmData->mir->compartment->runtime(), 1.759 + asmData->mir->compartment, 1.760 + &asmData->mir->alloc()); 1.761 + 1.762 + int64_t before = PRMJ_Now(); 1.763 + 1.764 + if (!OptimizeMIR(asmData->mir)) 1.765 + break; 1.766 + 1.767 + asmData->lir = GenerateLIR(asmData->mir); 1.768 + if (!asmData->lir) 1.769 + break; 1.770 + 1.771 + int64_t after = PRMJ_Now(); 1.772 + asmData->compileTime = (after - before) / PRMJ_USEC_PER_MSEC; 1.773 + 1.774 + success = true; 1.775 + } while(0); 1.776 + 1.777 + // On failure, signal parent for harvesting in CancelOutstandingJobs(). 1.778 + if (!success) { 1.779 + WorkerThreadState().noteAsmJSFailure(asmData->func); 1.780 + WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER); 1.781 + asmData = nullptr; 1.782 + return; 1.783 + } 1.784 + 1.785 + // On success, move work to the finished list. 1.786 + WorkerThreadState().asmJSFinishedList().append(asmData); 1.787 + asmData = nullptr; 1.788 + 1.789 + // Notify the main thread in case it's blocked waiting for a LifoAlloc. 1.790 + WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER); 1.791 +#else 1.792 + MOZ_CRASH(); 1.793 +#endif // JS_ION 1.794 +} 1.795 + 1.796 +void 1.797 +WorkerThread::handleIonWorkload() 1.798 +{ 1.799 +#ifdef JS_ION 1.800 + JS_ASSERT(WorkerThreadState().isLocked()); 1.801 + JS_ASSERT(WorkerThreadState().canStartIonCompile()); 1.802 + JS_ASSERT(idle()); 1.803 + 1.804 + ionBuilder = WorkerThreadState().ionWorklist().popCopy(); 1.805 + 1.806 + TraceLogger *logger = TraceLoggerForCurrentThread(); 1.807 + AutoTraceLog logScript(logger, TraceLogCreateTextId(logger, ionBuilder->script())); 1.808 + AutoTraceLog logCompile(logger, TraceLogger::IonCompilation); 1.809 + 1.810 + JSRuntime *rt = ionBuilder->script()->compartment()->runtimeFromAnyThread(); 1.811 + 1.812 + { 1.813 + AutoUnlockWorkerThreadState unlock; 1.814 + PerThreadData::AutoEnterRuntime enter(threadData.addr(), 1.815 + ionBuilder->script()->runtimeFromAnyThread()); 1.816 + jit::IonContext ictx(jit::CompileRuntime::get(rt), 1.817 + jit::CompileCompartment::get(ionBuilder->script()->compartment()), 1.818 + &ionBuilder->alloc()); 1.819 + ionBuilder->setBackgroundCodegen(jit::CompileBackEnd(ionBuilder)); 1.820 + } 1.821 + 1.822 + FinishOffThreadIonCompile(ionBuilder); 1.823 + ionBuilder = nullptr; 1.824 + 1.825 + // Ping the main thread so that the compiled code can be incorporated 1.826 + // at the next interrupt callback. Don't interrupt Ion code for this, as 1.827 + // this incorporation can be delayed indefinitely without affecting 1.828 + // performance as long as the main thread is actually executing Ion code. 1.829 + rt->requestInterrupt(JSRuntime::RequestInterruptAnyThreadDontStopIon); 1.830 + 1.831 + // Notify the main thread in case it is waiting for the compilation to finish. 1.832 + WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER); 1.833 +#else 1.834 + MOZ_CRASH(); 1.835 +#endif // JS_ION 1.836 +} 1.837 + 1.838 +void 1.839 +ExclusiveContext::setWorkerThread(WorkerThread *workerThread) 1.840 +{ 1.841 + workerThread_ = workerThread; 1.842 + perThreadData = workerThread->threadData.addr(); 1.843 +} 1.844 + 1.845 +frontend::CompileError & 1.846 +ExclusiveContext::addPendingCompileError() 1.847 +{ 1.848 + frontend::CompileError *error = js_new<frontend::CompileError>(); 1.849 + if (!error) 1.850 + MOZ_CRASH(); 1.851 + if (!workerThread()->parseTask->errors.append(error)) 1.852 + MOZ_CRASH(); 1.853 + return *error; 1.854 +} 1.855 + 1.856 +void 1.857 +ExclusiveContext::addPendingOverRecursed() 1.858 +{ 1.859 + if (workerThread()->parseTask) 1.860 + workerThread()->parseTask->overRecursed = true; 1.861 +} 1.862 + 1.863 +void 1.864 +WorkerThread::handleParseWorkload() 1.865 +{ 1.866 + JS_ASSERT(WorkerThreadState().isLocked()); 1.867 + JS_ASSERT(WorkerThreadState().canStartParseTask()); 1.868 + JS_ASSERT(idle()); 1.869 + 1.870 + parseTask = WorkerThreadState().parseWorklist().popCopy(); 1.871 + parseTask->cx->setWorkerThread(this); 1.872 + 1.873 + { 1.874 + AutoUnlockWorkerThreadState unlock; 1.875 + PerThreadData::AutoEnterRuntime enter(threadData.addr(), 1.876 + parseTask->exclusiveContextGlobal->runtimeFromAnyThread()); 1.877 + SourceBufferHolder srcBuf(parseTask->chars, parseTask->length, 1.878 + SourceBufferHolder::NoOwnership); 1.879 + parseTask->script = frontend::CompileScript(parseTask->cx, &parseTask->alloc, 1.880 + NullPtr(), NullPtr(), 1.881 + parseTask->options, 1.882 + srcBuf); 1.883 + } 1.884 + 1.885 + // The callback is invoked while we are still off the main thread. 1.886 + parseTask->callback(parseTask, parseTask->callbackData); 1.887 + 1.888 + // FinishOffThreadScript will need to be called on the script to 1.889 + // migrate it into the correct compartment. 1.890 + WorkerThreadState().parseFinishedList().append(parseTask); 1.891 + 1.892 + parseTask = nullptr; 1.893 + 1.894 + // Notify the main thread in case it is waiting for the parse/emit to finish. 1.895 + WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER); 1.896 +} 1.897 + 1.898 +void 1.899 +WorkerThread::handleCompressionWorkload() 1.900 +{ 1.901 + JS_ASSERT(WorkerThreadState().isLocked()); 1.902 + JS_ASSERT(WorkerThreadState().canStartCompressionTask()); 1.903 + JS_ASSERT(idle()); 1.904 + 1.905 + compressionTask = WorkerThreadState().compressionWorklist().popCopy(); 1.906 + compressionTask->workerThread = this; 1.907 + 1.908 + { 1.909 + AutoUnlockWorkerThreadState unlock; 1.910 + if (!compressionTask->work()) 1.911 + compressionTask->setOOM(); 1.912 + } 1.913 + 1.914 + compressionTask->workerThread = nullptr; 1.915 + compressionTask = nullptr; 1.916 + 1.917 + // Notify the main thread in case it is waiting for the compression to finish. 1.918 + WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER); 1.919 +} 1.920 + 1.921 +bool 1.922 +js::StartOffThreadCompression(ExclusiveContext *cx, SourceCompressionTask *task) 1.923 +{ 1.924 + EnsureWorkerThreadsInitialized(cx); 1.925 + 1.926 + AutoLockWorkerThreadState lock; 1.927 + 1.928 + if (!WorkerThreadState().compressionWorklist().append(task)) 1.929 + return false; 1.930 + 1.931 + WorkerThreadState().notifyOne(GlobalWorkerThreadState::PRODUCER); 1.932 + return true; 1.933 +} 1.934 + 1.935 +bool 1.936 +GlobalWorkerThreadState::compressionInProgress(SourceCompressionTask *task) 1.937 +{ 1.938 + JS_ASSERT(isLocked()); 1.939 + for (size_t i = 0; i < compressionWorklist().length(); i++) { 1.940 + if (compressionWorklist()[i] == task) 1.941 + return true; 1.942 + } 1.943 + for (size_t i = 0; i < threadCount; i++) { 1.944 + if (threads[i].compressionTask == task) 1.945 + return true; 1.946 + } 1.947 + return false; 1.948 +} 1.949 + 1.950 +bool 1.951 +SourceCompressionTask::complete() 1.952 +{ 1.953 + JS_ASSERT_IF(!ss, !chars); 1.954 + if (active()) { 1.955 + AutoLockWorkerThreadState lock; 1.956 + 1.957 + while (WorkerThreadState().compressionInProgress(this)) 1.958 + WorkerThreadState().wait(GlobalWorkerThreadState::CONSUMER); 1.959 + 1.960 + ss->ready_ = true; 1.961 + 1.962 + // Update memory accounting. 1.963 + if (!oom) 1.964 + cx->updateMallocCounter(ss->computedSizeOfData()); 1.965 + 1.966 + ss = nullptr; 1.967 + chars = nullptr; 1.968 + } 1.969 + if (oom) { 1.970 + js_ReportOutOfMemory(cx); 1.971 + return false; 1.972 + } 1.973 + return true; 1.974 +} 1.975 + 1.976 +SourceCompressionTask * 1.977 +GlobalWorkerThreadState::compressionTaskForSource(ScriptSource *ss) 1.978 +{ 1.979 + JS_ASSERT(isLocked()); 1.980 + for (size_t i = 0; i < compressionWorklist().length(); i++) { 1.981 + SourceCompressionTask *task = compressionWorklist()[i]; 1.982 + if (task->source() == ss) 1.983 + return task; 1.984 + } 1.985 + for (size_t i = 0; i < threadCount; i++) { 1.986 + SourceCompressionTask *task = threads[i].compressionTask; 1.987 + if (task && task->source() == ss) 1.988 + return task; 1.989 + } 1.990 + return nullptr; 1.991 +} 1.992 + 1.993 +const jschar * 1.994 +ScriptSource::getOffThreadCompressionChars(ExclusiveContext *cx) 1.995 +{ 1.996 + // If this is being compressed off thread, return its uncompressed chars. 1.997 + 1.998 + if (ready()) { 1.999 + // Compression has already finished on the source. 1.1000 + return nullptr; 1.1001 + } 1.1002 + 1.1003 + AutoLockWorkerThreadState lock; 1.1004 + 1.1005 + // Look for a token that hasn't finished compressing and whose source is 1.1006 + // the given ScriptSource. 1.1007 + if (SourceCompressionTask *task = WorkerThreadState().compressionTaskForSource(this)) 1.1008 + return task->uncompressedChars(); 1.1009 + 1.1010 + // Compressing has finished, so this ScriptSource is ready. Avoid future 1.1011 + // queries on the worker thread state when getting the chars. 1.1012 + ready_ = true; 1.1013 + 1.1014 + return nullptr; 1.1015 +} 1.1016 + 1.1017 +void 1.1018 +WorkerThread::threadLoop() 1.1019 +{ 1.1020 + JS::AutoAssertNoGC nogc; 1.1021 + AutoLockWorkerThreadState lock; 1.1022 + 1.1023 + js::TlsPerThreadData.set(threadData.addr()); 1.1024 + 1.1025 + // Compute the thread's stack limit, for over-recursed checks. 1.1026 + uintptr_t stackLimit = GetNativeStackBase(); 1.1027 +#if JS_STACK_GROWTH_DIRECTION > 0 1.1028 + stackLimit += WORKER_STACK_QUOTA; 1.1029 +#else 1.1030 + stackLimit -= WORKER_STACK_QUOTA; 1.1031 +#endif 1.1032 + for (size_t i = 0; i < ArrayLength(threadData.ref().nativeStackLimit); i++) 1.1033 + threadData.ref().nativeStackLimit[i] = stackLimit; 1.1034 + 1.1035 + while (true) { 1.1036 + JS_ASSERT(!ionBuilder && !asmData); 1.1037 + 1.1038 + // Block until a task is available. 1.1039 + while (true) { 1.1040 + if (terminate) 1.1041 + return; 1.1042 + if (WorkerThreadState().canStartIonCompile() || 1.1043 + WorkerThreadState().canStartAsmJSCompile() || 1.1044 + WorkerThreadState().canStartParseTask() || 1.1045 + WorkerThreadState().canStartCompressionTask()) 1.1046 + { 1.1047 + break; 1.1048 + } 1.1049 + WorkerThreadState().wait(GlobalWorkerThreadState::PRODUCER); 1.1050 + } 1.1051 + 1.1052 + // Dispatch tasks, prioritizing AsmJS work. 1.1053 + if (WorkerThreadState().canStartAsmJSCompile()) 1.1054 + handleAsmJSWorkload(); 1.1055 + else if (WorkerThreadState().canStartIonCompile()) 1.1056 + handleIonWorkload(); 1.1057 + else if (WorkerThreadState().canStartParseTask()) 1.1058 + handleParseWorkload(); 1.1059 + else if (WorkerThreadState().canStartCompressionTask()) 1.1060 + handleCompressionWorkload(); 1.1061 + else 1.1062 + MOZ_ASSUME_UNREACHABLE("No task to perform"); 1.1063 + } 1.1064 +} 1.1065 + 1.1066 +#else /* JS_THREADSAFE */ 1.1067 + 1.1068 +using namespace js; 1.1069 + 1.1070 +#ifdef JS_ION 1.1071 + 1.1072 +bool 1.1073 +js::StartOffThreadAsmJSCompile(ExclusiveContext *cx, AsmJSParallelTask *asmData) 1.1074 +{ 1.1075 + MOZ_ASSUME_UNREACHABLE("Off thread compilation not available in non-THREADSAFE builds"); 1.1076 +} 1.1077 + 1.1078 +bool 1.1079 +js::StartOffThreadIonCompile(JSContext *cx, jit::IonBuilder *builder) 1.1080 +{ 1.1081 + MOZ_ASSUME_UNREACHABLE("Off thread compilation not available in non-THREADSAFE builds"); 1.1082 +} 1.1083 + 1.1084 +#endif // JS_ION 1.1085 + 1.1086 +void 1.1087 +js::CancelOffThreadIonCompile(JSCompartment *compartment, JSScript *script) 1.1088 +{ 1.1089 +} 1.1090 + 1.1091 +void 1.1092 +js::CancelOffThreadParses(JSRuntime *rt) 1.1093 +{ 1.1094 +} 1.1095 + 1.1096 +bool 1.1097 +js::StartOffThreadParseScript(JSContext *cx, const ReadOnlyCompileOptions &options, 1.1098 + const jschar *chars, size_t length, 1.1099 + JS::OffThreadCompileCallback callback, void *callbackData) 1.1100 +{ 1.1101 + MOZ_ASSUME_UNREACHABLE("Off thread compilation not available in non-THREADSAFE builds"); 1.1102 +} 1.1103 + 1.1104 +bool 1.1105 +js::StartOffThreadCompression(ExclusiveContext *cx, SourceCompressionTask *task) 1.1106 +{ 1.1107 + MOZ_ASSUME_UNREACHABLE("Off thread compression not available"); 1.1108 +} 1.1109 + 1.1110 +bool 1.1111 +SourceCompressionTask::complete() 1.1112 +{ 1.1113 + JS_ASSERT(!active() && !oom); 1.1114 + return true; 1.1115 +} 1.1116 + 1.1117 +const jschar * 1.1118 +ScriptSource::getOffThreadCompressionChars(ExclusiveContext *cx) 1.1119 +{ 1.1120 + JS_ASSERT(ready()); 1.1121 + return nullptr; 1.1122 +} 1.1123 + 1.1124 +frontend::CompileError & 1.1125 +ExclusiveContext::addPendingCompileError() 1.1126 +{ 1.1127 + MOZ_ASSUME_UNREACHABLE("Off thread compilation not available."); 1.1128 +} 1.1129 + 1.1130 +void 1.1131 +ExclusiveContext::addPendingOverRecursed() 1.1132 +{ 1.1133 + MOZ_ASSUME_UNREACHABLE("Off thread compilation not available."); 1.1134 +} 1.1135 + 1.1136 +#endif /* JS_THREADSAFE */