1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/js/src/vm/ForkJoin.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,2210 @@ 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 +#if defined(XP_WIN) 1.11 +# include <io.h> // for isatty() 1.12 +#else 1.13 +# include <unistd.h> // for isatty() 1.14 +#endif 1.15 + 1.16 +#include "vm/ForkJoin.h" 1.17 + 1.18 +#include "mozilla/ThreadLocal.h" 1.19 + 1.20 +#include "jscntxt.h" 1.21 +#include "jslock.h" 1.22 +#include "jsprf.h" 1.23 + 1.24 +#include "builtin/TypedObject.h" 1.25 + 1.26 +#ifdef JS_THREADSAFE 1.27 +# include "jit/BaselineJIT.h" 1.28 +# include "vm/Monitor.h" 1.29 +#endif 1.30 + 1.31 +#if defined(JS_THREADSAFE) && defined(JS_ION) 1.32 +# include "jit/JitCommon.h" 1.33 +# ifdef DEBUG 1.34 +# include "jit/Ion.h" 1.35 +# include "jit/JitCompartment.h" 1.36 +# include "jit/MIR.h" 1.37 +# include "jit/MIRGraph.h" 1.38 +# endif 1.39 +#endif // THREADSAFE && ION 1.40 + 1.41 +#include "vm/Interpreter-inl.h" 1.42 + 1.43 +using namespace js; 1.44 +using namespace js::parallel; 1.45 +using namespace js::jit; 1.46 + 1.47 +using mozilla::ThreadLocal; 1.48 + 1.49 +/////////////////////////////////////////////////////////////////////////// 1.50 +// Degenerate configurations 1.51 +// 1.52 +// When JS_THREADSAFE or JS_ION is not defined, we simply run the 1.53 +// |func| callback sequentially. We also forego the feedback 1.54 +// altogether. 1.55 + 1.56 +static bool 1.57 +ExecuteSequentially(JSContext *cx_, HandleValue funVal, uint16_t *sliceStart, 1.58 + uint16_t sliceEnd); 1.59 + 1.60 +#if !defined(JS_THREADSAFE) || !defined(JS_ION) 1.61 +bool 1.62 +js::ForkJoin(JSContext *cx, CallArgs &args) 1.63 +{ 1.64 + RootedValue argZero(cx, args[0]); 1.65 + uint16_t sliceStart = uint16_t(args[1].toInt32()); 1.66 + uint16_t sliceEnd = uint16_t(args[2].toInt32()); 1.67 + if (!ExecuteSequentially(cx, argZero, &sliceStart, sliceEnd)) 1.68 + return false; 1.69 + MOZ_ASSERT(sliceStart == sliceEnd); 1.70 + return true; 1.71 +} 1.72 + 1.73 +JSContext * 1.74 +ForkJoinContext::acquireJSContext() 1.75 +{ 1.76 + return nullptr; 1.77 +} 1.78 + 1.79 +void 1.80 +ForkJoinContext::releaseJSContext() 1.81 +{ 1.82 +} 1.83 + 1.84 +bool 1.85 +ForkJoinContext::isMainThread() const 1.86 +{ 1.87 + return true; 1.88 +} 1.89 + 1.90 +JSRuntime * 1.91 +ForkJoinContext::runtime() 1.92 +{ 1.93 + MOZ_ASSUME_UNREACHABLE("Not THREADSAFE build"); 1.94 +} 1.95 + 1.96 +bool 1.97 +ForkJoinContext::check() 1.98 +{ 1.99 + MOZ_ASSUME_UNREACHABLE("Not THREADSAFE build"); 1.100 +} 1.101 + 1.102 +void 1.103 +ForkJoinContext::requestGC(JS::gcreason::Reason reason) 1.104 +{ 1.105 + MOZ_ASSUME_UNREACHABLE("Not THREADSAFE build"); 1.106 +} 1.107 + 1.108 +void 1.109 +ForkJoinContext::requestZoneGC(JS::Zone *zone, JS::gcreason::Reason reason) 1.110 +{ 1.111 + MOZ_ASSUME_UNREACHABLE("Not THREADSAFE build"); 1.112 +} 1.113 + 1.114 +bool 1.115 +ForkJoinContext::setPendingAbortFatal(ParallelBailoutCause cause) 1.116 +{ 1.117 + MOZ_ASSUME_UNREACHABLE("Not THREADSAFE build"); 1.118 + return false; 1.119 +} 1.120 + 1.121 +void 1.122 +ParallelBailoutRecord::setCause(ParallelBailoutCause cause, 1.123 + JSScript *outermostScript, 1.124 + JSScript *currentScript, 1.125 + jsbytecode *currentPc) 1.126 +{ 1.127 + MOZ_ASSUME_UNREACHABLE("Not THREADSAFE build"); 1.128 +} 1.129 + 1.130 +void 1.131 +js::ParallelBailoutRecord::updateCause(ParallelBailoutCause cause, 1.132 + JSScript *outermostScript, 1.133 + JSScript *currentScript, 1.134 + jsbytecode *currentPc) 1.135 +{ 1.136 + MOZ_ASSUME_UNREACHABLE("Not THREADSAFE build"); 1.137 +} 1.138 + 1.139 +void 1.140 +ParallelBailoutRecord::addTrace(JSScript *script, 1.141 + jsbytecode *pc) 1.142 +{ 1.143 + MOZ_ASSUME_UNREACHABLE("Not THREADSAFE build"); 1.144 +} 1.145 + 1.146 +bool 1.147 +js::InExclusiveParallelSection() 1.148 +{ 1.149 + return false; 1.150 +} 1.151 + 1.152 +bool 1.153 +js::ParallelTestsShouldPass(JSContext *cx) 1.154 +{ 1.155 + return false; 1.156 +} 1.157 + 1.158 +bool 1.159 +js::intrinsic_SetForkJoinTargetRegion(JSContext *cx, unsigned argc, Value *vp) 1.160 +{ 1.161 + return true; 1.162 +} 1.163 + 1.164 +static bool 1.165 +intrinsic_SetForkJoinTargetRegionPar(ForkJoinContext *cx, unsigned argc, Value *vp) 1.166 +{ 1.167 + return true; 1.168 +} 1.169 + 1.170 +JS_JITINFO_NATIVE_PARALLEL(js::intrinsic_SetForkJoinTargetRegionInfo, 1.171 + intrinsic_SetForkJoinTargetRegionPar); 1.172 + 1.173 +bool 1.174 +js::intrinsic_ClearThreadLocalArenas(JSContext *cx, unsigned argc, Value *vp) 1.175 +{ 1.176 + return true; 1.177 +} 1.178 + 1.179 +static bool 1.180 +intrinsic_ClearThreadLocalArenasPar(ForkJoinContext *cx, unsigned argc, Value *vp) 1.181 +{ 1.182 + return true; 1.183 +} 1.184 + 1.185 +JS_JITINFO_NATIVE_PARALLEL(js::intrinsic_ClearThreadLocalArenasInfo, 1.186 + intrinsic_ClearThreadLocalArenasPar); 1.187 + 1.188 +#endif // !JS_THREADSAFE || !JS_ION 1.189 + 1.190 +/////////////////////////////////////////////////////////////////////////// 1.191 +// All configurations 1.192 +// 1.193 +// Some code that is shared between degenerate and parallel configurations. 1.194 + 1.195 +static bool 1.196 +ExecuteSequentially(JSContext *cx, HandleValue funVal, uint16_t *sliceStart, 1.197 + uint16_t sliceEnd) 1.198 +{ 1.199 + FastInvokeGuard fig(cx, funVal); 1.200 + InvokeArgs &args = fig.args(); 1.201 + if (!args.init(3)) 1.202 + return false; 1.203 + args.setCallee(funVal); 1.204 + args.setThis(UndefinedValue()); 1.205 + args[0].setInt32(0); 1.206 + args[1].setInt32(*sliceStart); 1.207 + args[2].setInt32(sliceEnd); 1.208 + if (!fig.invoke(cx)) 1.209 + return false; 1.210 + *sliceStart = (uint16_t)(args.rval().toInt32()); 1.211 + return true; 1.212 +} 1.213 + 1.214 +ThreadLocal<ForkJoinContext*> ForkJoinContext::tlsForkJoinContext; 1.215 + 1.216 +/* static */ bool 1.217 +ForkJoinContext::initialize() 1.218 +{ 1.219 + if (!tlsForkJoinContext.initialized()) { 1.220 + if (!tlsForkJoinContext.init()) 1.221 + return false; 1.222 + } 1.223 + return true; 1.224 +} 1.225 + 1.226 +/////////////////////////////////////////////////////////////////////////// 1.227 +// Parallel configurations 1.228 +// 1.229 +// The remainder of this file is specific to cases where both 1.230 +// JS_THREADSAFE and JS_ION are enabled. 1.231 + 1.232 +#if defined(JS_THREADSAFE) && defined(JS_ION) 1.233 + 1.234 +/////////////////////////////////////////////////////////////////////////// 1.235 +// Class Declarations and Function Prototypes 1.236 + 1.237 +namespace js { 1.238 + 1.239 +// When writing tests, it is often useful to specify different modes 1.240 +// of operation. 1.241 +enum ForkJoinMode { 1.242 + // WARNING: If you change this enum, you MUST update 1.243 + // ForkJoinMode() in Utilities.js 1.244 + 1.245 + // The "normal" behavior: attempt parallel, fallback to 1.246 + // sequential. If compilation is ongoing in a helper thread, then 1.247 + // run sequential warmup iterations in the meantime. If those 1.248 + // iterations wind up completing all the work, just abort. 1.249 + ForkJoinModeNormal, 1.250 + 1.251 + // Like normal, except that we will keep running warmup iterations 1.252 + // until compilations are complete, even if there is no more work 1.253 + // to do. This is useful in tests as a "setup" run. 1.254 + ForkJoinModeCompile, 1.255 + 1.256 + // Requires that compilation has already completed. Expects parallel 1.257 + // execution to proceed without a hitch. (Reports an error otherwise) 1.258 + ForkJoinModeParallel, 1.259 + 1.260 + // Requires that compilation has already completed. Expects 1.261 + // parallel execution to bailout once but continue after that without 1.262 + // further bailouts. (Reports an error otherwise) 1.263 + ForkJoinModeRecover, 1.264 + 1.265 + // Expects all parallel executions to yield a bailout. If this is not 1.266 + // the case, reports an error. 1.267 + ForkJoinModeBailout, 1.268 + 1.269 + NumForkJoinModes 1.270 +}; 1.271 + 1.272 +class ForkJoinOperation 1.273 +{ 1.274 + public: 1.275 + // For tests, make sure to keep this in sync with minItemsTestingThreshold. 1.276 + static const uint32_t MAX_BAILOUTS = 3; 1.277 + uint32_t bailouts; 1.278 + 1.279 + // Information about the bailout: 1.280 + ParallelBailoutCause bailoutCause; 1.281 + RootedScript bailoutScript; 1.282 + jsbytecode *bailoutBytecode; 1.283 + 1.284 + ForkJoinOperation(JSContext *cx, HandleFunction fun, uint16_t sliceStart, 1.285 + uint16_t sliceEnd, ForkJoinMode mode); 1.286 + ExecutionStatus apply(); 1.287 + 1.288 + private: 1.289 + // Most of the functions involved in managing the parallel 1.290 + // compilation follow a similar control-flow. They return RedLight 1.291 + // if they have either encountered a fatal error or completed the 1.292 + // execution, such that no further work is needed. In that event, 1.293 + // they take an `ExecutionStatus*` which they use to report 1.294 + // whether execution was successful or not. If the function 1.295 + // returns `GreenLight`, then the parallel operation is not yet 1.296 + // fully completed, so the state machine should carry on. 1.297 + enum TrafficLight { 1.298 + RedLight, 1.299 + GreenLight 1.300 + }; 1.301 + 1.302 + struct WorklistData { 1.303 + // True if we enqueued the callees from the ion-compiled 1.304 + // version of this entry 1.305 + bool calleesEnqueued; 1.306 + 1.307 + // Last record useCount; updated after warmup 1.308 + // iterations; 1.309 + uint32_t useCount; 1.310 + 1.311 + // Number of continuous "stalls" --- meaning warmups 1.312 + // where useCount did not increase. 1.313 + uint32_t stallCount; 1.314 + 1.315 + void reset() { 1.316 + calleesEnqueued = false; 1.317 + useCount = 0; 1.318 + stallCount = 0; 1.319 + } 1.320 + }; 1.321 + 1.322 + JSContext *cx_; 1.323 + HandleFunction fun_; 1.324 + uint16_t sliceStart_; 1.325 + uint16_t sliceEnd_; 1.326 + Vector<ParallelBailoutRecord, 16> bailoutRecords_; 1.327 + AutoScriptVector worklist_; 1.328 + Vector<WorklistData, 16> worklistData_; 1.329 + ForkJoinMode mode_; 1.330 + 1.331 + TrafficLight enqueueInitialScript(ExecutionStatus *status); 1.332 + TrafficLight compileForParallelExecution(ExecutionStatus *status); 1.333 + TrafficLight warmupExecution(bool stopIfComplete, ExecutionStatus *status); 1.334 + TrafficLight parallelExecution(ExecutionStatus *status); 1.335 + TrafficLight sequentialExecution(bool disqualified, ExecutionStatus *status); 1.336 + TrafficLight recoverFromBailout(ExecutionStatus *status); 1.337 + TrafficLight fatalError(ExecutionStatus *status); 1.338 + bool isInitialScript(HandleScript script); 1.339 + void determineBailoutCause(); 1.340 + bool invalidateBailedOutScripts(); 1.341 + ExecutionStatus sequentialExecution(bool disqualified); 1.342 + 1.343 + TrafficLight appendCallTargetsToWorklist(uint32_t index, ExecutionStatus *status); 1.344 + TrafficLight appendCallTargetToWorklist(HandleScript script, ExecutionStatus *status); 1.345 + bool addToWorklist(HandleScript script); 1.346 + inline bool hasScript(Vector<types::RecompileInfo> &scripts, JSScript *script); 1.347 +}; // class ForkJoinOperation 1.348 + 1.349 +class ForkJoinShared : public ParallelJob, public Monitor 1.350 +{ 1.351 + ///////////////////////////////////////////////////////////////////////// 1.352 + // Constant fields 1.353 + 1.354 + JSContext *const cx_; // Current context 1.355 + ThreadPool *const threadPool_; // The thread pool 1.356 + HandleFunction fun_; // The JavaScript function to execute 1.357 + uint16_t sliceStart_; // The starting slice id. 1.358 + uint16_t sliceEnd_; // The ending slice id + 1. 1.359 + PRLock *cxLock_; // Locks cx_ for parallel VM calls 1.360 + ParallelBailoutRecord *const records_; // Bailout records for each worker 1.361 + 1.362 + ///////////////////////////////////////////////////////////////////////// 1.363 + // Per-thread arenas 1.364 + // 1.365 + // Each worker thread gets an arena to use when allocating. 1.366 + 1.367 + Vector<Allocator *, 16> allocators_; 1.368 + 1.369 + ///////////////////////////////////////////////////////////////////////// 1.370 + // Locked Fields 1.371 + // 1.372 + // Only to be accessed while holding the lock. 1.373 + 1.374 + bool gcRequested_; // True if a worker requested a GC 1.375 + JS::gcreason::Reason gcReason_; // Reason given to request GC 1.376 + Zone *gcZone_; // Zone for GC, or nullptr for full 1.377 + 1.378 + ///////////////////////////////////////////////////////////////////////// 1.379 + // Asynchronous Flags 1.380 + // 1.381 + // These can be accessed without the lock and are thus atomic. 1.382 + 1.383 + // Set to true when parallel execution should abort. 1.384 + mozilla::Atomic<bool, mozilla::ReleaseAcquire> abort_; 1.385 + 1.386 + // Set to true when a worker bails for a fatal reason. 1.387 + mozilla::Atomic<bool, mozilla::ReleaseAcquire> fatal_; 1.388 + 1.389 + public: 1.390 + ForkJoinShared(JSContext *cx, 1.391 + ThreadPool *threadPool, 1.392 + HandleFunction fun, 1.393 + uint16_t sliceStart, 1.394 + uint16_t sliceEnd, 1.395 + ParallelBailoutRecord *records); 1.396 + ~ForkJoinShared(); 1.397 + 1.398 + bool init(); 1.399 + 1.400 + ParallelResult execute(); 1.401 + 1.402 + // Invoked from parallel worker threads: 1.403 + virtual bool executeFromWorker(ThreadPoolWorker *worker, uintptr_t stackLimit) MOZ_OVERRIDE; 1.404 + 1.405 + // Invoked only from the main thread: 1.406 + virtual bool executeFromMainThread(ThreadPoolWorker *worker) MOZ_OVERRIDE; 1.407 + 1.408 + // Executes the user-supplied function a worker or the main thread. 1.409 + void executePortion(PerThreadData *perThread, ThreadPoolWorker *worker); 1.410 + 1.411 + // Moves all the per-thread arenas into the main compartment and processes 1.412 + // any pending requests for a GC. This can only safely be invoked on the 1.413 + // main thread after the workers have completed. 1.414 + void transferArenasToCompartmentAndProcessGCRequests(); 1.415 + 1.416 + 1.417 + // Requests a GC, either full or specific to a zone. 1.418 + void requestGC(JS::gcreason::Reason reason); 1.419 + void requestZoneGC(JS::Zone *zone, JS::gcreason::Reason reason); 1.420 + 1.421 + // Requests that computation abort. 1.422 + void setAbortFlagDueToInterrupt(ForkJoinContext &cx); 1.423 + void setAbortFlagAndRequestInterrupt(bool fatal); 1.424 + 1.425 + // Set the fatal flag for the next abort. 1.426 + void setPendingAbortFatal() { fatal_ = true; } 1.427 + 1.428 + JSRuntime *runtime() { return cx_->runtime(); } 1.429 + JS::Zone *zone() { return cx_->zone(); } 1.430 + JSCompartment *compartment() { return cx_->compartment(); } 1.431 + 1.432 + JSContext *acquireJSContext() { PR_Lock(cxLock_); return cx_; } 1.433 + void releaseJSContext() { PR_Unlock(cxLock_); } 1.434 +}; 1.435 + 1.436 +class AutoEnterWarmup 1.437 +{ 1.438 + JSRuntime *runtime_; 1.439 + 1.440 + public: 1.441 + AutoEnterWarmup(JSRuntime *runtime) : runtime_(runtime) { runtime_->forkJoinWarmup++; } 1.442 + ~AutoEnterWarmup() { runtime_->forkJoinWarmup--; } 1.443 +}; 1.444 + 1.445 +class AutoSetForkJoinContext 1.446 +{ 1.447 + public: 1.448 + AutoSetForkJoinContext(ForkJoinContext *threadCx) { 1.449 + ForkJoinContext::tlsForkJoinContext.set(threadCx); 1.450 + } 1.451 + 1.452 + ~AutoSetForkJoinContext() { 1.453 + ForkJoinContext::tlsForkJoinContext.set(nullptr); 1.454 + } 1.455 +}; 1.456 + 1.457 +} // namespace js 1.458 + 1.459 +/////////////////////////////////////////////////////////////////////////// 1.460 +// ForkJoinActivation 1.461 +// 1.462 +// Takes care of tidying up GC before we enter a fork join section. Also 1.463 +// pauses the barrier verifier, as we cannot enter fork join with the runtime 1.464 +// or the zone needing barriers. 1.465 + 1.466 +ForkJoinActivation::ForkJoinActivation(JSContext *cx) 1.467 + : Activation(cx, ForkJoin), 1.468 + prevIonTop_(cx->mainThread().ionTop), 1.469 + av_(cx->runtime(), false) 1.470 +{ 1.471 + // Note: we do not allow GC during parallel sections. 1.472 + // Moreover, we do not wish to worry about making 1.473 + // write barriers thread-safe. Therefore, we guarantee 1.474 + // that there is no incremental GC in progress and force 1.475 + // a minor GC to ensure no cross-generation pointers get 1.476 + // created: 1.477 + 1.478 + if (JS::IsIncrementalGCInProgress(cx->runtime())) { 1.479 + JS::PrepareForIncrementalGC(cx->runtime()); 1.480 + JS::FinishIncrementalGC(cx->runtime(), JS::gcreason::API); 1.481 + } 1.482 + 1.483 + MinorGC(cx->runtime(), JS::gcreason::API); 1.484 + 1.485 + cx->runtime()->gcHelperThread.waitBackgroundSweepEnd(); 1.486 + 1.487 + JS_ASSERT(!cx->runtime()->needsBarrier()); 1.488 + JS_ASSERT(!cx->zone()->needsBarrier()); 1.489 +} 1.490 + 1.491 +ForkJoinActivation::~ForkJoinActivation() 1.492 +{ 1.493 + cx_->mainThread().ionTop = prevIonTop_; 1.494 +} 1.495 + 1.496 +/////////////////////////////////////////////////////////////////////////// 1.497 +// js::ForkJoin() and ForkJoinOperation class 1.498 +// 1.499 +// These are the top-level objects that manage the parallel execution. 1.500 +// They handle parallel compilation (if necessary), triggering 1.501 +// parallel execution, and recovering from bailouts. 1.502 + 1.503 +static const char *ForkJoinModeString(ForkJoinMode mode); 1.504 + 1.505 +bool 1.506 +js::ForkJoin(JSContext *cx, CallArgs &args) 1.507 +{ 1.508 + JS_ASSERT(args.length() == 4); // else the self-hosted code is wrong 1.509 + JS_ASSERT(args[0].isObject()); 1.510 + JS_ASSERT(args[0].toObject().is<JSFunction>()); 1.511 + JS_ASSERT(args[1].isInt32()); 1.512 + JS_ASSERT(args[2].isInt32()); 1.513 + JS_ASSERT(args[3].isInt32()); 1.514 + JS_ASSERT(args[3].toInt32() < NumForkJoinModes); 1.515 + 1.516 + RootedFunction fun(cx, &args[0].toObject().as<JSFunction>()); 1.517 + uint16_t sliceStart = (uint16_t)(args[1].toInt32()); 1.518 + uint16_t sliceEnd = (uint16_t)(args[2].toInt32()); 1.519 + ForkJoinMode mode = (ForkJoinMode)(args[3].toInt32()); 1.520 + 1.521 + MOZ_ASSERT(sliceStart == args[1].toInt32()); 1.522 + MOZ_ASSERT(sliceEnd == args[2].toInt32()); 1.523 + MOZ_ASSERT(sliceStart <= sliceEnd); 1.524 + 1.525 + ForkJoinOperation op(cx, fun, sliceStart, sliceEnd, mode); 1.526 + ExecutionStatus status = op.apply(); 1.527 + if (status == ExecutionFatal) 1.528 + return false; 1.529 + 1.530 + switch (mode) { 1.531 + case ForkJoinModeNormal: 1.532 + case ForkJoinModeCompile: 1.533 + return true; 1.534 + 1.535 + case ForkJoinModeParallel: 1.536 + if (status == ExecutionParallel && op.bailouts == 0) 1.537 + return true; 1.538 + break; 1.539 + 1.540 + case ForkJoinModeRecover: 1.541 + if (status != ExecutionSequential && op.bailouts > 0) 1.542 + return true; 1.543 + break; 1.544 + 1.545 + case ForkJoinModeBailout: 1.546 + if (status != ExecutionParallel) 1.547 + return true; 1.548 + break; 1.549 + 1.550 + case NumForkJoinModes: 1.551 + break; 1.552 + } 1.553 + 1.554 + const char *statusString = "?"; 1.555 + switch (status) { 1.556 + case ExecutionSequential: statusString = "seq"; break; 1.557 + case ExecutionParallel: statusString = "par"; break; 1.558 + case ExecutionWarmup: statusString = "warmup"; break; 1.559 + case ExecutionFatal: statusString = "fatal"; break; 1.560 + } 1.561 + 1.562 + if (ParallelTestsShouldPass(cx)) { 1.563 + JS_ReportError(cx, "ForkJoin: mode=%s status=%s bailouts=%d", 1.564 + ForkJoinModeString(mode), statusString, op.bailouts); 1.565 + return false; 1.566 + } 1.567 + return true; 1.568 +} 1.569 + 1.570 +static const char * 1.571 +ForkJoinModeString(ForkJoinMode mode) { 1.572 + switch (mode) { 1.573 + case ForkJoinModeNormal: return "normal"; 1.574 + case ForkJoinModeCompile: return "compile"; 1.575 + case ForkJoinModeParallel: return "parallel"; 1.576 + case ForkJoinModeRecover: return "recover"; 1.577 + case ForkJoinModeBailout: return "bailout"; 1.578 + case NumForkJoinModes: return "max"; 1.579 + } 1.580 + return "???"; 1.581 +} 1.582 + 1.583 +ForkJoinOperation::ForkJoinOperation(JSContext *cx, HandleFunction fun, uint16_t sliceStart, 1.584 + uint16_t sliceEnd, ForkJoinMode mode) 1.585 + : bailouts(0), 1.586 + bailoutCause(ParallelBailoutNone), 1.587 + bailoutScript(cx), 1.588 + bailoutBytecode(nullptr), 1.589 + cx_(cx), 1.590 + fun_(fun), 1.591 + sliceStart_(sliceStart), 1.592 + sliceEnd_(sliceEnd), 1.593 + bailoutRecords_(cx), 1.594 + worklist_(cx), 1.595 + worklistData_(cx), 1.596 + mode_(mode) 1.597 +{ } 1.598 + 1.599 +ExecutionStatus 1.600 +ForkJoinOperation::apply() 1.601 +{ 1.602 + ExecutionStatus status; 1.603 + 1.604 + // High level outline of the procedure: 1.605 + // 1.606 + // - As we enter, we check for parallel script without "uncompiled" flag. 1.607 + // - If present, skip initial enqueue. 1.608 + // - While not too many bailouts: 1.609 + // - While all scripts in worklist are not compiled: 1.610 + // - For each script S in worklist: 1.611 + // - Compile S if not compiled 1.612 + // -> Error: fallback 1.613 + // - If compiled, add call targets to worklist w/o checking uncompiled 1.614 + // flag 1.615 + // - If some compilations pending, run warmup iteration 1.616 + // - Otherwise, clear "uncompiled targets" flag on main script and 1.617 + // break from loop 1.618 + // - Attempt parallel execution 1.619 + // - If successful: return happily 1.620 + // - If error: abort sadly 1.621 + // - If bailout: 1.622 + // - Invalidate any scripts that may need to be invalidated 1.623 + // - Re-enqueue main script and any uncompiled scripts that were called 1.624 + // - Too many bailouts: Fallback to sequential 1.625 + 1.626 + JS_ASSERT_IF(!jit::IsBaselineEnabled(cx_), !jit::IsIonEnabled(cx_)); 1.627 + if (!jit::IsBaselineEnabled(cx_) || !jit::IsIonEnabled(cx_)) 1.628 + return sequentialExecution(true); 1.629 + 1.630 + SpewBeginOp(cx_, "ForkJoinOperation"); 1.631 + 1.632 + // How many workers do we have, counting the main thread. 1.633 + unsigned numWorkers = cx_->runtime()->threadPool.numWorkers(); 1.634 + 1.635 + if (!bailoutRecords_.resize(numWorkers)) 1.636 + return SpewEndOp(ExecutionFatal); 1.637 + 1.638 + for (uint32_t i = 0; i < numWorkers; i++) 1.639 + bailoutRecords_[i].init(cx_); 1.640 + 1.641 + if (enqueueInitialScript(&status) == RedLight) 1.642 + return SpewEndOp(status); 1.643 + 1.644 + Spew(SpewOps, "Execution mode: %s", ForkJoinModeString(mode_)); 1.645 + switch (mode_) { 1.646 + case ForkJoinModeNormal: 1.647 + case ForkJoinModeCompile: 1.648 + case ForkJoinModeBailout: 1.649 + break; 1.650 + 1.651 + case ForkJoinModeParallel: 1.652 + case ForkJoinModeRecover: 1.653 + // These two modes are used to check that every iteration can 1.654 + // be executed in parallel. They expect compilation to have 1.655 + // been done. But, when using gc zeal, it's possible that 1.656 + // compiled scripts were collected. 1.657 + if (ParallelTestsShouldPass(cx_) && worklist_.length() != 0) { 1.658 + JS_ReportError(cx_, "ForkJoin: compilation required in par or bailout mode"); 1.659 + return SpewEndOp(ExecutionFatal); 1.660 + } 1.661 + break; 1.662 + 1.663 + case NumForkJoinModes: 1.664 + MOZ_ASSUME_UNREACHABLE("Invalid mode"); 1.665 + } 1.666 + 1.667 + while (bailouts < MAX_BAILOUTS) { 1.668 + for (uint32_t i = 0; i < numWorkers; i++) 1.669 + bailoutRecords_[i].reset(cx_); 1.670 + 1.671 + if (compileForParallelExecution(&status) == RedLight) 1.672 + return SpewEndOp(status); 1.673 + 1.674 + JS_ASSERT(worklist_.length() == 0); 1.675 + if (parallelExecution(&status) == RedLight) 1.676 + return SpewEndOp(status); 1.677 + 1.678 + if (recoverFromBailout(&status) == RedLight) 1.679 + return SpewEndOp(status); 1.680 + } 1.681 + 1.682 + // After enough tries, just execute sequentially. 1.683 + return SpewEndOp(sequentialExecution(true)); 1.684 +} 1.685 + 1.686 +ForkJoinOperation::TrafficLight 1.687 +ForkJoinOperation::enqueueInitialScript(ExecutionStatus *status) 1.688 +{ 1.689 + // GreenLight: script successfully enqueued if necessary 1.690 + // RedLight: fatal error or fell back to sequential 1.691 + 1.692 + // The kernel should be a self-hosted function. 1.693 + if (!fun_->is<JSFunction>()) 1.694 + return sequentialExecution(true, status); 1.695 + 1.696 + RootedFunction callee(cx_, &fun_->as<JSFunction>()); 1.697 + 1.698 + if (!callee->isInterpreted() || !callee->isSelfHostedBuiltin()) 1.699 + return sequentialExecution(true, status); 1.700 + 1.701 + // If the main script is already compiled, and we have no reason 1.702 + // to suspect any of its callees are not compiled, then we can 1.703 + // just skip the compilation step. 1.704 + RootedScript script(cx_, callee->getOrCreateScript(cx_)); 1.705 + if (!script) 1.706 + return RedLight; 1.707 + 1.708 + if (script->hasParallelIonScript()) { 1.709 + // Notify that there's been activity on the entry script. 1.710 + JitCompartment *jitComp = cx_->compartment()->jitCompartment(); 1.711 + if (!jitComp->notifyOfActiveParallelEntryScript(cx_, script)) { 1.712 + *status = ExecutionFatal; 1.713 + return RedLight; 1.714 + } 1.715 + 1.716 + if (!script->parallelIonScript()->hasUncompiledCallTarget()) { 1.717 + Spew(SpewOps, "Script %p:%s:%d already compiled, no uncompiled callees", 1.718 + script.get(), script->filename(), script->lineno()); 1.719 + return GreenLight; 1.720 + } 1.721 + 1.722 + Spew(SpewOps, "Script %p:%s:%d already compiled, may have uncompiled callees", 1.723 + script.get(), script->filename(), script->lineno()); 1.724 + } 1.725 + 1.726 + // Otherwise, add to the worklist of scripts to process. 1.727 + if (addToWorklist(script) == RedLight) 1.728 + return fatalError(status); 1.729 + return GreenLight; 1.730 +} 1.731 + 1.732 +ForkJoinOperation::TrafficLight 1.733 +ForkJoinOperation::compileForParallelExecution(ExecutionStatus *status) 1.734 +{ 1.735 + // GreenLight: all scripts compiled 1.736 + // RedLight: fatal error or completed work via warmups or fallback 1.737 + 1.738 + // This routine attempts to do whatever compilation is necessary 1.739 + // to execute a single parallel attempt. When it returns, either 1.740 + // (1) we have fallen back to sequential; (2) we have run enough 1.741 + // warmup runs to complete all the work; or (3) we have compiled 1.742 + // all scripts we think likely to be executed during a parallel 1.743 + // execution. 1.744 + 1.745 + RootedFunction fun(cx_); 1.746 + RootedScript script(cx_); 1.747 + 1.748 + // After 3 stalls, we stop waiting for a script to gather type 1.749 + // info and move on with execution. 1.750 + const uint32_t stallThreshold = 3; 1.751 + 1.752 + // This loop continues to iterate until the full contents of 1.753 + // `worklist` have been successfully compiled for parallel 1.754 + // execution. The compilations themselves typically occur on 1.755 + // helper threads. While we wait for the compilations to complete, 1.756 + // or for sufficient type information to be gathered, we execute 1.757 + // warmup iterations. 1.758 + while (true) { 1.759 + bool offMainThreadCompilationsInProgress = false; 1.760 + bool gatheringTypeInformation = false; 1.761 + 1.762 + // Walk over the worklist to check on the status of each entry. 1.763 + for (uint32_t i = 0; i < worklist_.length(); i++) { 1.764 + script = worklist_[i]; 1.765 + script->ensureNonLazyCanonicalFunction(cx_); 1.766 + fun = script->functionNonDelazifying(); 1.767 + 1.768 + // No baseline script means no type information, hence we 1.769 + // will not be able to compile very well. In such cases, 1.770 + // we continue to run baseline iterations until either (1) 1.771 + // the potential callee *has* a baseline script or (2) the 1.772 + // potential callee's use count stops increasing, 1.773 + // indicating that they are not in fact a callee. 1.774 + if (!script->hasBaselineScript()) { 1.775 + uint32_t previousUseCount = worklistData_[i].useCount; 1.776 + uint32_t currentUseCount = script->getUseCount(); 1.777 + if (previousUseCount < currentUseCount) { 1.778 + worklistData_[i].useCount = currentUseCount; 1.779 + worklistData_[i].stallCount = 0; 1.780 + gatheringTypeInformation = true; 1.781 + 1.782 + Spew(SpewCompile, 1.783 + "Script %p:%s:%d has no baseline script, " 1.784 + "but use count grew from %d to %d", 1.785 + script.get(), script->filename(), script->lineno(), 1.786 + previousUseCount, currentUseCount); 1.787 + } else { 1.788 + uint32_t stallCount = ++worklistData_[i].stallCount; 1.789 + if (stallCount < stallThreshold) { 1.790 + gatheringTypeInformation = true; 1.791 + } 1.792 + 1.793 + Spew(SpewCompile, 1.794 + "Script %p:%s:%d has no baseline script, " 1.795 + "and use count has %u stalls at %d", 1.796 + script.get(), script->filename(), script->lineno(), 1.797 + stallCount, previousUseCount); 1.798 + } 1.799 + continue; 1.800 + } 1.801 + 1.802 + if (!script->hasParallelIonScript()) { 1.803 + // Script has not yet been compiled. Attempt to compile it. 1.804 + SpewBeginCompile(script); 1.805 + MethodStatus mstatus = jit::CanEnterInParallel(cx_, script); 1.806 + SpewEndCompile(mstatus); 1.807 + 1.808 + switch (mstatus) { 1.809 + case Method_Error: 1.810 + return fatalError(status); 1.811 + 1.812 + case Method_CantCompile: 1.813 + Spew(SpewCompile, 1.814 + "Script %p:%s:%d cannot be compiled, " 1.815 + "falling back to sequential execution", 1.816 + script.get(), script->filename(), script->lineno()); 1.817 + return sequentialExecution(true, status); 1.818 + 1.819 + case Method_Skipped: 1.820 + // A "skipped" result either means that we are compiling 1.821 + // in parallel OR some other transient error occurred. 1.822 + if (script->isParallelIonCompilingOffThread()) { 1.823 + Spew(SpewCompile, 1.824 + "Script %p:%s:%d compiling off-thread", 1.825 + script.get(), script->filename(), script->lineno()); 1.826 + offMainThreadCompilationsInProgress = true; 1.827 + continue; 1.828 + } 1.829 + return sequentialExecution(false, status); 1.830 + 1.831 + case Method_Compiled: 1.832 + Spew(SpewCompile, 1.833 + "Script %p:%s:%d compiled", 1.834 + script.get(), script->filename(), script->lineno()); 1.835 + JS_ASSERT(script->hasParallelIonScript()); 1.836 + 1.837 + if (isInitialScript(script)) { 1.838 + JitCompartment *jitComp = cx_->compartment()->jitCompartment(); 1.839 + if (!jitComp->notifyOfActiveParallelEntryScript(cx_, script)) { 1.840 + *status = ExecutionFatal; 1.841 + return RedLight; 1.842 + } 1.843 + } 1.844 + 1.845 + break; 1.846 + } 1.847 + } 1.848 + 1.849 + // At this point, either the script was already compiled 1.850 + // or we just compiled it. Check whether its "uncompiled 1.851 + // call target" flag is set and add the targets to our 1.852 + // worklist if so. Clear the flag after that, since we 1.853 + // will be compiling the call targets. 1.854 + JS_ASSERT(script->hasParallelIonScript()); 1.855 + if (appendCallTargetsToWorklist(i, status) == RedLight) 1.856 + return RedLight; 1.857 + } 1.858 + 1.859 + // If there is compilation occurring in a helper thread, then 1.860 + // run a warmup iterations in the main thread while we wait. 1.861 + // There is a chance that this warmup will finish all the work 1.862 + // we have to do, so we should stop then, unless we are in 1.863 + // compile mode, in which case we'll continue to block. 1.864 + // 1.865 + // Note that even in compile mode, we can't block *forever*: 1.866 + // - OMTC compiles will finish; 1.867 + // - no work is being done, so use counts on not-yet-baselined 1.868 + // scripts will not increase. 1.869 + if (offMainThreadCompilationsInProgress || gatheringTypeInformation) { 1.870 + bool stopIfComplete = (mode_ != ForkJoinModeCompile); 1.871 + if (warmupExecution(stopIfComplete, status) == RedLight) 1.872 + return RedLight; 1.873 + continue; 1.874 + } 1.875 + 1.876 + // All compilations are complete. However, be careful: it is 1.877 + // possible that a garbage collection occurred while we were 1.878 + // iterating and caused some of the scripts we thought we had 1.879 + // compiled to be collected. In that case, we will just have 1.880 + // to begin again. 1.881 + bool allScriptsPresent = true; 1.882 + for (uint32_t i = 0; i < worklist_.length(); i++) { 1.883 + if (!worklist_[i]->hasParallelIonScript()) { 1.884 + if (worklistData_[i].stallCount < stallThreshold) { 1.885 + worklistData_[i].reset(); 1.886 + allScriptsPresent = false; 1.887 + 1.888 + Spew(SpewCompile, 1.889 + "Script %p:%s:%d is not stalled, " 1.890 + "but no parallel ion script found, " 1.891 + "restarting loop", 1.892 + script.get(), script->filename(), script->lineno()); 1.893 + } 1.894 + } 1.895 + } 1.896 + 1.897 + if (allScriptsPresent) 1.898 + break; 1.899 + } 1.900 + 1.901 + Spew(SpewCompile, "Compilation complete (final worklist length %d)", 1.902 + worklist_.length()); 1.903 + 1.904 + // At this point, all scripts and their transitive callees are 1.905 + // either stalled (indicating they are unlikely to be called) or 1.906 + // in a compiled state. Therefore we can clear the 1.907 + // "hasUncompiledCallTarget" flag on them and then clear the 1.908 + // worklist. 1.909 + for (uint32_t i = 0; i < worklist_.length(); i++) { 1.910 + if (worklist_[i]->hasParallelIonScript()) { 1.911 + JS_ASSERT(worklistData_[i].calleesEnqueued); 1.912 + worklist_[i]->parallelIonScript()->clearHasUncompiledCallTarget(); 1.913 + } else { 1.914 + JS_ASSERT(worklistData_[i].stallCount >= stallThreshold); 1.915 + } 1.916 + } 1.917 + worklist_.clear(); 1.918 + worklistData_.clear(); 1.919 + return GreenLight; 1.920 +} 1.921 + 1.922 +ForkJoinOperation::TrafficLight 1.923 +ForkJoinOperation::appendCallTargetsToWorklist(uint32_t index, ExecutionStatus *status) 1.924 +{ 1.925 + // GreenLight: call targets appended 1.926 + // RedLight: fatal error or completed work via warmups or fallback 1.927 + 1.928 + JS_ASSERT(worklist_[index]->hasParallelIonScript()); 1.929 + 1.930 + // Check whether we have already enqueued the targets for 1.931 + // this entry and avoid doing it again if so. 1.932 + if (worklistData_[index].calleesEnqueued) 1.933 + return GreenLight; 1.934 + worklistData_[index].calleesEnqueued = true; 1.935 + 1.936 + // Iterate through the callees and enqueue them. 1.937 + RootedScript target(cx_); 1.938 + IonScript *ion = worklist_[index]->parallelIonScript(); 1.939 + for (uint32_t i = 0; i < ion->callTargetEntries(); i++) { 1.940 + target = ion->callTargetList()[i]; 1.941 + parallel::Spew(parallel::SpewCompile, 1.942 + "Adding call target %s:%u", 1.943 + target->filename(), target->lineno()); 1.944 + if (appendCallTargetToWorklist(target, status) == RedLight) 1.945 + return RedLight; 1.946 + } 1.947 + 1.948 + return GreenLight; 1.949 +} 1.950 + 1.951 +ForkJoinOperation::TrafficLight 1.952 +ForkJoinOperation::appendCallTargetToWorklist(HandleScript script, ExecutionStatus *status) 1.953 +{ 1.954 + // GreenLight: call target appended if necessary 1.955 + // RedLight: fatal error or completed work via warmups or fallback 1.956 + 1.957 + JS_ASSERT(script); 1.958 + 1.959 + // Fallback to sequential if disabled. 1.960 + if (!script->canParallelIonCompile()) { 1.961 + Spew(SpewCompile, "Skipping %p:%s:%u, canParallelIonCompile() is false", 1.962 + script.get(), script->filename(), script->lineno()); 1.963 + return sequentialExecution(true, status); 1.964 + } 1.965 + 1.966 + if (script->hasParallelIonScript()) { 1.967 + // Skip if the code is expected to result in a bailout. 1.968 + if (script->parallelIonScript()->bailoutExpected()) { 1.969 + Spew(SpewCompile, "Skipping %p:%s:%u, bailout expected", 1.970 + script.get(), script->filename(), script->lineno()); 1.971 + return sequentialExecution(false, status); 1.972 + } 1.973 + } 1.974 + 1.975 + if (!addToWorklist(script)) 1.976 + return fatalError(status); 1.977 + 1.978 + return GreenLight; 1.979 +} 1.980 + 1.981 +bool 1.982 +ForkJoinOperation::addToWorklist(HandleScript script) 1.983 +{ 1.984 + for (uint32_t i = 0; i < worklist_.length(); i++) { 1.985 + if (worklist_[i] == script) { 1.986 + Spew(SpewCompile, "Skipping %p:%s:%u, already in worklist", 1.987 + script.get(), script->filename(), script->lineno()); 1.988 + return true; 1.989 + } 1.990 + } 1.991 + 1.992 + Spew(SpewCompile, "Enqueued %p:%s:%u", 1.993 + script.get(), script->filename(), script->lineno()); 1.994 + 1.995 + // Note that we add all possibly compilable functions to the worklist, 1.996 + // even if they're already compiled. This is so that we can return 1.997 + // Method_Compiled and not Method_Skipped if we have a worklist full of 1.998 + // already-compiled functions. 1.999 + if (!worklist_.append(script)) 1.1000 + return false; 1.1001 + 1.1002 + // we have not yet enqueued the callees of this script 1.1003 + if (!worklistData_.append(WorklistData())) 1.1004 + return false; 1.1005 + worklistData_[worklistData_.length() - 1].reset(); 1.1006 + 1.1007 + return true; 1.1008 +} 1.1009 + 1.1010 +ForkJoinOperation::TrafficLight 1.1011 +ForkJoinOperation::sequentialExecution(bool disqualified, ExecutionStatus *status) 1.1012 +{ 1.1013 + // RedLight: fatal error or completed work 1.1014 + 1.1015 + *status = sequentialExecution(disqualified); 1.1016 + return RedLight; 1.1017 +} 1.1018 + 1.1019 +ExecutionStatus 1.1020 +ForkJoinOperation::sequentialExecution(bool disqualified) 1.1021 +{ 1.1022 + // XXX use disqualified to set parallelIon to ION_DISABLED_SCRIPT? 1.1023 + 1.1024 + Spew(SpewOps, "Executing sequential execution (disqualified=%d).", 1.1025 + disqualified); 1.1026 + 1.1027 + if (sliceStart_ == sliceEnd_) 1.1028 + return ExecutionSequential; 1.1029 + 1.1030 + RootedValue funVal(cx_, ObjectValue(*fun_)); 1.1031 + if (!ExecuteSequentially(cx_, funVal, &sliceStart_, sliceEnd_)) 1.1032 + return ExecutionFatal; 1.1033 + MOZ_ASSERT(sliceStart_ == sliceEnd_); 1.1034 + return ExecutionSequential; 1.1035 +} 1.1036 + 1.1037 +ForkJoinOperation::TrafficLight 1.1038 +ForkJoinOperation::fatalError(ExecutionStatus *status) 1.1039 +{ 1.1040 + // RedLight: fatal error 1.1041 + 1.1042 + *status = ExecutionFatal; 1.1043 + return RedLight; 1.1044 +} 1.1045 + 1.1046 +static const char * 1.1047 +BailoutExplanation(ParallelBailoutCause cause) 1.1048 +{ 1.1049 + switch (cause) { 1.1050 + case ParallelBailoutNone: 1.1051 + return "no particular reason"; 1.1052 + case ParallelBailoutCompilationSkipped: 1.1053 + return "compilation failed (method skipped)"; 1.1054 + case ParallelBailoutCompilationFailure: 1.1055 + return "compilation failed"; 1.1056 + case ParallelBailoutInterrupt: 1.1057 + return "interrupted"; 1.1058 + case ParallelBailoutFailedIC: 1.1059 + return "failed to attach stub to IC"; 1.1060 + case ParallelBailoutHeapBusy: 1.1061 + return "heap busy flag set during interrupt"; 1.1062 + case ParallelBailoutMainScriptNotPresent: 1.1063 + return "main script not present"; 1.1064 + case ParallelBailoutCalledToUncompiledScript: 1.1065 + return "called to uncompiled script"; 1.1066 + case ParallelBailoutIllegalWrite: 1.1067 + return "illegal write"; 1.1068 + case ParallelBailoutAccessToIntrinsic: 1.1069 + return "access to intrinsic"; 1.1070 + case ParallelBailoutOverRecursed: 1.1071 + return "over recursed"; 1.1072 + case ParallelBailoutOutOfMemory: 1.1073 + return "out of memory"; 1.1074 + case ParallelBailoutUnsupported: 1.1075 + return "unsupported"; 1.1076 + case ParallelBailoutUnsupportedVM: 1.1077 + return "unsupported operation in VM call"; 1.1078 + case ParallelBailoutUnsupportedStringComparison: 1.1079 + return "unsupported string comparison"; 1.1080 + case ParallelBailoutRequestedGC: 1.1081 + return "requested GC"; 1.1082 + case ParallelBailoutRequestedZoneGC: 1.1083 + return "requested zone GC"; 1.1084 + default: 1.1085 + return "no known reason"; 1.1086 + } 1.1087 +} 1.1088 + 1.1089 +bool 1.1090 +ForkJoinOperation::isInitialScript(HandleScript script) 1.1091 +{ 1.1092 + return fun_->is<JSFunction>() && (fun_->as<JSFunction>().nonLazyScript() == script); 1.1093 +} 1.1094 + 1.1095 +void 1.1096 +ForkJoinOperation::determineBailoutCause() 1.1097 +{ 1.1098 + bailoutCause = ParallelBailoutNone; 1.1099 + for (uint32_t i = 0; i < bailoutRecords_.length(); i++) { 1.1100 + if (bailoutRecords_[i].cause == ParallelBailoutNone) 1.1101 + continue; 1.1102 + 1.1103 + if (bailoutRecords_[i].cause == ParallelBailoutInterrupt) 1.1104 + continue; 1.1105 + 1.1106 + bailoutCause = bailoutRecords_[i].cause; 1.1107 + const char *causeStr = BailoutExplanation(bailoutCause); 1.1108 + if (bailoutRecords_[i].depth) { 1.1109 + bailoutScript = bailoutRecords_[i].trace[0].script; 1.1110 + bailoutBytecode = bailoutRecords_[i].trace[0].bytecode; 1.1111 + 1.1112 + const char *filename = bailoutScript->filename(); 1.1113 + int line = JS_PCToLineNumber(cx_, bailoutScript, bailoutBytecode); 1.1114 + JS_ReportWarning(cx_, "Bailed out of parallel operation: %s at %s:%d", 1.1115 + causeStr, filename, line); 1.1116 + 1.1117 + Spew(SpewBailouts, "Bailout from thread %d: cause %d at loc %s:%d", 1.1118 + i, 1.1119 + bailoutCause, 1.1120 + bailoutScript->filename(), 1.1121 + PCToLineNumber(bailoutScript, bailoutBytecode)); 1.1122 + } else { 1.1123 + JS_ReportWarning(cx_, "Bailed out of parallel operation: %s", 1.1124 + causeStr); 1.1125 + 1.1126 + Spew(SpewBailouts, "Bailout from thread %d: cause %d, unknown loc", 1.1127 + i, 1.1128 + bailoutCause); 1.1129 + } 1.1130 + } 1.1131 +} 1.1132 + 1.1133 +bool 1.1134 +ForkJoinOperation::invalidateBailedOutScripts() 1.1135 +{ 1.1136 + Vector<types::RecompileInfo> invalid(cx_); 1.1137 + for (uint32_t i = 0; i < bailoutRecords_.length(); i++) { 1.1138 + RootedScript script(cx_, bailoutRecords_[i].topScript); 1.1139 + 1.1140 + // No script to invalidate. 1.1141 + if (!script || !script->hasParallelIonScript()) 1.1142 + continue; 1.1143 + 1.1144 + Spew(SpewBailouts, 1.1145 + "Bailout from thread %d: cause %d, topScript %p:%s:%d", 1.1146 + i, 1.1147 + bailoutRecords_[i].cause, 1.1148 + script.get(), script->filename(), script->lineno()); 1.1149 + 1.1150 + switch (bailoutRecords_[i].cause) { 1.1151 + // An interrupt is not the fault of the script, so don't 1.1152 + // invalidate it. 1.1153 + case ParallelBailoutInterrupt: continue; 1.1154 + 1.1155 + // An illegal write will not be made legal by invalidation. 1.1156 + case ParallelBailoutIllegalWrite: continue; 1.1157 + 1.1158 + // For other cases, consider invalidation. 1.1159 + default: break; 1.1160 + } 1.1161 + 1.1162 + // Already invalidated. 1.1163 + if (hasScript(invalid, script)) 1.1164 + continue; 1.1165 + 1.1166 + Spew(SpewBailouts, "Invalidating script %p:%s:%d due to cause %d", 1.1167 + script.get(), script->filename(), script->lineno(), 1.1168 + bailoutRecords_[i].cause); 1.1169 + 1.1170 + types::RecompileInfo co = script->parallelIonScript()->recompileInfo(); 1.1171 + 1.1172 + if (!invalid.append(co)) 1.1173 + return false; 1.1174 + 1.1175 + // any script that we have marked for invalidation will need 1.1176 + // to be recompiled 1.1177 + if (!addToWorklist(script)) 1.1178 + return false; 1.1179 + } 1.1180 + 1.1181 + Invalidate(cx_, invalid); 1.1182 + 1.1183 + return true; 1.1184 +} 1.1185 + 1.1186 +ForkJoinOperation::TrafficLight 1.1187 +ForkJoinOperation::warmupExecution(bool stopIfComplete, ExecutionStatus *status) 1.1188 +{ 1.1189 + // GreenLight: warmup succeeded, still more work to do 1.1190 + // RedLight: fatal error or warmup completed all work (check status) 1.1191 + 1.1192 + if (sliceStart_ == sliceEnd_) { 1.1193 + Spew(SpewOps, "Warmup execution finished all the work."); 1.1194 + 1.1195 + if (stopIfComplete) { 1.1196 + *status = ExecutionWarmup; 1.1197 + return RedLight; 1.1198 + } 1.1199 + 1.1200 + // If we finished all slices in warmup, be sure check the 1.1201 + // interrupt flag. This is because we won't be running more JS 1.1202 + // code, and thus no more automatic checking of the interrupt 1.1203 + // flag. 1.1204 + if (!CheckForInterrupt(cx_)) { 1.1205 + *status = ExecutionFatal; 1.1206 + return RedLight; 1.1207 + } 1.1208 + 1.1209 + return GreenLight; 1.1210 + } 1.1211 + 1.1212 + Spew(SpewOps, "Executing warmup from slice %d.", sliceStart_); 1.1213 + 1.1214 + AutoEnterWarmup warmup(cx_->runtime()); 1.1215 + RootedValue funVal(cx_, ObjectValue(*fun_)); 1.1216 + if (!ExecuteSequentially(cx_, funVal, &sliceStart_, sliceStart_ + 1)) { 1.1217 + *status = ExecutionFatal; 1.1218 + return RedLight; 1.1219 + } 1.1220 + 1.1221 + return GreenLight; 1.1222 +} 1.1223 + 1.1224 +ForkJoinOperation::TrafficLight 1.1225 +ForkJoinOperation::parallelExecution(ExecutionStatus *status) 1.1226 +{ 1.1227 + // GreenLight: bailout occurred, keep trying 1.1228 + // RedLight: fatal error or all work completed 1.1229 + 1.1230 + // Recursive use of the ThreadPool is not supported. Right now we 1.1231 + // cannot get here because parallel code cannot invoke native 1.1232 + // functions such as ForkJoin(). 1.1233 + JS_ASSERT(ForkJoinContext::current() == nullptr); 1.1234 + 1.1235 + if (sliceStart_ == sliceEnd_) { 1.1236 + Spew(SpewOps, "Warmup execution finished all the work."); 1.1237 + *status = ExecutionWarmup; 1.1238 + return RedLight; 1.1239 + } 1.1240 + 1.1241 + ForkJoinActivation activation(cx_); 1.1242 + ThreadPool *threadPool = &cx_->runtime()->threadPool; 1.1243 + ForkJoinShared shared(cx_, threadPool, fun_, sliceStart_, sliceEnd_, &bailoutRecords_[0]); 1.1244 + if (!shared.init()) { 1.1245 + *status = ExecutionFatal; 1.1246 + return RedLight; 1.1247 + } 1.1248 + 1.1249 + switch (shared.execute()) { 1.1250 + case TP_SUCCESS: 1.1251 + *status = ExecutionParallel; 1.1252 + return RedLight; 1.1253 + 1.1254 + case TP_FATAL: 1.1255 + *status = ExecutionFatal; 1.1256 + return RedLight; 1.1257 + 1.1258 + case TP_RETRY_SEQUENTIALLY: 1.1259 + case TP_RETRY_AFTER_GC: 1.1260 + break; // bailout 1.1261 + } 1.1262 + 1.1263 + return GreenLight; 1.1264 +} 1.1265 + 1.1266 +ForkJoinOperation::TrafficLight 1.1267 +ForkJoinOperation::recoverFromBailout(ExecutionStatus *status) 1.1268 +{ 1.1269 + // GreenLight: bailout recovered, try to compile-and-run again 1.1270 + // RedLight: fatal error 1.1271 + 1.1272 + bailouts += 1; 1.1273 + determineBailoutCause(); 1.1274 + 1.1275 + SpewBailout(bailouts, bailoutScript, bailoutBytecode, bailoutCause); 1.1276 + 1.1277 + // After any bailout, we always scan over callee list of main 1.1278 + // function, if nothing else 1.1279 + RootedScript mainScript(cx_, fun_->nonLazyScript()); 1.1280 + if (!addToWorklist(mainScript)) 1.1281 + return fatalError(status); 1.1282 + 1.1283 + // Also invalidate and recompile any callees that were implicated 1.1284 + // by the bailout 1.1285 + if (!invalidateBailedOutScripts()) 1.1286 + return fatalError(status); 1.1287 + 1.1288 + if (warmupExecution(/*stopIfComplete:*/true, status) == RedLight) 1.1289 + return RedLight; 1.1290 + 1.1291 + return GreenLight; 1.1292 +} 1.1293 + 1.1294 +bool 1.1295 +ForkJoinOperation::hasScript(Vector<types::RecompileInfo> &scripts, JSScript *script) 1.1296 +{ 1.1297 + for (uint32_t i = 0; i < scripts.length(); i++) { 1.1298 + if (scripts[i] == script->parallelIonScript()->recompileInfo()) 1.1299 + return true; 1.1300 + } 1.1301 + return false; 1.1302 +} 1.1303 + 1.1304 +// Can only enter callees with a valid IonScript. 1.1305 +template <uint32_t maxArgc> 1.1306 +class ParallelIonInvoke 1.1307 +{ 1.1308 + EnterJitCode enter_; 1.1309 + void *jitcode_; 1.1310 + void *calleeToken_; 1.1311 + Value argv_[maxArgc + 2]; 1.1312 + uint32_t argc_; 1.1313 + 1.1314 + public: 1.1315 + Value *args; 1.1316 + 1.1317 + ParallelIonInvoke(JSRuntime *rt, 1.1318 + HandleFunction callee, 1.1319 + uint32_t argc) 1.1320 + : argc_(argc), 1.1321 + args(argv_ + 2) 1.1322 + { 1.1323 + JS_ASSERT(argc <= maxArgc + 2); 1.1324 + 1.1325 + // Set 'callee' and 'this'. 1.1326 + argv_[0] = ObjectValue(*callee); 1.1327 + argv_[1] = UndefinedValue(); 1.1328 + 1.1329 + // Find JIT code pointer. 1.1330 + IonScript *ion = callee->nonLazyScript()->parallelIonScript(); 1.1331 + JitCode *code = ion->method(); 1.1332 + jitcode_ = code->raw(); 1.1333 + enter_ = rt->jitRuntime()->enterIon(); 1.1334 + calleeToken_ = CalleeToToken(callee); 1.1335 + } 1.1336 + 1.1337 + bool invoke(PerThreadData *perThread) { 1.1338 + RootedValue result(perThread); 1.1339 + CALL_GENERATED_CODE(enter_, jitcode_, argc_ + 1, argv_ + 1, nullptr, calleeToken_, 1.1340 + nullptr, 0, result.address()); 1.1341 + return !result.isMagic(); 1.1342 + } 1.1343 +}; 1.1344 + 1.1345 +///////////////////////////////////////////////////////////////////////////// 1.1346 +// ForkJoinShared 1.1347 +// 1.1348 + 1.1349 +ForkJoinShared::ForkJoinShared(JSContext *cx, 1.1350 + ThreadPool *threadPool, 1.1351 + HandleFunction fun, 1.1352 + uint16_t sliceStart, 1.1353 + uint16_t sliceEnd, 1.1354 + ParallelBailoutRecord *records) 1.1355 + : cx_(cx), 1.1356 + threadPool_(threadPool), 1.1357 + fun_(fun), 1.1358 + sliceStart_(sliceStart), 1.1359 + sliceEnd_(sliceEnd), 1.1360 + cxLock_(nullptr), 1.1361 + records_(records), 1.1362 + allocators_(cx), 1.1363 + gcRequested_(false), 1.1364 + gcReason_(JS::gcreason::NUM_REASONS), 1.1365 + gcZone_(nullptr), 1.1366 + abort_(false), 1.1367 + fatal_(false) 1.1368 +{ 1.1369 +} 1.1370 + 1.1371 +bool 1.1372 +ForkJoinShared::init() 1.1373 +{ 1.1374 + // Create temporary arenas to hold the data allocated during the 1.1375 + // parallel code. 1.1376 + // 1.1377 + // Note: you might think (as I did, initially) that we could use 1.1378 + // compartment |Allocator| for the main thread. This is not true, 1.1379 + // because when executing parallel code we sometimes check what 1.1380 + // arena list an object is in to decide if it is writable. If we 1.1381 + // used the compartment |Allocator| for the main thread, then the 1.1382 + // main thread would be permitted to write to any object it wants. 1.1383 + 1.1384 + if (!Monitor::init()) 1.1385 + return false; 1.1386 + 1.1387 + cxLock_ = PR_NewLock(); 1.1388 + if (!cxLock_) 1.1389 + return false; 1.1390 + 1.1391 + for (unsigned i = 0; i < threadPool_->numWorkers(); i++) { 1.1392 + Allocator *allocator = cx_->new_<Allocator>(cx_->zone()); 1.1393 + if (!allocator) 1.1394 + return false; 1.1395 + 1.1396 + if (!allocators_.append(allocator)) { 1.1397 + js_delete(allocator); 1.1398 + return false; 1.1399 + } 1.1400 + } 1.1401 + 1.1402 + return true; 1.1403 +} 1.1404 + 1.1405 +ForkJoinShared::~ForkJoinShared() 1.1406 +{ 1.1407 + PR_DestroyLock(cxLock_); 1.1408 + 1.1409 + while (allocators_.length() > 0) 1.1410 + js_delete(allocators_.popCopy()); 1.1411 +} 1.1412 + 1.1413 +ParallelResult 1.1414 +ForkJoinShared::execute() 1.1415 +{ 1.1416 + // Sometimes a GC request occurs *just before* we enter into the 1.1417 + // parallel section. Rather than enter into the parallel section 1.1418 + // and then abort, we just check here and abort early. 1.1419 + if (cx_->runtime()->interruptPar) 1.1420 + return TP_RETRY_SEQUENTIALLY; 1.1421 + 1.1422 + AutoLockMonitor lock(*this); 1.1423 + 1.1424 + ParallelResult jobResult = TP_SUCCESS; 1.1425 + { 1.1426 + AutoUnlockMonitor unlock(*this); 1.1427 + 1.1428 + // Push parallel tasks and wait until they're all done. 1.1429 + jobResult = threadPool_->executeJob(cx_, this, sliceStart_, sliceEnd_); 1.1430 + if (jobResult == TP_FATAL) 1.1431 + return TP_FATAL; 1.1432 + } 1.1433 + 1.1434 + transferArenasToCompartmentAndProcessGCRequests(); 1.1435 + 1.1436 + // Check if any of the workers failed. 1.1437 + if (abort_) { 1.1438 + if (fatal_) 1.1439 + return TP_FATAL; 1.1440 + return TP_RETRY_SEQUENTIALLY; 1.1441 + } 1.1442 + 1.1443 +#ifdef DEBUG 1.1444 + Spew(SpewOps, "Completed parallel job [slices: %d, threads: %d, stolen: %d (work stealing:%s)]", 1.1445 + sliceEnd_ - sliceStart_, 1.1446 + threadPool_->numWorkers(), 1.1447 + threadPool_->stolenSlices(), 1.1448 + threadPool_->workStealing() ? "ON" : "OFF"); 1.1449 +#endif 1.1450 + 1.1451 + // Everything went swimmingly. Give yourself a pat on the back. 1.1452 + return jobResult; 1.1453 +} 1.1454 + 1.1455 +void 1.1456 +ForkJoinShared::transferArenasToCompartmentAndProcessGCRequests() 1.1457 +{ 1.1458 + JSCompartment *comp = cx_->compartment(); 1.1459 + for (unsigned i = 0; i < threadPool_->numWorkers(); i++) 1.1460 + comp->adoptWorkerAllocator(allocators_[i]); 1.1461 + 1.1462 + if (gcRequested_) { 1.1463 + if (!gcZone_) 1.1464 + TriggerGC(cx_->runtime(), gcReason_); 1.1465 + else 1.1466 + TriggerZoneGC(gcZone_, gcReason_); 1.1467 + gcRequested_ = false; 1.1468 + gcZone_ = nullptr; 1.1469 + } 1.1470 +} 1.1471 + 1.1472 +bool 1.1473 +ForkJoinShared::executeFromWorker(ThreadPoolWorker *worker, uintptr_t stackLimit) 1.1474 +{ 1.1475 + PerThreadData thisThread(cx_->runtime()); 1.1476 + if (!thisThread.init()) { 1.1477 + setAbortFlagAndRequestInterrupt(true); 1.1478 + return false; 1.1479 + } 1.1480 + TlsPerThreadData.set(&thisThread); 1.1481 + 1.1482 +#ifdef JS_ARM_SIMULATOR 1.1483 + stackLimit = Simulator::StackLimit(); 1.1484 +#endif 1.1485 + 1.1486 + // Don't use setIonStackLimit() because that acquires the ionStackLimitLock, and the 1.1487 + // lock has not been initialized in these cases. 1.1488 + thisThread.jitStackLimit = stackLimit; 1.1489 + executePortion(&thisThread, worker); 1.1490 + TlsPerThreadData.set(nullptr); 1.1491 + 1.1492 + return !abort_; 1.1493 +} 1.1494 + 1.1495 +bool 1.1496 +ForkJoinShared::executeFromMainThread(ThreadPoolWorker *worker) 1.1497 +{ 1.1498 + executePortion(&cx_->mainThread(), worker); 1.1499 + return !abort_; 1.1500 +} 1.1501 + 1.1502 +void 1.1503 +ForkJoinShared::executePortion(PerThreadData *perThread, ThreadPoolWorker *worker) 1.1504 +{ 1.1505 + // WARNING: This code runs ON THE PARALLEL WORKER THREAD. 1.1506 + // Be careful when accessing cx_. 1.1507 + 1.1508 + // ForkJoinContext already contains an AutoAssertNoGC; however, the analysis 1.1509 + // does not propagate this type information. We duplicate the assertion 1.1510 + // here for maximum clarity. 1.1511 + JS::AutoAssertNoGC nogc(runtime()); 1.1512 + 1.1513 + Allocator *allocator = allocators_[worker->id()]; 1.1514 + ForkJoinContext cx(perThread, worker, allocator, this, &records_[worker->id()]); 1.1515 + AutoSetForkJoinContext autoContext(&cx); 1.1516 + 1.1517 +#ifdef DEBUG 1.1518 + // Set the maximum worker and slice number for prettier spewing. 1.1519 + cx.maxWorkerId = threadPool_->numWorkers(); 1.1520 +#endif 1.1521 + 1.1522 + Spew(SpewOps, "Up"); 1.1523 + 1.1524 + // Make a new IonContext for the slice, which is needed if we need to 1.1525 + // re-enter the VM. 1.1526 + IonContext icx(CompileRuntime::get(cx_->runtime()), 1.1527 + CompileCompartment::get(cx_->compartment()), 1.1528 + nullptr); 1.1529 + 1.1530 + JS_ASSERT(cx.bailoutRecord->topScript == nullptr); 1.1531 + 1.1532 + if (!fun_->nonLazyScript()->hasParallelIonScript()) { 1.1533 + // Sometimes, particularly with GCZeal, the parallel ion 1.1534 + // script can be collected between starting the parallel 1.1535 + // op and reaching this point. In that case, we just fail 1.1536 + // and fallback. 1.1537 + Spew(SpewOps, "Down (Script no longer present)"); 1.1538 + cx.bailoutRecord->setCause(ParallelBailoutMainScriptNotPresent); 1.1539 + setAbortFlagAndRequestInterrupt(false); 1.1540 + } else { 1.1541 + ParallelIonInvoke<3> fii(cx_->runtime(), fun_, 3); 1.1542 + 1.1543 + fii.args[0] = Int32Value(worker->id()); 1.1544 + fii.args[1] = Int32Value(sliceStart_); 1.1545 + fii.args[2] = Int32Value(sliceEnd_); 1.1546 + 1.1547 + bool ok = fii.invoke(perThread); 1.1548 + JS_ASSERT(ok == !cx.bailoutRecord->topScript); 1.1549 + if (!ok) 1.1550 + setAbortFlagAndRequestInterrupt(false); 1.1551 + } 1.1552 + 1.1553 + Spew(SpewOps, "Down"); 1.1554 +} 1.1555 + 1.1556 +void 1.1557 +ForkJoinShared::setAbortFlagDueToInterrupt(ForkJoinContext &cx) 1.1558 +{ 1.1559 + JS_ASSERT(cx_->runtime()->interruptPar); 1.1560 + // The GC Needed flag should not be set during parallel 1.1561 + // execution. Instead, one of the requestGC() or 1.1562 + // requestZoneGC() methods should be invoked. 1.1563 + JS_ASSERT(!cx_->runtime()->gcIsNeeded); 1.1564 + 1.1565 + if (!abort_) { 1.1566 + cx.bailoutRecord->setCause(ParallelBailoutInterrupt); 1.1567 + setAbortFlagAndRequestInterrupt(false); 1.1568 + } 1.1569 +} 1.1570 + 1.1571 +void 1.1572 +ForkJoinShared::setAbortFlagAndRequestInterrupt(bool fatal) 1.1573 +{ 1.1574 + AutoLockMonitor lock(*this); 1.1575 + 1.1576 + abort_ = true; 1.1577 + fatal_ = fatal_ || fatal; 1.1578 + 1.1579 + // Note: The ForkJoin trigger here avoids the expensive memory protection needed to 1.1580 + // interrupt Ion code compiled for sequential execution. 1.1581 + cx_->runtime()->requestInterrupt(JSRuntime::RequestInterruptAnyThreadForkJoin); 1.1582 +} 1.1583 + 1.1584 +void 1.1585 +ForkJoinShared::requestGC(JS::gcreason::Reason reason) 1.1586 +{ 1.1587 + AutoLockMonitor lock(*this); 1.1588 + 1.1589 + gcZone_ = nullptr; 1.1590 + gcReason_ = reason; 1.1591 + gcRequested_ = true; 1.1592 +} 1.1593 + 1.1594 +void 1.1595 +ForkJoinShared::requestZoneGC(JS::Zone *zone, JS::gcreason::Reason reason) 1.1596 +{ 1.1597 + AutoLockMonitor lock(*this); 1.1598 + 1.1599 + if (gcRequested_ && gcZone_ != zone) { 1.1600 + // If a full GC has been requested, or a GC for another zone, 1.1601 + // issue a request for a full GC. 1.1602 + gcZone_ = nullptr; 1.1603 + gcReason_ = reason; 1.1604 + gcRequested_ = true; 1.1605 + } else { 1.1606 + // Otherwise, just GC this zone. 1.1607 + gcZone_ = zone; 1.1608 + gcReason_ = reason; 1.1609 + gcRequested_ = true; 1.1610 + } 1.1611 +} 1.1612 + 1.1613 +///////////////////////////////////////////////////////////////////////////// 1.1614 +// ForkJoinContext 1.1615 +// 1.1616 + 1.1617 +ForkJoinContext::ForkJoinContext(PerThreadData *perThreadData, ThreadPoolWorker *worker, 1.1618 + Allocator *allocator, ForkJoinShared *shared, 1.1619 + ParallelBailoutRecord *bailoutRecord) 1.1620 + : ThreadSafeContext(shared->runtime(), perThreadData, Context_ForkJoin), 1.1621 + bailoutRecord(bailoutRecord), 1.1622 + targetRegionStart(nullptr), 1.1623 + targetRegionEnd(nullptr), 1.1624 + shared_(shared), 1.1625 + worker_(worker), 1.1626 + acquiredJSContext_(false), 1.1627 + nogc_(shared->runtime()) 1.1628 +{ 1.1629 + /* 1.1630 + * Unsafely set the zone. This is used to track malloc counters and to 1.1631 + * trigger GCs and is otherwise not thread-safe to access. 1.1632 + */ 1.1633 + zone_ = shared->zone(); 1.1634 + 1.1635 + /* 1.1636 + * Unsafely set the compartment. This is used to get read-only access to 1.1637 + * shared tables. 1.1638 + */ 1.1639 + compartment_ = shared->compartment(); 1.1640 + 1.1641 + allocator_ = allocator; 1.1642 +} 1.1643 + 1.1644 +bool 1.1645 +ForkJoinContext::isMainThread() const 1.1646 +{ 1.1647 + return perThreadData == &shared_->runtime()->mainThread; 1.1648 +} 1.1649 + 1.1650 +JSRuntime * 1.1651 +ForkJoinContext::runtime() 1.1652 +{ 1.1653 + return shared_->runtime(); 1.1654 +} 1.1655 + 1.1656 +JSContext * 1.1657 +ForkJoinContext::acquireJSContext() 1.1658 +{ 1.1659 + JSContext *cx = shared_->acquireJSContext(); 1.1660 + JS_ASSERT(!acquiredJSContext_); 1.1661 + acquiredJSContext_ = true; 1.1662 + return cx; 1.1663 +} 1.1664 + 1.1665 +void 1.1666 +ForkJoinContext::releaseJSContext() 1.1667 +{ 1.1668 + JS_ASSERT(acquiredJSContext_); 1.1669 + acquiredJSContext_ = false; 1.1670 + return shared_->releaseJSContext(); 1.1671 +} 1.1672 + 1.1673 +bool 1.1674 +ForkJoinContext::hasAcquiredJSContext() const 1.1675 +{ 1.1676 + return acquiredJSContext_; 1.1677 +} 1.1678 + 1.1679 +bool 1.1680 +ForkJoinContext::check() 1.1681 +{ 1.1682 + if (runtime()->interruptPar) { 1.1683 + shared_->setAbortFlagDueToInterrupt(*this); 1.1684 + return false; 1.1685 + } 1.1686 + return true; 1.1687 +} 1.1688 + 1.1689 +void 1.1690 +ForkJoinContext::requestGC(JS::gcreason::Reason reason) 1.1691 +{ 1.1692 + shared_->requestGC(reason); 1.1693 + bailoutRecord->setCause(ParallelBailoutRequestedGC); 1.1694 + shared_->setAbortFlagAndRequestInterrupt(false); 1.1695 +} 1.1696 + 1.1697 +void 1.1698 +ForkJoinContext::requestZoneGC(JS::Zone *zone, JS::gcreason::Reason reason) 1.1699 +{ 1.1700 + shared_->requestZoneGC(zone, reason); 1.1701 + bailoutRecord->setCause(ParallelBailoutRequestedZoneGC); 1.1702 + shared_->setAbortFlagAndRequestInterrupt(false); 1.1703 +} 1.1704 + 1.1705 +bool 1.1706 +ForkJoinContext::setPendingAbortFatal(ParallelBailoutCause cause) 1.1707 +{ 1.1708 + shared_->setPendingAbortFatal(); 1.1709 + bailoutRecord->setCause(cause); 1.1710 + return false; 1.1711 +} 1.1712 + 1.1713 +////////////////////////////////////////////////////////////////////////////// 1.1714 +// ParallelBailoutRecord 1.1715 + 1.1716 +void 1.1717 +js::ParallelBailoutRecord::init(JSContext *cx) 1.1718 +{ 1.1719 + reset(cx); 1.1720 +} 1.1721 + 1.1722 +void 1.1723 +js::ParallelBailoutRecord::reset(JSContext *cx) 1.1724 +{ 1.1725 + topScript = nullptr; 1.1726 + cause = ParallelBailoutNone; 1.1727 + depth = 0; 1.1728 +} 1.1729 + 1.1730 +void 1.1731 +js::ParallelBailoutRecord::setCause(ParallelBailoutCause cause, 1.1732 + JSScript *outermostScript, 1.1733 + JSScript *currentScript, 1.1734 + jsbytecode *currentPc) 1.1735 +{ 1.1736 + this->cause = cause; 1.1737 + updateCause(cause, outermostScript, currentScript, currentPc); 1.1738 +} 1.1739 + 1.1740 +void 1.1741 +js::ParallelBailoutRecord::updateCause(ParallelBailoutCause cause, 1.1742 + JSScript *outermostScript, 1.1743 + JSScript *currentScript, 1.1744 + jsbytecode *currentPc) 1.1745 +{ 1.1746 + JS_ASSERT_IF(outermostScript, currentScript); 1.1747 + JS_ASSERT_IF(outermostScript, outermostScript->hasParallelIonScript()); 1.1748 + JS_ASSERT_IF(currentScript, outermostScript); 1.1749 + JS_ASSERT_IF(!currentScript, !currentPc); 1.1750 + 1.1751 + if (this->cause == ParallelBailoutNone) 1.1752 + this->cause = cause; 1.1753 + 1.1754 + if (outermostScript) 1.1755 + this->topScript = outermostScript; 1.1756 + 1.1757 + if (currentScript) 1.1758 + addTrace(currentScript, currentPc); 1.1759 +} 1.1760 + 1.1761 +void 1.1762 +js::ParallelBailoutRecord::addTrace(JSScript *script, 1.1763 + jsbytecode *pc) 1.1764 +{ 1.1765 + // Ideally, this should never occur, because we should always have 1.1766 + // a script when we invoke setCause, but I havent' fully 1.1767 + // refactored things to that point yet: 1.1768 + if (topScript == nullptr && script != nullptr) 1.1769 + topScript = script; 1.1770 + 1.1771 + if (depth < MaxDepth) { 1.1772 + trace[depth].script = script; 1.1773 + trace[depth].bytecode = pc; 1.1774 + depth += 1; 1.1775 + } 1.1776 +} 1.1777 + 1.1778 +////////////////////////////////////////////////////////////////////////////// 1.1779 + 1.1780 +// 1.1781 +// Debug spew 1.1782 +// 1.1783 + 1.1784 +#ifdef DEBUG 1.1785 + 1.1786 +static const char * 1.1787 +ExecutionStatusToString(ExecutionStatus status) 1.1788 +{ 1.1789 + switch (status) { 1.1790 + case ExecutionFatal: 1.1791 + return "fatal"; 1.1792 + case ExecutionSequential: 1.1793 + return "sequential"; 1.1794 + case ExecutionWarmup: 1.1795 + return "warmup"; 1.1796 + case ExecutionParallel: 1.1797 + return "parallel"; 1.1798 + } 1.1799 + return "(unknown status)"; 1.1800 +} 1.1801 + 1.1802 +static const char * 1.1803 +MethodStatusToString(MethodStatus status) 1.1804 +{ 1.1805 + switch (status) { 1.1806 + case Method_Error: 1.1807 + return "error"; 1.1808 + case Method_CantCompile: 1.1809 + return "can't compile"; 1.1810 + case Method_Skipped: 1.1811 + return "skipped"; 1.1812 + case Method_Compiled: 1.1813 + return "compiled"; 1.1814 + } 1.1815 + return "(unknown status)"; 1.1816 +} 1.1817 + 1.1818 +static unsigned 1.1819 +NumberOfDigits(unsigned n) 1.1820 +{ 1.1821 + if (n == 0) 1.1822 + return 1; 1.1823 + unsigned d = 0; 1.1824 + while (n != 0) { 1.1825 + d++; 1.1826 + n /= 10; 1.1827 + } 1.1828 + return d; 1.1829 +} 1.1830 + 1.1831 +static const size_t BufferSize = 4096; 1.1832 + 1.1833 +class ParallelSpewer 1.1834 +{ 1.1835 + uint32_t depth; 1.1836 + bool colorable; 1.1837 + bool active[NumSpewChannels]; 1.1838 + 1.1839 + const char *color(const char *colorCode) { 1.1840 + if (!colorable) 1.1841 + return ""; 1.1842 + return colorCode; 1.1843 + } 1.1844 + 1.1845 + const char *reset() { return color("\x1b[0m"); } 1.1846 + const char *bold() { return color("\x1b[1m"); } 1.1847 + const char *red() { return color("\x1b[31m"); } 1.1848 + const char *green() { return color("\x1b[32m"); } 1.1849 + const char *yellow() { return color("\x1b[33m"); } 1.1850 + const char *cyan() { return color("\x1b[36m"); } 1.1851 + const char *workerColor(uint32_t id) { 1.1852 + static const char *colors[] = { 1.1853 + "\x1b[7m\x1b[31m", "\x1b[7m\x1b[32m", "\x1b[7m\x1b[33m", 1.1854 + "\x1b[7m\x1b[34m", "\x1b[7m\x1b[35m", "\x1b[7m\x1b[36m", 1.1855 + "\x1b[7m\x1b[37m", 1.1856 + "\x1b[31m", "\x1b[32m", "\x1b[33m", 1.1857 + "\x1b[34m", "\x1b[35m", "\x1b[36m", 1.1858 + "\x1b[37m" 1.1859 + }; 1.1860 + return color(colors[id % 14]); 1.1861 + } 1.1862 + 1.1863 + public: 1.1864 + ParallelSpewer() 1.1865 + : depth(0) 1.1866 + { 1.1867 + const char *env; 1.1868 + 1.1869 + mozilla::PodArrayZero(active); 1.1870 + env = getenv("PAFLAGS"); 1.1871 + if (env) { 1.1872 + if (strstr(env, "ops")) 1.1873 + active[SpewOps] = true; 1.1874 + if (strstr(env, "compile")) 1.1875 + active[SpewCompile] = true; 1.1876 + if (strstr(env, "bailouts")) 1.1877 + active[SpewBailouts] = true; 1.1878 + if (strstr(env, "full")) { 1.1879 + for (uint32_t i = 0; i < NumSpewChannels; i++) 1.1880 + active[i] = true; 1.1881 + } 1.1882 + } 1.1883 + 1.1884 + env = getenv("TERM"); 1.1885 + if (env && isatty(fileno(stderr))) { 1.1886 + if (strcmp(env, "xterm-color") == 0 || strcmp(env, "xterm-256color") == 0) 1.1887 + colorable = true; 1.1888 + } 1.1889 + } 1.1890 + 1.1891 + bool isActive(js::parallel::SpewChannel channel) { 1.1892 + return active[channel]; 1.1893 + } 1.1894 + 1.1895 + void spewVA(js::parallel::SpewChannel channel, const char *fmt, va_list ap) { 1.1896 + if (!active[channel]) 1.1897 + return; 1.1898 + 1.1899 + // Print into a buffer first so we use one fprintf, which usually 1.1900 + // doesn't get interrupted when running with multiple threads. 1.1901 + char buf[BufferSize]; 1.1902 + 1.1903 + if (ForkJoinContext *cx = ForkJoinContext::current()) { 1.1904 + // Print the format first into a buffer to right-justify the 1.1905 + // worker ids. 1.1906 + char bufbuf[BufferSize]; 1.1907 + JS_snprintf(bufbuf, BufferSize, "[%%sParallel:%%0%du%%s] ", 1.1908 + NumberOfDigits(cx->maxWorkerId)); 1.1909 + JS_snprintf(buf, BufferSize, bufbuf, workerColor(cx->workerId()), 1.1910 + cx->workerId(), reset()); 1.1911 + } else { 1.1912 + JS_snprintf(buf, BufferSize, "[Parallel:M] "); 1.1913 + } 1.1914 + 1.1915 + for (uint32_t i = 0; i < depth; i++) 1.1916 + JS_snprintf(buf + strlen(buf), BufferSize, " "); 1.1917 + 1.1918 + JS_vsnprintf(buf + strlen(buf), BufferSize, fmt, ap); 1.1919 + JS_snprintf(buf + strlen(buf), BufferSize, "\n"); 1.1920 + 1.1921 + fprintf(stderr, "%s", buf); 1.1922 + } 1.1923 + 1.1924 + void spew(js::parallel::SpewChannel channel, const char *fmt, ...) { 1.1925 + va_list ap; 1.1926 + va_start(ap, fmt); 1.1927 + spewVA(channel, fmt, ap); 1.1928 + va_end(ap); 1.1929 + } 1.1930 + 1.1931 + void beginOp(JSContext *cx, const char *name) { 1.1932 + if (!active[SpewOps]) 1.1933 + return; 1.1934 + 1.1935 + if (cx) { 1.1936 + jsbytecode *pc; 1.1937 + RootedScript script(cx, cx->currentScript(&pc)); 1.1938 + if (script && pc) { 1.1939 + NonBuiltinScriptFrameIter iter(cx); 1.1940 + if (iter.done()) { 1.1941 + spew(SpewOps, "%sBEGIN %s%s (%s:%u)", bold(), name, reset(), 1.1942 + script->filename(), PCToLineNumber(script, pc)); 1.1943 + } else { 1.1944 + spew(SpewOps, "%sBEGIN %s%s (%s:%u -> %s:%u)", bold(), name, reset(), 1.1945 + iter.script()->filename(), PCToLineNumber(iter.script(), iter.pc()), 1.1946 + script->filename(), PCToLineNumber(script, pc)); 1.1947 + } 1.1948 + } else { 1.1949 + spew(SpewOps, "%sBEGIN %s%s", bold(), name, reset()); 1.1950 + } 1.1951 + } else { 1.1952 + spew(SpewOps, "%sBEGIN %s%s", bold(), name, reset()); 1.1953 + } 1.1954 + 1.1955 + depth++; 1.1956 + } 1.1957 + 1.1958 + void endOp(ExecutionStatus status) { 1.1959 + if (!active[SpewOps]) 1.1960 + return; 1.1961 + 1.1962 + JS_ASSERT(depth > 0); 1.1963 + depth--; 1.1964 + 1.1965 + const char *statusColor; 1.1966 + switch (status) { 1.1967 + case ExecutionFatal: 1.1968 + statusColor = red(); 1.1969 + break; 1.1970 + case ExecutionSequential: 1.1971 + statusColor = yellow(); 1.1972 + break; 1.1973 + case ExecutionParallel: 1.1974 + statusColor = green(); 1.1975 + break; 1.1976 + default: 1.1977 + statusColor = reset(); 1.1978 + break; 1.1979 + } 1.1980 + 1.1981 + spew(SpewOps, "%sEND %s%s%s", bold(), 1.1982 + statusColor, ExecutionStatusToString(status), reset()); 1.1983 + } 1.1984 + 1.1985 + void bailout(uint32_t count, HandleScript script, 1.1986 + jsbytecode *pc, ParallelBailoutCause cause) { 1.1987 + if (!active[SpewOps]) 1.1988 + return; 1.1989 + 1.1990 + const char *filename = ""; 1.1991 + unsigned line=0, column=0; 1.1992 + if (script) { 1.1993 + line = PCToLineNumber(script, pc, &column); 1.1994 + filename = script->filename(); 1.1995 + } 1.1996 + 1.1997 + spew(SpewOps, "%s%sBAILOUT %d%s: %s (%d) at %s:%d:%d", bold(), yellow(), count, reset(), 1.1998 + BailoutExplanation(cause), cause, filename, line, column); 1.1999 + } 1.2000 + 1.2001 + void beginCompile(HandleScript script) { 1.2002 + if (!active[SpewCompile]) 1.2003 + return; 1.2004 + 1.2005 + spew(SpewCompile, "COMPILE %p:%s:%u", script.get(), script->filename(), script->lineno()); 1.2006 + depth++; 1.2007 + } 1.2008 + 1.2009 + void endCompile(MethodStatus status) { 1.2010 + if (!active[SpewCompile]) 1.2011 + return; 1.2012 + 1.2013 + JS_ASSERT(depth > 0); 1.2014 + depth--; 1.2015 + 1.2016 + const char *statusColor; 1.2017 + switch (status) { 1.2018 + case Method_Error: 1.2019 + case Method_CantCompile: 1.2020 + statusColor = red(); 1.2021 + break; 1.2022 + case Method_Skipped: 1.2023 + statusColor = yellow(); 1.2024 + break; 1.2025 + case Method_Compiled: 1.2026 + statusColor = green(); 1.2027 + break; 1.2028 + default: 1.2029 + statusColor = reset(); 1.2030 + break; 1.2031 + } 1.2032 + 1.2033 + spew(SpewCompile, "END %s%s%s", statusColor, MethodStatusToString(status), reset()); 1.2034 + } 1.2035 + 1.2036 + void spewMIR(MDefinition *mir, const char *fmt, va_list ap) { 1.2037 + if (!active[SpewCompile]) 1.2038 + return; 1.2039 + 1.2040 + char buf[BufferSize]; 1.2041 + JS_vsnprintf(buf, BufferSize, fmt, ap); 1.2042 + 1.2043 + JSScript *script = mir->block()->info().script(); 1.2044 + spew(SpewCompile, "%s%s%s: %s (%s:%u)", cyan(), mir->opName(), reset(), buf, 1.2045 + script->filename(), PCToLineNumber(script, mir->trackedPc())); 1.2046 + } 1.2047 + 1.2048 + void spewBailoutIR(IonLIRTraceData *data) { 1.2049 + if (!active[SpewBailouts]) 1.2050 + return; 1.2051 + 1.2052 + // If we didn't bail from a LIR/MIR but from a propagated parallel 1.2053 + // bailout, don't bother printing anything since we've printed it 1.2054 + // elsewhere. 1.2055 + if (data->mirOpName && data->script) { 1.2056 + spew(SpewBailouts, "%sBailout%s: %s / %s%s%s (block %d lir %d) (%s:%u)", yellow(), reset(), 1.2057 + data->lirOpName, cyan(), data->mirOpName, reset(), 1.2058 + data->blockIndex, data->lirIndex, data->script->filename(), 1.2059 + PCToLineNumber(data->script, data->pc)); 1.2060 + } 1.2061 + } 1.2062 +}; 1.2063 + 1.2064 +// Singleton instance of the spewer. 1.2065 +static ParallelSpewer spewer; 1.2066 + 1.2067 +bool 1.2068 +parallel::SpewEnabled(SpewChannel channel) 1.2069 +{ 1.2070 + return spewer.isActive(channel); 1.2071 +} 1.2072 + 1.2073 +void 1.2074 +parallel::Spew(SpewChannel channel, const char *fmt, ...) 1.2075 +{ 1.2076 + va_list ap; 1.2077 + va_start(ap, fmt); 1.2078 + spewer.spewVA(channel, fmt, ap); 1.2079 + va_end(ap); 1.2080 +} 1.2081 + 1.2082 +void 1.2083 +parallel::SpewBeginOp(JSContext *cx, const char *name) 1.2084 +{ 1.2085 + spewer.beginOp(cx, name); 1.2086 +} 1.2087 + 1.2088 +ExecutionStatus 1.2089 +parallel::SpewEndOp(ExecutionStatus status) 1.2090 +{ 1.2091 + spewer.endOp(status); 1.2092 + return status; 1.2093 +} 1.2094 + 1.2095 +void 1.2096 +parallel::SpewBailout(uint32_t count, HandleScript script, 1.2097 + jsbytecode *pc, ParallelBailoutCause cause) 1.2098 +{ 1.2099 + spewer.bailout(count, script, pc, cause); 1.2100 +} 1.2101 + 1.2102 +void 1.2103 +parallel::SpewBeginCompile(HandleScript script) 1.2104 +{ 1.2105 + spewer.beginCompile(script); 1.2106 +} 1.2107 + 1.2108 +MethodStatus 1.2109 +parallel::SpewEndCompile(MethodStatus status) 1.2110 +{ 1.2111 + spewer.endCompile(status); 1.2112 + return status; 1.2113 +} 1.2114 + 1.2115 +void 1.2116 +parallel::SpewMIR(MDefinition *mir, const char *fmt, ...) 1.2117 +{ 1.2118 + va_list ap; 1.2119 + va_start(ap, fmt); 1.2120 + spewer.spewMIR(mir, fmt, ap); 1.2121 + va_end(ap); 1.2122 +} 1.2123 + 1.2124 +void 1.2125 +parallel::SpewBailoutIR(IonLIRTraceData *data) 1.2126 +{ 1.2127 + spewer.spewBailoutIR(data); 1.2128 +} 1.2129 + 1.2130 +#endif // DEBUG 1.2131 + 1.2132 +bool 1.2133 +js::InExclusiveParallelSection() 1.2134 +{ 1.2135 + return InParallelSection() && ForkJoinContext::current()->hasAcquiredJSContext(); 1.2136 +} 1.2137 + 1.2138 +bool 1.2139 +js::ParallelTestsShouldPass(JSContext *cx) 1.2140 +{ 1.2141 + return jit::IsIonEnabled(cx) && 1.2142 + jit::IsBaselineEnabled(cx) && 1.2143 + !jit::js_JitOptions.eagerCompilation && 1.2144 + jit::js_JitOptions.baselineUsesBeforeCompile != 0 && 1.2145 + cx->runtime()->gcZeal() == 0; 1.2146 +} 1.2147 + 1.2148 +void 1.2149 +js::RequestInterruptForForkJoin(JSRuntime *rt, JSRuntime::InterruptMode mode) 1.2150 +{ 1.2151 + if (mode != JSRuntime::RequestInterruptAnyThreadDontStopIon) 1.2152 + rt->interruptPar = true; 1.2153 +} 1.2154 + 1.2155 +bool 1.2156 +js::intrinsic_SetForkJoinTargetRegion(JSContext *cx, unsigned argc, Value *vp) 1.2157 +{ 1.2158 + // This version of SetForkJoinTargetRegion is called during 1.2159 + // sequential execution. It is a no-op. The parallel version 1.2160 + // is intrinsic_SetForkJoinTargetRegionPar(), below. 1.2161 + return true; 1.2162 +} 1.2163 + 1.2164 +static bool 1.2165 +intrinsic_SetForkJoinTargetRegionPar(ForkJoinContext *cx, unsigned argc, Value *vp) 1.2166 +{ 1.2167 + // Sets the *target region*, which is the portion of the output 1.2168 + // buffer that the current iteration is permitted to write to. 1.2169 + // 1.2170 + // Note: it is important that the target region should be an 1.2171 + // entire element (or several elements) of the output array and 1.2172 + // not some region that spans from the middle of one element into 1.2173 + // the middle of another. This is because the guarding code 1.2174 + // assumes that handles, which never straddle across elements, 1.2175 + // will either be contained entirely within the target region or 1.2176 + // be contained entirely without of the region, and not straddling 1.2177 + // the region nor encompassing it. 1.2178 + 1.2179 + CallArgs args = CallArgsFromVp(argc, vp); 1.2180 + JS_ASSERT(argc == 3); 1.2181 + JS_ASSERT(args[0].isObject() && args[0].toObject().is<TypedObject>()); 1.2182 + JS_ASSERT(args[1].isInt32()); 1.2183 + JS_ASSERT(args[2].isInt32()); 1.2184 + 1.2185 + uint8_t *mem = args[0].toObject().as<TypedObject>().typedMem(); 1.2186 + int32_t start = args[1].toInt32(); 1.2187 + int32_t end = args[2].toInt32(); 1.2188 + 1.2189 + cx->targetRegionStart = mem + start; 1.2190 + cx->targetRegionEnd = mem + end; 1.2191 + return true; 1.2192 +} 1.2193 + 1.2194 +JS_JITINFO_NATIVE_PARALLEL(js::intrinsic_SetForkJoinTargetRegionInfo, 1.2195 + intrinsic_SetForkJoinTargetRegionPar); 1.2196 + 1.2197 +bool 1.2198 +js::intrinsic_ClearThreadLocalArenas(JSContext *cx, unsigned argc, Value *vp) 1.2199 +{ 1.2200 + return true; 1.2201 +} 1.2202 + 1.2203 +static bool 1.2204 +intrinsic_ClearThreadLocalArenasPar(ForkJoinContext *cx, unsigned argc, Value *vp) 1.2205 +{ 1.2206 + cx->allocator()->arenas.wipeDuringParallelExecution(cx->runtime()); 1.2207 + return true; 1.2208 +} 1.2209 + 1.2210 +JS_JITINFO_NATIVE_PARALLEL(js::intrinsic_ClearThreadLocalArenasInfo, 1.2211 + intrinsic_ClearThreadLocalArenasPar); 1.2212 + 1.2213 +#endif // JS_THREADSAFE && JS_ION