michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #if defined(XP_WIN) michael@0: # include // for isatty() michael@0: #else michael@0: # include // for isatty() michael@0: #endif michael@0: michael@0: #include "vm/ForkJoin.h" michael@0: michael@0: #include "mozilla/ThreadLocal.h" michael@0: michael@0: #include "jscntxt.h" michael@0: #include "jslock.h" michael@0: #include "jsprf.h" michael@0: michael@0: #include "builtin/TypedObject.h" michael@0: michael@0: #ifdef JS_THREADSAFE michael@0: # include "jit/BaselineJIT.h" michael@0: # include "vm/Monitor.h" michael@0: #endif michael@0: michael@0: #if defined(JS_THREADSAFE) && defined(JS_ION) michael@0: # include "jit/JitCommon.h" michael@0: # ifdef DEBUG michael@0: # include "jit/Ion.h" michael@0: # include "jit/JitCompartment.h" michael@0: # include "jit/MIR.h" michael@0: # include "jit/MIRGraph.h" michael@0: # endif michael@0: #endif // THREADSAFE && ION michael@0: michael@0: #include "vm/Interpreter-inl.h" michael@0: michael@0: using namespace js; michael@0: using namespace js::parallel; michael@0: using namespace js::jit; michael@0: michael@0: using mozilla::ThreadLocal; michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // Degenerate configurations michael@0: // michael@0: // When JS_THREADSAFE or JS_ION is not defined, we simply run the michael@0: // |func| callback sequentially. We also forego the feedback michael@0: // altogether. michael@0: michael@0: static bool michael@0: ExecuteSequentially(JSContext *cx_, HandleValue funVal, uint16_t *sliceStart, michael@0: uint16_t sliceEnd); michael@0: michael@0: #if !defined(JS_THREADSAFE) || !defined(JS_ION) michael@0: bool michael@0: js::ForkJoin(JSContext *cx, CallArgs &args) michael@0: { michael@0: RootedValue argZero(cx, args[0]); michael@0: uint16_t sliceStart = uint16_t(args[1].toInt32()); michael@0: uint16_t sliceEnd = uint16_t(args[2].toInt32()); michael@0: if (!ExecuteSequentially(cx, argZero, &sliceStart, sliceEnd)) michael@0: return false; michael@0: MOZ_ASSERT(sliceStart == sliceEnd); michael@0: return true; michael@0: } michael@0: michael@0: JSContext * michael@0: ForkJoinContext::acquireJSContext() michael@0: { michael@0: return nullptr; michael@0: } michael@0: michael@0: void michael@0: ForkJoinContext::releaseJSContext() michael@0: { michael@0: } michael@0: michael@0: bool michael@0: ForkJoinContext::isMainThread() const michael@0: { michael@0: return true; michael@0: } michael@0: michael@0: JSRuntime * michael@0: ForkJoinContext::runtime() michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("Not THREADSAFE build"); michael@0: } michael@0: michael@0: bool michael@0: ForkJoinContext::check() michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("Not THREADSAFE build"); michael@0: } michael@0: michael@0: void michael@0: ForkJoinContext::requestGC(JS::gcreason::Reason reason) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("Not THREADSAFE build"); michael@0: } michael@0: michael@0: void michael@0: ForkJoinContext::requestZoneGC(JS::Zone *zone, JS::gcreason::Reason reason) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("Not THREADSAFE build"); michael@0: } michael@0: michael@0: bool michael@0: ForkJoinContext::setPendingAbortFatal(ParallelBailoutCause cause) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("Not THREADSAFE build"); michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: ParallelBailoutRecord::setCause(ParallelBailoutCause cause, michael@0: JSScript *outermostScript, michael@0: JSScript *currentScript, michael@0: jsbytecode *currentPc) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("Not THREADSAFE build"); michael@0: } michael@0: michael@0: void michael@0: js::ParallelBailoutRecord::updateCause(ParallelBailoutCause cause, michael@0: JSScript *outermostScript, michael@0: JSScript *currentScript, michael@0: jsbytecode *currentPc) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("Not THREADSAFE build"); michael@0: } michael@0: michael@0: void michael@0: ParallelBailoutRecord::addTrace(JSScript *script, michael@0: jsbytecode *pc) michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("Not THREADSAFE build"); michael@0: } michael@0: michael@0: bool michael@0: js::InExclusiveParallelSection() michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: js::ParallelTestsShouldPass(JSContext *cx) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: js::intrinsic_SetForkJoinTargetRegion(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: intrinsic_SetForkJoinTargetRegionPar(ForkJoinContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return true; michael@0: } michael@0: michael@0: JS_JITINFO_NATIVE_PARALLEL(js::intrinsic_SetForkJoinTargetRegionInfo, michael@0: intrinsic_SetForkJoinTargetRegionPar); michael@0: michael@0: bool michael@0: js::intrinsic_ClearThreadLocalArenas(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: intrinsic_ClearThreadLocalArenasPar(ForkJoinContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return true; michael@0: } michael@0: michael@0: JS_JITINFO_NATIVE_PARALLEL(js::intrinsic_ClearThreadLocalArenasInfo, michael@0: intrinsic_ClearThreadLocalArenasPar); michael@0: michael@0: #endif // !JS_THREADSAFE || !JS_ION michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // All configurations michael@0: // michael@0: // Some code that is shared between degenerate and parallel configurations. michael@0: michael@0: static bool michael@0: ExecuteSequentially(JSContext *cx, HandleValue funVal, uint16_t *sliceStart, michael@0: uint16_t sliceEnd) michael@0: { michael@0: FastInvokeGuard fig(cx, funVal); michael@0: InvokeArgs &args = fig.args(); michael@0: if (!args.init(3)) michael@0: return false; michael@0: args.setCallee(funVal); michael@0: args.setThis(UndefinedValue()); michael@0: args[0].setInt32(0); michael@0: args[1].setInt32(*sliceStart); michael@0: args[2].setInt32(sliceEnd); michael@0: if (!fig.invoke(cx)) michael@0: return false; michael@0: *sliceStart = (uint16_t)(args.rval().toInt32()); michael@0: return true; michael@0: } michael@0: michael@0: ThreadLocal ForkJoinContext::tlsForkJoinContext; michael@0: michael@0: /* static */ bool michael@0: ForkJoinContext::initialize() michael@0: { michael@0: if (!tlsForkJoinContext.initialized()) { michael@0: if (!tlsForkJoinContext.init()) michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // Parallel configurations michael@0: // michael@0: // The remainder of this file is specific to cases where both michael@0: // JS_THREADSAFE and JS_ION are enabled. michael@0: michael@0: #if defined(JS_THREADSAFE) && defined(JS_ION) michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // Class Declarations and Function Prototypes michael@0: michael@0: namespace js { michael@0: michael@0: // When writing tests, it is often useful to specify different modes michael@0: // of operation. michael@0: enum ForkJoinMode { michael@0: // WARNING: If you change this enum, you MUST update michael@0: // ForkJoinMode() in Utilities.js michael@0: michael@0: // The "normal" behavior: attempt parallel, fallback to michael@0: // sequential. If compilation is ongoing in a helper thread, then michael@0: // run sequential warmup iterations in the meantime. If those michael@0: // iterations wind up completing all the work, just abort. michael@0: ForkJoinModeNormal, michael@0: michael@0: // Like normal, except that we will keep running warmup iterations michael@0: // until compilations are complete, even if there is no more work michael@0: // to do. This is useful in tests as a "setup" run. michael@0: ForkJoinModeCompile, michael@0: michael@0: // Requires that compilation has already completed. Expects parallel michael@0: // execution to proceed without a hitch. (Reports an error otherwise) michael@0: ForkJoinModeParallel, michael@0: michael@0: // Requires that compilation has already completed. Expects michael@0: // parallel execution to bailout once but continue after that without michael@0: // further bailouts. (Reports an error otherwise) michael@0: ForkJoinModeRecover, michael@0: michael@0: // Expects all parallel executions to yield a bailout. If this is not michael@0: // the case, reports an error. michael@0: ForkJoinModeBailout, michael@0: michael@0: NumForkJoinModes michael@0: }; michael@0: michael@0: class ForkJoinOperation michael@0: { michael@0: public: michael@0: // For tests, make sure to keep this in sync with minItemsTestingThreshold. michael@0: static const uint32_t MAX_BAILOUTS = 3; michael@0: uint32_t bailouts; michael@0: michael@0: // Information about the bailout: michael@0: ParallelBailoutCause bailoutCause; michael@0: RootedScript bailoutScript; michael@0: jsbytecode *bailoutBytecode; michael@0: michael@0: ForkJoinOperation(JSContext *cx, HandleFunction fun, uint16_t sliceStart, michael@0: uint16_t sliceEnd, ForkJoinMode mode); michael@0: ExecutionStatus apply(); michael@0: michael@0: private: michael@0: // Most of the functions involved in managing the parallel michael@0: // compilation follow a similar control-flow. They return RedLight michael@0: // if they have either encountered a fatal error or completed the michael@0: // execution, such that no further work is needed. In that event, michael@0: // they take an `ExecutionStatus*` which they use to report michael@0: // whether execution was successful or not. If the function michael@0: // returns `GreenLight`, then the parallel operation is not yet michael@0: // fully completed, so the state machine should carry on. michael@0: enum TrafficLight { michael@0: RedLight, michael@0: GreenLight michael@0: }; michael@0: michael@0: struct WorklistData { michael@0: // True if we enqueued the callees from the ion-compiled michael@0: // version of this entry michael@0: bool calleesEnqueued; michael@0: michael@0: // Last record useCount; updated after warmup michael@0: // iterations; michael@0: uint32_t useCount; michael@0: michael@0: // Number of continuous "stalls" --- meaning warmups michael@0: // where useCount did not increase. michael@0: uint32_t stallCount; michael@0: michael@0: void reset() { michael@0: calleesEnqueued = false; michael@0: useCount = 0; michael@0: stallCount = 0; michael@0: } michael@0: }; michael@0: michael@0: JSContext *cx_; michael@0: HandleFunction fun_; michael@0: uint16_t sliceStart_; michael@0: uint16_t sliceEnd_; michael@0: Vector bailoutRecords_; michael@0: AutoScriptVector worklist_; michael@0: Vector worklistData_; michael@0: ForkJoinMode mode_; michael@0: michael@0: TrafficLight enqueueInitialScript(ExecutionStatus *status); michael@0: TrafficLight compileForParallelExecution(ExecutionStatus *status); michael@0: TrafficLight warmupExecution(bool stopIfComplete, ExecutionStatus *status); michael@0: TrafficLight parallelExecution(ExecutionStatus *status); michael@0: TrafficLight sequentialExecution(bool disqualified, ExecutionStatus *status); michael@0: TrafficLight recoverFromBailout(ExecutionStatus *status); michael@0: TrafficLight fatalError(ExecutionStatus *status); michael@0: bool isInitialScript(HandleScript script); michael@0: void determineBailoutCause(); michael@0: bool invalidateBailedOutScripts(); michael@0: ExecutionStatus sequentialExecution(bool disqualified); michael@0: michael@0: TrafficLight appendCallTargetsToWorklist(uint32_t index, ExecutionStatus *status); michael@0: TrafficLight appendCallTargetToWorklist(HandleScript script, ExecutionStatus *status); michael@0: bool addToWorklist(HandleScript script); michael@0: inline bool hasScript(Vector &scripts, JSScript *script); michael@0: }; // class ForkJoinOperation michael@0: michael@0: class ForkJoinShared : public ParallelJob, public Monitor michael@0: { michael@0: ///////////////////////////////////////////////////////////////////////// michael@0: // Constant fields michael@0: michael@0: JSContext *const cx_; // Current context michael@0: ThreadPool *const threadPool_; // The thread pool michael@0: HandleFunction fun_; // The JavaScript function to execute michael@0: uint16_t sliceStart_; // The starting slice id. michael@0: uint16_t sliceEnd_; // The ending slice id + 1. michael@0: PRLock *cxLock_; // Locks cx_ for parallel VM calls michael@0: ParallelBailoutRecord *const records_; // Bailout records for each worker michael@0: michael@0: ///////////////////////////////////////////////////////////////////////// michael@0: // Per-thread arenas michael@0: // michael@0: // Each worker thread gets an arena to use when allocating. michael@0: michael@0: Vector allocators_; michael@0: michael@0: ///////////////////////////////////////////////////////////////////////// michael@0: // Locked Fields michael@0: // michael@0: // Only to be accessed while holding the lock. michael@0: michael@0: bool gcRequested_; // True if a worker requested a GC michael@0: JS::gcreason::Reason gcReason_; // Reason given to request GC michael@0: Zone *gcZone_; // Zone for GC, or nullptr for full michael@0: michael@0: ///////////////////////////////////////////////////////////////////////// michael@0: // Asynchronous Flags michael@0: // michael@0: // These can be accessed without the lock and are thus atomic. michael@0: michael@0: // Set to true when parallel execution should abort. michael@0: mozilla::Atomic abort_; michael@0: michael@0: // Set to true when a worker bails for a fatal reason. michael@0: mozilla::Atomic fatal_; michael@0: michael@0: public: michael@0: ForkJoinShared(JSContext *cx, michael@0: ThreadPool *threadPool, michael@0: HandleFunction fun, michael@0: uint16_t sliceStart, michael@0: uint16_t sliceEnd, michael@0: ParallelBailoutRecord *records); michael@0: ~ForkJoinShared(); michael@0: michael@0: bool init(); michael@0: michael@0: ParallelResult execute(); michael@0: michael@0: // Invoked from parallel worker threads: michael@0: virtual bool executeFromWorker(ThreadPoolWorker *worker, uintptr_t stackLimit) MOZ_OVERRIDE; michael@0: michael@0: // Invoked only from the main thread: michael@0: virtual bool executeFromMainThread(ThreadPoolWorker *worker) MOZ_OVERRIDE; michael@0: michael@0: // Executes the user-supplied function a worker or the main thread. michael@0: void executePortion(PerThreadData *perThread, ThreadPoolWorker *worker); michael@0: michael@0: // Moves all the per-thread arenas into the main compartment and processes michael@0: // any pending requests for a GC. This can only safely be invoked on the michael@0: // main thread after the workers have completed. michael@0: void transferArenasToCompartmentAndProcessGCRequests(); michael@0: michael@0: michael@0: // Requests a GC, either full or specific to a zone. michael@0: void requestGC(JS::gcreason::Reason reason); michael@0: void requestZoneGC(JS::Zone *zone, JS::gcreason::Reason reason); michael@0: michael@0: // Requests that computation abort. michael@0: void setAbortFlagDueToInterrupt(ForkJoinContext &cx); michael@0: void setAbortFlagAndRequestInterrupt(bool fatal); michael@0: michael@0: // Set the fatal flag for the next abort. michael@0: void setPendingAbortFatal() { fatal_ = true; } michael@0: michael@0: JSRuntime *runtime() { return cx_->runtime(); } michael@0: JS::Zone *zone() { return cx_->zone(); } michael@0: JSCompartment *compartment() { return cx_->compartment(); } michael@0: michael@0: JSContext *acquireJSContext() { PR_Lock(cxLock_); return cx_; } michael@0: void releaseJSContext() { PR_Unlock(cxLock_); } michael@0: }; michael@0: michael@0: class AutoEnterWarmup michael@0: { michael@0: JSRuntime *runtime_; michael@0: michael@0: public: michael@0: AutoEnterWarmup(JSRuntime *runtime) : runtime_(runtime) { runtime_->forkJoinWarmup++; } michael@0: ~AutoEnterWarmup() { runtime_->forkJoinWarmup--; } michael@0: }; michael@0: michael@0: class AutoSetForkJoinContext michael@0: { michael@0: public: michael@0: AutoSetForkJoinContext(ForkJoinContext *threadCx) { michael@0: ForkJoinContext::tlsForkJoinContext.set(threadCx); michael@0: } michael@0: michael@0: ~AutoSetForkJoinContext() { michael@0: ForkJoinContext::tlsForkJoinContext.set(nullptr); michael@0: } michael@0: }; michael@0: michael@0: } // namespace js michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // ForkJoinActivation michael@0: // michael@0: // Takes care of tidying up GC before we enter a fork join section. Also michael@0: // pauses the barrier verifier, as we cannot enter fork join with the runtime michael@0: // or the zone needing barriers. michael@0: michael@0: ForkJoinActivation::ForkJoinActivation(JSContext *cx) michael@0: : Activation(cx, ForkJoin), michael@0: prevIonTop_(cx->mainThread().ionTop), michael@0: av_(cx->runtime(), false) michael@0: { michael@0: // Note: we do not allow GC during parallel sections. michael@0: // Moreover, we do not wish to worry about making michael@0: // write barriers thread-safe. Therefore, we guarantee michael@0: // that there is no incremental GC in progress and force michael@0: // a minor GC to ensure no cross-generation pointers get michael@0: // created: michael@0: michael@0: if (JS::IsIncrementalGCInProgress(cx->runtime())) { michael@0: JS::PrepareForIncrementalGC(cx->runtime()); michael@0: JS::FinishIncrementalGC(cx->runtime(), JS::gcreason::API); michael@0: } michael@0: michael@0: MinorGC(cx->runtime(), JS::gcreason::API); michael@0: michael@0: cx->runtime()->gcHelperThread.waitBackgroundSweepEnd(); michael@0: michael@0: JS_ASSERT(!cx->runtime()->needsBarrier()); michael@0: JS_ASSERT(!cx->zone()->needsBarrier()); michael@0: } michael@0: michael@0: ForkJoinActivation::~ForkJoinActivation() michael@0: { michael@0: cx_->mainThread().ionTop = prevIonTop_; michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // js::ForkJoin() and ForkJoinOperation class michael@0: // michael@0: // These are the top-level objects that manage the parallel execution. michael@0: // They handle parallel compilation (if necessary), triggering michael@0: // parallel execution, and recovering from bailouts. michael@0: michael@0: static const char *ForkJoinModeString(ForkJoinMode mode); michael@0: michael@0: bool michael@0: js::ForkJoin(JSContext *cx, CallArgs &args) michael@0: { michael@0: JS_ASSERT(args.length() == 4); // else the self-hosted code is wrong michael@0: JS_ASSERT(args[0].isObject()); michael@0: JS_ASSERT(args[0].toObject().is()); michael@0: JS_ASSERT(args[1].isInt32()); michael@0: JS_ASSERT(args[2].isInt32()); michael@0: JS_ASSERT(args[3].isInt32()); michael@0: JS_ASSERT(args[3].toInt32() < NumForkJoinModes); michael@0: michael@0: RootedFunction fun(cx, &args[0].toObject().as()); michael@0: uint16_t sliceStart = (uint16_t)(args[1].toInt32()); michael@0: uint16_t sliceEnd = (uint16_t)(args[2].toInt32()); michael@0: ForkJoinMode mode = (ForkJoinMode)(args[3].toInt32()); michael@0: michael@0: MOZ_ASSERT(sliceStart == args[1].toInt32()); michael@0: MOZ_ASSERT(sliceEnd == args[2].toInt32()); michael@0: MOZ_ASSERT(sliceStart <= sliceEnd); michael@0: michael@0: ForkJoinOperation op(cx, fun, sliceStart, sliceEnd, mode); michael@0: ExecutionStatus status = op.apply(); michael@0: if (status == ExecutionFatal) michael@0: return false; michael@0: michael@0: switch (mode) { michael@0: case ForkJoinModeNormal: michael@0: case ForkJoinModeCompile: michael@0: return true; michael@0: michael@0: case ForkJoinModeParallel: michael@0: if (status == ExecutionParallel && op.bailouts == 0) michael@0: return true; michael@0: break; michael@0: michael@0: case ForkJoinModeRecover: michael@0: if (status != ExecutionSequential && op.bailouts > 0) michael@0: return true; michael@0: break; michael@0: michael@0: case ForkJoinModeBailout: michael@0: if (status != ExecutionParallel) michael@0: return true; michael@0: break; michael@0: michael@0: case NumForkJoinModes: michael@0: break; michael@0: } michael@0: michael@0: const char *statusString = "?"; michael@0: switch (status) { michael@0: case ExecutionSequential: statusString = "seq"; break; michael@0: case ExecutionParallel: statusString = "par"; break; michael@0: case ExecutionWarmup: statusString = "warmup"; break; michael@0: case ExecutionFatal: statusString = "fatal"; break; michael@0: } michael@0: michael@0: if (ParallelTestsShouldPass(cx)) { michael@0: JS_ReportError(cx, "ForkJoin: mode=%s status=%s bailouts=%d", michael@0: ForkJoinModeString(mode), statusString, op.bailouts); michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: static const char * michael@0: ForkJoinModeString(ForkJoinMode mode) { michael@0: switch (mode) { michael@0: case ForkJoinModeNormal: return "normal"; michael@0: case ForkJoinModeCompile: return "compile"; michael@0: case ForkJoinModeParallel: return "parallel"; michael@0: case ForkJoinModeRecover: return "recover"; michael@0: case ForkJoinModeBailout: return "bailout"; michael@0: case NumForkJoinModes: return "max"; michael@0: } michael@0: return "???"; michael@0: } michael@0: michael@0: ForkJoinOperation::ForkJoinOperation(JSContext *cx, HandleFunction fun, uint16_t sliceStart, michael@0: uint16_t sliceEnd, ForkJoinMode mode) michael@0: : bailouts(0), michael@0: bailoutCause(ParallelBailoutNone), michael@0: bailoutScript(cx), michael@0: bailoutBytecode(nullptr), michael@0: cx_(cx), michael@0: fun_(fun), michael@0: sliceStart_(sliceStart), michael@0: sliceEnd_(sliceEnd), michael@0: bailoutRecords_(cx), michael@0: worklist_(cx), michael@0: worklistData_(cx), michael@0: mode_(mode) michael@0: { } michael@0: michael@0: ExecutionStatus michael@0: ForkJoinOperation::apply() michael@0: { michael@0: ExecutionStatus status; michael@0: michael@0: // High level outline of the procedure: michael@0: // michael@0: // - As we enter, we check for parallel script without "uncompiled" flag. michael@0: // - If present, skip initial enqueue. michael@0: // - While not too many bailouts: michael@0: // - While all scripts in worklist are not compiled: michael@0: // - For each script S in worklist: michael@0: // - Compile S if not compiled michael@0: // -> Error: fallback michael@0: // - If compiled, add call targets to worklist w/o checking uncompiled michael@0: // flag michael@0: // - If some compilations pending, run warmup iteration michael@0: // - Otherwise, clear "uncompiled targets" flag on main script and michael@0: // break from loop michael@0: // - Attempt parallel execution michael@0: // - If successful: return happily michael@0: // - If error: abort sadly michael@0: // - If bailout: michael@0: // - Invalidate any scripts that may need to be invalidated michael@0: // - Re-enqueue main script and any uncompiled scripts that were called michael@0: // - Too many bailouts: Fallback to sequential michael@0: michael@0: JS_ASSERT_IF(!jit::IsBaselineEnabled(cx_), !jit::IsIonEnabled(cx_)); michael@0: if (!jit::IsBaselineEnabled(cx_) || !jit::IsIonEnabled(cx_)) michael@0: return sequentialExecution(true); michael@0: michael@0: SpewBeginOp(cx_, "ForkJoinOperation"); michael@0: michael@0: // How many workers do we have, counting the main thread. michael@0: unsigned numWorkers = cx_->runtime()->threadPool.numWorkers(); michael@0: michael@0: if (!bailoutRecords_.resize(numWorkers)) michael@0: return SpewEndOp(ExecutionFatal); michael@0: michael@0: for (uint32_t i = 0; i < numWorkers; i++) michael@0: bailoutRecords_[i].init(cx_); michael@0: michael@0: if (enqueueInitialScript(&status) == RedLight) michael@0: return SpewEndOp(status); michael@0: michael@0: Spew(SpewOps, "Execution mode: %s", ForkJoinModeString(mode_)); michael@0: switch (mode_) { michael@0: case ForkJoinModeNormal: michael@0: case ForkJoinModeCompile: michael@0: case ForkJoinModeBailout: michael@0: break; michael@0: michael@0: case ForkJoinModeParallel: michael@0: case ForkJoinModeRecover: michael@0: // These two modes are used to check that every iteration can michael@0: // be executed in parallel. They expect compilation to have michael@0: // been done. But, when using gc zeal, it's possible that michael@0: // compiled scripts were collected. michael@0: if (ParallelTestsShouldPass(cx_) && worklist_.length() != 0) { michael@0: JS_ReportError(cx_, "ForkJoin: compilation required in par or bailout mode"); michael@0: return SpewEndOp(ExecutionFatal); michael@0: } michael@0: break; michael@0: michael@0: case NumForkJoinModes: michael@0: MOZ_ASSUME_UNREACHABLE("Invalid mode"); michael@0: } michael@0: michael@0: while (bailouts < MAX_BAILOUTS) { michael@0: for (uint32_t i = 0; i < numWorkers; i++) michael@0: bailoutRecords_[i].reset(cx_); michael@0: michael@0: if (compileForParallelExecution(&status) == RedLight) michael@0: return SpewEndOp(status); michael@0: michael@0: JS_ASSERT(worklist_.length() == 0); michael@0: if (parallelExecution(&status) == RedLight) michael@0: return SpewEndOp(status); michael@0: michael@0: if (recoverFromBailout(&status) == RedLight) michael@0: return SpewEndOp(status); michael@0: } michael@0: michael@0: // After enough tries, just execute sequentially. michael@0: return SpewEndOp(sequentialExecution(true)); michael@0: } michael@0: michael@0: ForkJoinOperation::TrafficLight michael@0: ForkJoinOperation::enqueueInitialScript(ExecutionStatus *status) michael@0: { michael@0: // GreenLight: script successfully enqueued if necessary michael@0: // RedLight: fatal error or fell back to sequential michael@0: michael@0: // The kernel should be a self-hosted function. michael@0: if (!fun_->is()) michael@0: return sequentialExecution(true, status); michael@0: michael@0: RootedFunction callee(cx_, &fun_->as()); michael@0: michael@0: if (!callee->isInterpreted() || !callee->isSelfHostedBuiltin()) michael@0: return sequentialExecution(true, status); michael@0: michael@0: // If the main script is already compiled, and we have no reason michael@0: // to suspect any of its callees are not compiled, then we can michael@0: // just skip the compilation step. michael@0: RootedScript script(cx_, callee->getOrCreateScript(cx_)); michael@0: if (!script) michael@0: return RedLight; michael@0: michael@0: if (script->hasParallelIonScript()) { michael@0: // Notify that there's been activity on the entry script. michael@0: JitCompartment *jitComp = cx_->compartment()->jitCompartment(); michael@0: if (!jitComp->notifyOfActiveParallelEntryScript(cx_, script)) { michael@0: *status = ExecutionFatal; michael@0: return RedLight; michael@0: } michael@0: michael@0: if (!script->parallelIonScript()->hasUncompiledCallTarget()) { michael@0: Spew(SpewOps, "Script %p:%s:%d already compiled, no uncompiled callees", michael@0: script.get(), script->filename(), script->lineno()); michael@0: return GreenLight; michael@0: } michael@0: michael@0: Spew(SpewOps, "Script %p:%s:%d already compiled, may have uncompiled callees", michael@0: script.get(), script->filename(), script->lineno()); michael@0: } michael@0: michael@0: // Otherwise, add to the worklist of scripts to process. michael@0: if (addToWorklist(script) == RedLight) michael@0: return fatalError(status); michael@0: return GreenLight; michael@0: } michael@0: michael@0: ForkJoinOperation::TrafficLight michael@0: ForkJoinOperation::compileForParallelExecution(ExecutionStatus *status) michael@0: { michael@0: // GreenLight: all scripts compiled michael@0: // RedLight: fatal error or completed work via warmups or fallback michael@0: michael@0: // This routine attempts to do whatever compilation is necessary michael@0: // to execute a single parallel attempt. When it returns, either michael@0: // (1) we have fallen back to sequential; (2) we have run enough michael@0: // warmup runs to complete all the work; or (3) we have compiled michael@0: // all scripts we think likely to be executed during a parallel michael@0: // execution. michael@0: michael@0: RootedFunction fun(cx_); michael@0: RootedScript script(cx_); michael@0: michael@0: // After 3 stalls, we stop waiting for a script to gather type michael@0: // info and move on with execution. michael@0: const uint32_t stallThreshold = 3; michael@0: michael@0: // This loop continues to iterate until the full contents of michael@0: // `worklist` have been successfully compiled for parallel michael@0: // execution. The compilations themselves typically occur on michael@0: // helper threads. While we wait for the compilations to complete, michael@0: // or for sufficient type information to be gathered, we execute michael@0: // warmup iterations. michael@0: while (true) { michael@0: bool offMainThreadCompilationsInProgress = false; michael@0: bool gatheringTypeInformation = false; michael@0: michael@0: // Walk over the worklist to check on the status of each entry. michael@0: for (uint32_t i = 0; i < worklist_.length(); i++) { michael@0: script = worklist_[i]; michael@0: script->ensureNonLazyCanonicalFunction(cx_); michael@0: fun = script->functionNonDelazifying(); michael@0: michael@0: // No baseline script means no type information, hence we michael@0: // will not be able to compile very well. In such cases, michael@0: // we continue to run baseline iterations until either (1) michael@0: // the potential callee *has* a baseline script or (2) the michael@0: // potential callee's use count stops increasing, michael@0: // indicating that they are not in fact a callee. michael@0: if (!script->hasBaselineScript()) { michael@0: uint32_t previousUseCount = worklistData_[i].useCount; michael@0: uint32_t currentUseCount = script->getUseCount(); michael@0: if (previousUseCount < currentUseCount) { michael@0: worklistData_[i].useCount = currentUseCount; michael@0: worklistData_[i].stallCount = 0; michael@0: gatheringTypeInformation = true; michael@0: michael@0: Spew(SpewCompile, michael@0: "Script %p:%s:%d has no baseline script, " michael@0: "but use count grew from %d to %d", michael@0: script.get(), script->filename(), script->lineno(), michael@0: previousUseCount, currentUseCount); michael@0: } else { michael@0: uint32_t stallCount = ++worklistData_[i].stallCount; michael@0: if (stallCount < stallThreshold) { michael@0: gatheringTypeInformation = true; michael@0: } michael@0: michael@0: Spew(SpewCompile, michael@0: "Script %p:%s:%d has no baseline script, " michael@0: "and use count has %u stalls at %d", michael@0: script.get(), script->filename(), script->lineno(), michael@0: stallCount, previousUseCount); michael@0: } michael@0: continue; michael@0: } michael@0: michael@0: if (!script->hasParallelIonScript()) { michael@0: // Script has not yet been compiled. Attempt to compile it. michael@0: SpewBeginCompile(script); michael@0: MethodStatus mstatus = jit::CanEnterInParallel(cx_, script); michael@0: SpewEndCompile(mstatus); michael@0: michael@0: switch (mstatus) { michael@0: case Method_Error: michael@0: return fatalError(status); michael@0: michael@0: case Method_CantCompile: michael@0: Spew(SpewCompile, michael@0: "Script %p:%s:%d cannot be compiled, " michael@0: "falling back to sequential execution", michael@0: script.get(), script->filename(), script->lineno()); michael@0: return sequentialExecution(true, status); michael@0: michael@0: case Method_Skipped: michael@0: // A "skipped" result either means that we are compiling michael@0: // in parallel OR some other transient error occurred. michael@0: if (script->isParallelIonCompilingOffThread()) { michael@0: Spew(SpewCompile, michael@0: "Script %p:%s:%d compiling off-thread", michael@0: script.get(), script->filename(), script->lineno()); michael@0: offMainThreadCompilationsInProgress = true; michael@0: continue; michael@0: } michael@0: return sequentialExecution(false, status); michael@0: michael@0: case Method_Compiled: michael@0: Spew(SpewCompile, michael@0: "Script %p:%s:%d compiled", michael@0: script.get(), script->filename(), script->lineno()); michael@0: JS_ASSERT(script->hasParallelIonScript()); michael@0: michael@0: if (isInitialScript(script)) { michael@0: JitCompartment *jitComp = cx_->compartment()->jitCompartment(); michael@0: if (!jitComp->notifyOfActiveParallelEntryScript(cx_, script)) { michael@0: *status = ExecutionFatal; michael@0: return RedLight; michael@0: } michael@0: } michael@0: michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // At this point, either the script was already compiled michael@0: // or we just compiled it. Check whether its "uncompiled michael@0: // call target" flag is set and add the targets to our michael@0: // worklist if so. Clear the flag after that, since we michael@0: // will be compiling the call targets. michael@0: JS_ASSERT(script->hasParallelIonScript()); michael@0: if (appendCallTargetsToWorklist(i, status) == RedLight) michael@0: return RedLight; michael@0: } michael@0: michael@0: // If there is compilation occurring in a helper thread, then michael@0: // run a warmup iterations in the main thread while we wait. michael@0: // There is a chance that this warmup will finish all the work michael@0: // we have to do, so we should stop then, unless we are in michael@0: // compile mode, in which case we'll continue to block. michael@0: // michael@0: // Note that even in compile mode, we can't block *forever*: michael@0: // - OMTC compiles will finish; michael@0: // - no work is being done, so use counts on not-yet-baselined michael@0: // scripts will not increase. michael@0: if (offMainThreadCompilationsInProgress || gatheringTypeInformation) { michael@0: bool stopIfComplete = (mode_ != ForkJoinModeCompile); michael@0: if (warmupExecution(stopIfComplete, status) == RedLight) michael@0: return RedLight; michael@0: continue; michael@0: } michael@0: michael@0: // All compilations are complete. However, be careful: it is michael@0: // possible that a garbage collection occurred while we were michael@0: // iterating and caused some of the scripts we thought we had michael@0: // compiled to be collected. In that case, we will just have michael@0: // to begin again. michael@0: bool allScriptsPresent = true; michael@0: for (uint32_t i = 0; i < worklist_.length(); i++) { michael@0: if (!worklist_[i]->hasParallelIonScript()) { michael@0: if (worklistData_[i].stallCount < stallThreshold) { michael@0: worklistData_[i].reset(); michael@0: allScriptsPresent = false; michael@0: michael@0: Spew(SpewCompile, michael@0: "Script %p:%s:%d is not stalled, " michael@0: "but no parallel ion script found, " michael@0: "restarting loop", michael@0: script.get(), script->filename(), script->lineno()); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (allScriptsPresent) michael@0: break; michael@0: } michael@0: michael@0: Spew(SpewCompile, "Compilation complete (final worklist length %d)", michael@0: worklist_.length()); michael@0: michael@0: // At this point, all scripts and their transitive callees are michael@0: // either stalled (indicating they are unlikely to be called) or michael@0: // in a compiled state. Therefore we can clear the michael@0: // "hasUncompiledCallTarget" flag on them and then clear the michael@0: // worklist. michael@0: for (uint32_t i = 0; i < worklist_.length(); i++) { michael@0: if (worklist_[i]->hasParallelIonScript()) { michael@0: JS_ASSERT(worklistData_[i].calleesEnqueued); michael@0: worklist_[i]->parallelIonScript()->clearHasUncompiledCallTarget(); michael@0: } else { michael@0: JS_ASSERT(worklistData_[i].stallCount >= stallThreshold); michael@0: } michael@0: } michael@0: worklist_.clear(); michael@0: worklistData_.clear(); michael@0: return GreenLight; michael@0: } michael@0: michael@0: ForkJoinOperation::TrafficLight michael@0: ForkJoinOperation::appendCallTargetsToWorklist(uint32_t index, ExecutionStatus *status) michael@0: { michael@0: // GreenLight: call targets appended michael@0: // RedLight: fatal error or completed work via warmups or fallback michael@0: michael@0: JS_ASSERT(worklist_[index]->hasParallelIonScript()); michael@0: michael@0: // Check whether we have already enqueued the targets for michael@0: // this entry and avoid doing it again if so. michael@0: if (worklistData_[index].calleesEnqueued) michael@0: return GreenLight; michael@0: worklistData_[index].calleesEnqueued = true; michael@0: michael@0: // Iterate through the callees and enqueue them. michael@0: RootedScript target(cx_); michael@0: IonScript *ion = worklist_[index]->parallelIonScript(); michael@0: for (uint32_t i = 0; i < ion->callTargetEntries(); i++) { michael@0: target = ion->callTargetList()[i]; michael@0: parallel::Spew(parallel::SpewCompile, michael@0: "Adding call target %s:%u", michael@0: target->filename(), target->lineno()); michael@0: if (appendCallTargetToWorklist(target, status) == RedLight) michael@0: return RedLight; michael@0: } michael@0: michael@0: return GreenLight; michael@0: } michael@0: michael@0: ForkJoinOperation::TrafficLight michael@0: ForkJoinOperation::appendCallTargetToWorklist(HandleScript script, ExecutionStatus *status) michael@0: { michael@0: // GreenLight: call target appended if necessary michael@0: // RedLight: fatal error or completed work via warmups or fallback michael@0: michael@0: JS_ASSERT(script); michael@0: michael@0: // Fallback to sequential if disabled. michael@0: if (!script->canParallelIonCompile()) { michael@0: Spew(SpewCompile, "Skipping %p:%s:%u, canParallelIonCompile() is false", michael@0: script.get(), script->filename(), script->lineno()); michael@0: return sequentialExecution(true, status); michael@0: } michael@0: michael@0: if (script->hasParallelIonScript()) { michael@0: // Skip if the code is expected to result in a bailout. michael@0: if (script->parallelIonScript()->bailoutExpected()) { michael@0: Spew(SpewCompile, "Skipping %p:%s:%u, bailout expected", michael@0: script.get(), script->filename(), script->lineno()); michael@0: return sequentialExecution(false, status); michael@0: } michael@0: } michael@0: michael@0: if (!addToWorklist(script)) michael@0: return fatalError(status); michael@0: michael@0: return GreenLight; michael@0: } michael@0: michael@0: bool michael@0: ForkJoinOperation::addToWorklist(HandleScript script) michael@0: { michael@0: for (uint32_t i = 0; i < worklist_.length(); i++) { michael@0: if (worklist_[i] == script) { michael@0: Spew(SpewCompile, "Skipping %p:%s:%u, already in worklist", michael@0: script.get(), script->filename(), script->lineno()); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: Spew(SpewCompile, "Enqueued %p:%s:%u", michael@0: script.get(), script->filename(), script->lineno()); michael@0: michael@0: // Note that we add all possibly compilable functions to the worklist, michael@0: // even if they're already compiled. This is so that we can return michael@0: // Method_Compiled and not Method_Skipped if we have a worklist full of michael@0: // already-compiled functions. michael@0: if (!worklist_.append(script)) michael@0: return false; michael@0: michael@0: // we have not yet enqueued the callees of this script michael@0: if (!worklistData_.append(WorklistData())) michael@0: return false; michael@0: worklistData_[worklistData_.length() - 1].reset(); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: ForkJoinOperation::TrafficLight michael@0: ForkJoinOperation::sequentialExecution(bool disqualified, ExecutionStatus *status) michael@0: { michael@0: // RedLight: fatal error or completed work michael@0: michael@0: *status = sequentialExecution(disqualified); michael@0: return RedLight; michael@0: } michael@0: michael@0: ExecutionStatus michael@0: ForkJoinOperation::sequentialExecution(bool disqualified) michael@0: { michael@0: // XXX use disqualified to set parallelIon to ION_DISABLED_SCRIPT? michael@0: michael@0: Spew(SpewOps, "Executing sequential execution (disqualified=%d).", michael@0: disqualified); michael@0: michael@0: if (sliceStart_ == sliceEnd_) michael@0: return ExecutionSequential; michael@0: michael@0: RootedValue funVal(cx_, ObjectValue(*fun_)); michael@0: if (!ExecuteSequentially(cx_, funVal, &sliceStart_, sliceEnd_)) michael@0: return ExecutionFatal; michael@0: MOZ_ASSERT(sliceStart_ == sliceEnd_); michael@0: return ExecutionSequential; michael@0: } michael@0: michael@0: ForkJoinOperation::TrafficLight michael@0: ForkJoinOperation::fatalError(ExecutionStatus *status) michael@0: { michael@0: // RedLight: fatal error michael@0: michael@0: *status = ExecutionFatal; michael@0: return RedLight; michael@0: } michael@0: michael@0: static const char * michael@0: BailoutExplanation(ParallelBailoutCause cause) michael@0: { michael@0: switch (cause) { michael@0: case ParallelBailoutNone: michael@0: return "no particular reason"; michael@0: case ParallelBailoutCompilationSkipped: michael@0: return "compilation failed (method skipped)"; michael@0: case ParallelBailoutCompilationFailure: michael@0: return "compilation failed"; michael@0: case ParallelBailoutInterrupt: michael@0: return "interrupted"; michael@0: case ParallelBailoutFailedIC: michael@0: return "failed to attach stub to IC"; michael@0: case ParallelBailoutHeapBusy: michael@0: return "heap busy flag set during interrupt"; michael@0: case ParallelBailoutMainScriptNotPresent: michael@0: return "main script not present"; michael@0: case ParallelBailoutCalledToUncompiledScript: michael@0: return "called to uncompiled script"; michael@0: case ParallelBailoutIllegalWrite: michael@0: return "illegal write"; michael@0: case ParallelBailoutAccessToIntrinsic: michael@0: return "access to intrinsic"; michael@0: case ParallelBailoutOverRecursed: michael@0: return "over recursed"; michael@0: case ParallelBailoutOutOfMemory: michael@0: return "out of memory"; michael@0: case ParallelBailoutUnsupported: michael@0: return "unsupported"; michael@0: case ParallelBailoutUnsupportedVM: michael@0: return "unsupported operation in VM call"; michael@0: case ParallelBailoutUnsupportedStringComparison: michael@0: return "unsupported string comparison"; michael@0: case ParallelBailoutRequestedGC: michael@0: return "requested GC"; michael@0: case ParallelBailoutRequestedZoneGC: michael@0: return "requested zone GC"; michael@0: default: michael@0: return "no known reason"; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: ForkJoinOperation::isInitialScript(HandleScript script) michael@0: { michael@0: return fun_->is() && (fun_->as().nonLazyScript() == script); michael@0: } michael@0: michael@0: void michael@0: ForkJoinOperation::determineBailoutCause() michael@0: { michael@0: bailoutCause = ParallelBailoutNone; michael@0: for (uint32_t i = 0; i < bailoutRecords_.length(); i++) { michael@0: if (bailoutRecords_[i].cause == ParallelBailoutNone) michael@0: continue; michael@0: michael@0: if (bailoutRecords_[i].cause == ParallelBailoutInterrupt) michael@0: continue; michael@0: michael@0: bailoutCause = bailoutRecords_[i].cause; michael@0: const char *causeStr = BailoutExplanation(bailoutCause); michael@0: if (bailoutRecords_[i].depth) { michael@0: bailoutScript = bailoutRecords_[i].trace[0].script; michael@0: bailoutBytecode = bailoutRecords_[i].trace[0].bytecode; michael@0: michael@0: const char *filename = bailoutScript->filename(); michael@0: int line = JS_PCToLineNumber(cx_, bailoutScript, bailoutBytecode); michael@0: JS_ReportWarning(cx_, "Bailed out of parallel operation: %s at %s:%d", michael@0: causeStr, filename, line); michael@0: michael@0: Spew(SpewBailouts, "Bailout from thread %d: cause %d at loc %s:%d", michael@0: i, michael@0: bailoutCause, michael@0: bailoutScript->filename(), michael@0: PCToLineNumber(bailoutScript, bailoutBytecode)); michael@0: } else { michael@0: JS_ReportWarning(cx_, "Bailed out of parallel operation: %s", michael@0: causeStr); michael@0: michael@0: Spew(SpewBailouts, "Bailout from thread %d: cause %d, unknown loc", michael@0: i, michael@0: bailoutCause); michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool michael@0: ForkJoinOperation::invalidateBailedOutScripts() michael@0: { michael@0: Vector invalid(cx_); michael@0: for (uint32_t i = 0; i < bailoutRecords_.length(); i++) { michael@0: RootedScript script(cx_, bailoutRecords_[i].topScript); michael@0: michael@0: // No script to invalidate. michael@0: if (!script || !script->hasParallelIonScript()) michael@0: continue; michael@0: michael@0: Spew(SpewBailouts, michael@0: "Bailout from thread %d: cause %d, topScript %p:%s:%d", michael@0: i, michael@0: bailoutRecords_[i].cause, michael@0: script.get(), script->filename(), script->lineno()); michael@0: michael@0: switch (bailoutRecords_[i].cause) { michael@0: // An interrupt is not the fault of the script, so don't michael@0: // invalidate it. michael@0: case ParallelBailoutInterrupt: continue; michael@0: michael@0: // An illegal write will not be made legal by invalidation. michael@0: case ParallelBailoutIllegalWrite: continue; michael@0: michael@0: // For other cases, consider invalidation. michael@0: default: break; michael@0: } michael@0: michael@0: // Already invalidated. michael@0: if (hasScript(invalid, script)) michael@0: continue; michael@0: michael@0: Spew(SpewBailouts, "Invalidating script %p:%s:%d due to cause %d", michael@0: script.get(), script->filename(), script->lineno(), michael@0: bailoutRecords_[i].cause); michael@0: michael@0: types::RecompileInfo co = script->parallelIonScript()->recompileInfo(); michael@0: michael@0: if (!invalid.append(co)) michael@0: return false; michael@0: michael@0: // any script that we have marked for invalidation will need michael@0: // to be recompiled michael@0: if (!addToWorklist(script)) michael@0: return false; michael@0: } michael@0: michael@0: Invalidate(cx_, invalid); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: ForkJoinOperation::TrafficLight michael@0: ForkJoinOperation::warmupExecution(bool stopIfComplete, ExecutionStatus *status) michael@0: { michael@0: // GreenLight: warmup succeeded, still more work to do michael@0: // RedLight: fatal error or warmup completed all work (check status) michael@0: michael@0: if (sliceStart_ == sliceEnd_) { michael@0: Spew(SpewOps, "Warmup execution finished all the work."); michael@0: michael@0: if (stopIfComplete) { michael@0: *status = ExecutionWarmup; michael@0: return RedLight; michael@0: } michael@0: michael@0: // If we finished all slices in warmup, be sure check the michael@0: // interrupt flag. This is because we won't be running more JS michael@0: // code, and thus no more automatic checking of the interrupt michael@0: // flag. michael@0: if (!CheckForInterrupt(cx_)) { michael@0: *status = ExecutionFatal; michael@0: return RedLight; michael@0: } michael@0: michael@0: return GreenLight; michael@0: } michael@0: michael@0: Spew(SpewOps, "Executing warmup from slice %d.", sliceStart_); michael@0: michael@0: AutoEnterWarmup warmup(cx_->runtime()); michael@0: RootedValue funVal(cx_, ObjectValue(*fun_)); michael@0: if (!ExecuteSequentially(cx_, funVal, &sliceStart_, sliceStart_ + 1)) { michael@0: *status = ExecutionFatal; michael@0: return RedLight; michael@0: } michael@0: michael@0: return GreenLight; michael@0: } michael@0: michael@0: ForkJoinOperation::TrafficLight michael@0: ForkJoinOperation::parallelExecution(ExecutionStatus *status) michael@0: { michael@0: // GreenLight: bailout occurred, keep trying michael@0: // RedLight: fatal error or all work completed michael@0: michael@0: // Recursive use of the ThreadPool is not supported. Right now we michael@0: // cannot get here because parallel code cannot invoke native michael@0: // functions such as ForkJoin(). michael@0: JS_ASSERT(ForkJoinContext::current() == nullptr); michael@0: michael@0: if (sliceStart_ == sliceEnd_) { michael@0: Spew(SpewOps, "Warmup execution finished all the work."); michael@0: *status = ExecutionWarmup; michael@0: return RedLight; michael@0: } michael@0: michael@0: ForkJoinActivation activation(cx_); michael@0: ThreadPool *threadPool = &cx_->runtime()->threadPool; michael@0: ForkJoinShared shared(cx_, threadPool, fun_, sliceStart_, sliceEnd_, &bailoutRecords_[0]); michael@0: if (!shared.init()) { michael@0: *status = ExecutionFatal; michael@0: return RedLight; michael@0: } michael@0: michael@0: switch (shared.execute()) { michael@0: case TP_SUCCESS: michael@0: *status = ExecutionParallel; michael@0: return RedLight; michael@0: michael@0: case TP_FATAL: michael@0: *status = ExecutionFatal; michael@0: return RedLight; michael@0: michael@0: case TP_RETRY_SEQUENTIALLY: michael@0: case TP_RETRY_AFTER_GC: michael@0: break; // bailout michael@0: } michael@0: michael@0: return GreenLight; michael@0: } michael@0: michael@0: ForkJoinOperation::TrafficLight michael@0: ForkJoinOperation::recoverFromBailout(ExecutionStatus *status) michael@0: { michael@0: // GreenLight: bailout recovered, try to compile-and-run again michael@0: // RedLight: fatal error michael@0: michael@0: bailouts += 1; michael@0: determineBailoutCause(); michael@0: michael@0: SpewBailout(bailouts, bailoutScript, bailoutBytecode, bailoutCause); michael@0: michael@0: // After any bailout, we always scan over callee list of main michael@0: // function, if nothing else michael@0: RootedScript mainScript(cx_, fun_->nonLazyScript()); michael@0: if (!addToWorklist(mainScript)) michael@0: return fatalError(status); michael@0: michael@0: // Also invalidate and recompile any callees that were implicated michael@0: // by the bailout michael@0: if (!invalidateBailedOutScripts()) michael@0: return fatalError(status); michael@0: michael@0: if (warmupExecution(/*stopIfComplete:*/true, status) == RedLight) michael@0: return RedLight; michael@0: michael@0: return GreenLight; michael@0: } michael@0: michael@0: bool michael@0: ForkJoinOperation::hasScript(Vector &scripts, JSScript *script) michael@0: { michael@0: for (uint32_t i = 0; i < scripts.length(); i++) { michael@0: if (scripts[i] == script->parallelIonScript()->recompileInfo()) michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: // Can only enter callees with a valid IonScript. michael@0: template michael@0: class ParallelIonInvoke michael@0: { michael@0: EnterJitCode enter_; michael@0: void *jitcode_; michael@0: void *calleeToken_; michael@0: Value argv_[maxArgc + 2]; michael@0: uint32_t argc_; michael@0: michael@0: public: michael@0: Value *args; michael@0: michael@0: ParallelIonInvoke(JSRuntime *rt, michael@0: HandleFunction callee, michael@0: uint32_t argc) michael@0: : argc_(argc), michael@0: args(argv_ + 2) michael@0: { michael@0: JS_ASSERT(argc <= maxArgc + 2); michael@0: michael@0: // Set 'callee' and 'this'. michael@0: argv_[0] = ObjectValue(*callee); michael@0: argv_[1] = UndefinedValue(); michael@0: michael@0: // Find JIT code pointer. michael@0: IonScript *ion = callee->nonLazyScript()->parallelIonScript(); michael@0: JitCode *code = ion->method(); michael@0: jitcode_ = code->raw(); michael@0: enter_ = rt->jitRuntime()->enterIon(); michael@0: calleeToken_ = CalleeToToken(callee); michael@0: } michael@0: michael@0: bool invoke(PerThreadData *perThread) { michael@0: RootedValue result(perThread); michael@0: CALL_GENERATED_CODE(enter_, jitcode_, argc_ + 1, argv_ + 1, nullptr, calleeToken_, michael@0: nullptr, 0, result.address()); michael@0: return !result.isMagic(); michael@0: } michael@0: }; michael@0: michael@0: ///////////////////////////////////////////////////////////////////////////// michael@0: // ForkJoinShared michael@0: // michael@0: michael@0: ForkJoinShared::ForkJoinShared(JSContext *cx, michael@0: ThreadPool *threadPool, michael@0: HandleFunction fun, michael@0: uint16_t sliceStart, michael@0: uint16_t sliceEnd, michael@0: ParallelBailoutRecord *records) michael@0: : cx_(cx), michael@0: threadPool_(threadPool), michael@0: fun_(fun), michael@0: sliceStart_(sliceStart), michael@0: sliceEnd_(sliceEnd), michael@0: cxLock_(nullptr), michael@0: records_(records), michael@0: allocators_(cx), michael@0: gcRequested_(false), michael@0: gcReason_(JS::gcreason::NUM_REASONS), michael@0: gcZone_(nullptr), michael@0: abort_(false), michael@0: fatal_(false) michael@0: { michael@0: } michael@0: michael@0: bool michael@0: ForkJoinShared::init() michael@0: { michael@0: // Create temporary arenas to hold the data allocated during the michael@0: // parallel code. michael@0: // michael@0: // Note: you might think (as I did, initially) that we could use michael@0: // compartment |Allocator| for the main thread. This is not true, michael@0: // because when executing parallel code we sometimes check what michael@0: // arena list an object is in to decide if it is writable. If we michael@0: // used the compartment |Allocator| for the main thread, then the michael@0: // main thread would be permitted to write to any object it wants. michael@0: michael@0: if (!Monitor::init()) michael@0: return false; michael@0: michael@0: cxLock_ = PR_NewLock(); michael@0: if (!cxLock_) michael@0: return false; michael@0: michael@0: for (unsigned i = 0; i < threadPool_->numWorkers(); i++) { michael@0: Allocator *allocator = cx_->new_(cx_->zone()); michael@0: if (!allocator) michael@0: return false; michael@0: michael@0: if (!allocators_.append(allocator)) { michael@0: js_delete(allocator); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: ForkJoinShared::~ForkJoinShared() michael@0: { michael@0: PR_DestroyLock(cxLock_); michael@0: michael@0: while (allocators_.length() > 0) michael@0: js_delete(allocators_.popCopy()); michael@0: } michael@0: michael@0: ParallelResult michael@0: ForkJoinShared::execute() michael@0: { michael@0: // Sometimes a GC request occurs *just before* we enter into the michael@0: // parallel section. Rather than enter into the parallel section michael@0: // and then abort, we just check here and abort early. michael@0: if (cx_->runtime()->interruptPar) michael@0: return TP_RETRY_SEQUENTIALLY; michael@0: michael@0: AutoLockMonitor lock(*this); michael@0: michael@0: ParallelResult jobResult = TP_SUCCESS; michael@0: { michael@0: AutoUnlockMonitor unlock(*this); michael@0: michael@0: // Push parallel tasks and wait until they're all done. michael@0: jobResult = threadPool_->executeJob(cx_, this, sliceStart_, sliceEnd_); michael@0: if (jobResult == TP_FATAL) michael@0: return TP_FATAL; michael@0: } michael@0: michael@0: transferArenasToCompartmentAndProcessGCRequests(); michael@0: michael@0: // Check if any of the workers failed. michael@0: if (abort_) { michael@0: if (fatal_) michael@0: return TP_FATAL; michael@0: return TP_RETRY_SEQUENTIALLY; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: Spew(SpewOps, "Completed parallel job [slices: %d, threads: %d, stolen: %d (work stealing:%s)]", michael@0: sliceEnd_ - sliceStart_, michael@0: threadPool_->numWorkers(), michael@0: threadPool_->stolenSlices(), michael@0: threadPool_->workStealing() ? "ON" : "OFF"); michael@0: #endif michael@0: michael@0: // Everything went swimmingly. Give yourself a pat on the back. michael@0: return jobResult; michael@0: } michael@0: michael@0: void michael@0: ForkJoinShared::transferArenasToCompartmentAndProcessGCRequests() michael@0: { michael@0: JSCompartment *comp = cx_->compartment(); michael@0: for (unsigned i = 0; i < threadPool_->numWorkers(); i++) michael@0: comp->adoptWorkerAllocator(allocators_[i]); michael@0: michael@0: if (gcRequested_) { michael@0: if (!gcZone_) michael@0: TriggerGC(cx_->runtime(), gcReason_); michael@0: else michael@0: TriggerZoneGC(gcZone_, gcReason_); michael@0: gcRequested_ = false; michael@0: gcZone_ = nullptr; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: ForkJoinShared::executeFromWorker(ThreadPoolWorker *worker, uintptr_t stackLimit) michael@0: { michael@0: PerThreadData thisThread(cx_->runtime()); michael@0: if (!thisThread.init()) { michael@0: setAbortFlagAndRequestInterrupt(true); michael@0: return false; michael@0: } michael@0: TlsPerThreadData.set(&thisThread); michael@0: michael@0: #ifdef JS_ARM_SIMULATOR michael@0: stackLimit = Simulator::StackLimit(); michael@0: #endif michael@0: michael@0: // Don't use setIonStackLimit() because that acquires the ionStackLimitLock, and the michael@0: // lock has not been initialized in these cases. michael@0: thisThread.jitStackLimit = stackLimit; michael@0: executePortion(&thisThread, worker); michael@0: TlsPerThreadData.set(nullptr); michael@0: michael@0: return !abort_; michael@0: } michael@0: michael@0: bool michael@0: ForkJoinShared::executeFromMainThread(ThreadPoolWorker *worker) michael@0: { michael@0: executePortion(&cx_->mainThread(), worker); michael@0: return !abort_; michael@0: } michael@0: michael@0: void michael@0: ForkJoinShared::executePortion(PerThreadData *perThread, ThreadPoolWorker *worker) michael@0: { michael@0: // WARNING: This code runs ON THE PARALLEL WORKER THREAD. michael@0: // Be careful when accessing cx_. michael@0: michael@0: // ForkJoinContext already contains an AutoAssertNoGC; however, the analysis michael@0: // does not propagate this type information. We duplicate the assertion michael@0: // here for maximum clarity. michael@0: JS::AutoAssertNoGC nogc(runtime()); michael@0: michael@0: Allocator *allocator = allocators_[worker->id()]; michael@0: ForkJoinContext cx(perThread, worker, allocator, this, &records_[worker->id()]); michael@0: AutoSetForkJoinContext autoContext(&cx); michael@0: michael@0: #ifdef DEBUG michael@0: // Set the maximum worker and slice number for prettier spewing. michael@0: cx.maxWorkerId = threadPool_->numWorkers(); michael@0: #endif michael@0: michael@0: Spew(SpewOps, "Up"); michael@0: michael@0: // Make a new IonContext for the slice, which is needed if we need to michael@0: // re-enter the VM. michael@0: IonContext icx(CompileRuntime::get(cx_->runtime()), michael@0: CompileCompartment::get(cx_->compartment()), michael@0: nullptr); michael@0: michael@0: JS_ASSERT(cx.bailoutRecord->topScript == nullptr); michael@0: michael@0: if (!fun_->nonLazyScript()->hasParallelIonScript()) { michael@0: // Sometimes, particularly with GCZeal, the parallel ion michael@0: // script can be collected between starting the parallel michael@0: // op and reaching this point. In that case, we just fail michael@0: // and fallback. michael@0: Spew(SpewOps, "Down (Script no longer present)"); michael@0: cx.bailoutRecord->setCause(ParallelBailoutMainScriptNotPresent); michael@0: setAbortFlagAndRequestInterrupt(false); michael@0: } else { michael@0: ParallelIonInvoke<3> fii(cx_->runtime(), fun_, 3); michael@0: michael@0: fii.args[0] = Int32Value(worker->id()); michael@0: fii.args[1] = Int32Value(sliceStart_); michael@0: fii.args[2] = Int32Value(sliceEnd_); michael@0: michael@0: bool ok = fii.invoke(perThread); michael@0: JS_ASSERT(ok == !cx.bailoutRecord->topScript); michael@0: if (!ok) michael@0: setAbortFlagAndRequestInterrupt(false); michael@0: } michael@0: michael@0: Spew(SpewOps, "Down"); michael@0: } michael@0: michael@0: void michael@0: ForkJoinShared::setAbortFlagDueToInterrupt(ForkJoinContext &cx) michael@0: { michael@0: JS_ASSERT(cx_->runtime()->interruptPar); michael@0: // The GC Needed flag should not be set during parallel michael@0: // execution. Instead, one of the requestGC() or michael@0: // requestZoneGC() methods should be invoked. michael@0: JS_ASSERT(!cx_->runtime()->gcIsNeeded); michael@0: michael@0: if (!abort_) { michael@0: cx.bailoutRecord->setCause(ParallelBailoutInterrupt); michael@0: setAbortFlagAndRequestInterrupt(false); michael@0: } michael@0: } michael@0: michael@0: void michael@0: ForkJoinShared::setAbortFlagAndRequestInterrupt(bool fatal) michael@0: { michael@0: AutoLockMonitor lock(*this); michael@0: michael@0: abort_ = true; michael@0: fatal_ = fatal_ || fatal; michael@0: michael@0: // Note: The ForkJoin trigger here avoids the expensive memory protection needed to michael@0: // interrupt Ion code compiled for sequential execution. michael@0: cx_->runtime()->requestInterrupt(JSRuntime::RequestInterruptAnyThreadForkJoin); michael@0: } michael@0: michael@0: void michael@0: ForkJoinShared::requestGC(JS::gcreason::Reason reason) michael@0: { michael@0: AutoLockMonitor lock(*this); michael@0: michael@0: gcZone_ = nullptr; michael@0: gcReason_ = reason; michael@0: gcRequested_ = true; michael@0: } michael@0: michael@0: void michael@0: ForkJoinShared::requestZoneGC(JS::Zone *zone, JS::gcreason::Reason reason) michael@0: { michael@0: AutoLockMonitor lock(*this); michael@0: michael@0: if (gcRequested_ && gcZone_ != zone) { michael@0: // If a full GC has been requested, or a GC for another zone, michael@0: // issue a request for a full GC. michael@0: gcZone_ = nullptr; michael@0: gcReason_ = reason; michael@0: gcRequested_ = true; michael@0: } else { michael@0: // Otherwise, just GC this zone. michael@0: gcZone_ = zone; michael@0: gcReason_ = reason; michael@0: gcRequested_ = true; michael@0: } michael@0: } michael@0: michael@0: ///////////////////////////////////////////////////////////////////////////// michael@0: // ForkJoinContext michael@0: // michael@0: michael@0: ForkJoinContext::ForkJoinContext(PerThreadData *perThreadData, ThreadPoolWorker *worker, michael@0: Allocator *allocator, ForkJoinShared *shared, michael@0: ParallelBailoutRecord *bailoutRecord) michael@0: : ThreadSafeContext(shared->runtime(), perThreadData, Context_ForkJoin), michael@0: bailoutRecord(bailoutRecord), michael@0: targetRegionStart(nullptr), michael@0: targetRegionEnd(nullptr), michael@0: shared_(shared), michael@0: worker_(worker), michael@0: acquiredJSContext_(false), michael@0: nogc_(shared->runtime()) michael@0: { michael@0: /* michael@0: * Unsafely set the zone. This is used to track malloc counters and to michael@0: * trigger GCs and is otherwise not thread-safe to access. michael@0: */ michael@0: zone_ = shared->zone(); michael@0: michael@0: /* michael@0: * Unsafely set the compartment. This is used to get read-only access to michael@0: * shared tables. michael@0: */ michael@0: compartment_ = shared->compartment(); michael@0: michael@0: allocator_ = allocator; michael@0: } michael@0: michael@0: bool michael@0: ForkJoinContext::isMainThread() const michael@0: { michael@0: return perThreadData == &shared_->runtime()->mainThread; michael@0: } michael@0: michael@0: JSRuntime * michael@0: ForkJoinContext::runtime() michael@0: { michael@0: return shared_->runtime(); michael@0: } michael@0: michael@0: JSContext * michael@0: ForkJoinContext::acquireJSContext() michael@0: { michael@0: JSContext *cx = shared_->acquireJSContext(); michael@0: JS_ASSERT(!acquiredJSContext_); michael@0: acquiredJSContext_ = true; michael@0: return cx; michael@0: } michael@0: michael@0: void michael@0: ForkJoinContext::releaseJSContext() michael@0: { michael@0: JS_ASSERT(acquiredJSContext_); michael@0: acquiredJSContext_ = false; michael@0: return shared_->releaseJSContext(); michael@0: } michael@0: michael@0: bool michael@0: ForkJoinContext::hasAcquiredJSContext() const michael@0: { michael@0: return acquiredJSContext_; michael@0: } michael@0: michael@0: bool michael@0: ForkJoinContext::check() michael@0: { michael@0: if (runtime()->interruptPar) { michael@0: shared_->setAbortFlagDueToInterrupt(*this); michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: ForkJoinContext::requestGC(JS::gcreason::Reason reason) michael@0: { michael@0: shared_->requestGC(reason); michael@0: bailoutRecord->setCause(ParallelBailoutRequestedGC); michael@0: shared_->setAbortFlagAndRequestInterrupt(false); michael@0: } michael@0: michael@0: void michael@0: ForkJoinContext::requestZoneGC(JS::Zone *zone, JS::gcreason::Reason reason) michael@0: { michael@0: shared_->requestZoneGC(zone, reason); michael@0: bailoutRecord->setCause(ParallelBailoutRequestedZoneGC); michael@0: shared_->setAbortFlagAndRequestInterrupt(false); michael@0: } michael@0: michael@0: bool michael@0: ForkJoinContext::setPendingAbortFatal(ParallelBailoutCause cause) michael@0: { michael@0: shared_->setPendingAbortFatal(); michael@0: bailoutRecord->setCause(cause); michael@0: return false; michael@0: } michael@0: michael@0: ////////////////////////////////////////////////////////////////////////////// michael@0: // ParallelBailoutRecord michael@0: michael@0: void michael@0: js::ParallelBailoutRecord::init(JSContext *cx) michael@0: { michael@0: reset(cx); michael@0: } michael@0: michael@0: void michael@0: js::ParallelBailoutRecord::reset(JSContext *cx) michael@0: { michael@0: topScript = nullptr; michael@0: cause = ParallelBailoutNone; michael@0: depth = 0; michael@0: } michael@0: michael@0: void michael@0: js::ParallelBailoutRecord::setCause(ParallelBailoutCause cause, michael@0: JSScript *outermostScript, michael@0: JSScript *currentScript, michael@0: jsbytecode *currentPc) michael@0: { michael@0: this->cause = cause; michael@0: updateCause(cause, outermostScript, currentScript, currentPc); michael@0: } michael@0: michael@0: void michael@0: js::ParallelBailoutRecord::updateCause(ParallelBailoutCause cause, michael@0: JSScript *outermostScript, michael@0: JSScript *currentScript, michael@0: jsbytecode *currentPc) michael@0: { michael@0: JS_ASSERT_IF(outermostScript, currentScript); michael@0: JS_ASSERT_IF(outermostScript, outermostScript->hasParallelIonScript()); michael@0: JS_ASSERT_IF(currentScript, outermostScript); michael@0: JS_ASSERT_IF(!currentScript, !currentPc); michael@0: michael@0: if (this->cause == ParallelBailoutNone) michael@0: this->cause = cause; michael@0: michael@0: if (outermostScript) michael@0: this->topScript = outermostScript; michael@0: michael@0: if (currentScript) michael@0: addTrace(currentScript, currentPc); michael@0: } michael@0: michael@0: void michael@0: js::ParallelBailoutRecord::addTrace(JSScript *script, michael@0: jsbytecode *pc) michael@0: { michael@0: // Ideally, this should never occur, because we should always have michael@0: // a script when we invoke setCause, but I havent' fully michael@0: // refactored things to that point yet: michael@0: if (topScript == nullptr && script != nullptr) michael@0: topScript = script; michael@0: michael@0: if (depth < MaxDepth) { michael@0: trace[depth].script = script; michael@0: trace[depth].bytecode = pc; michael@0: depth += 1; michael@0: } michael@0: } michael@0: michael@0: ////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: // michael@0: // Debug spew michael@0: // michael@0: michael@0: #ifdef DEBUG michael@0: michael@0: static const char * michael@0: ExecutionStatusToString(ExecutionStatus status) michael@0: { michael@0: switch (status) { michael@0: case ExecutionFatal: michael@0: return "fatal"; michael@0: case ExecutionSequential: michael@0: return "sequential"; michael@0: case ExecutionWarmup: michael@0: return "warmup"; michael@0: case ExecutionParallel: michael@0: return "parallel"; michael@0: } michael@0: return "(unknown status)"; michael@0: } michael@0: michael@0: static const char * michael@0: MethodStatusToString(MethodStatus status) michael@0: { michael@0: switch (status) { michael@0: case Method_Error: michael@0: return "error"; michael@0: case Method_CantCompile: michael@0: return "can't compile"; michael@0: case Method_Skipped: michael@0: return "skipped"; michael@0: case Method_Compiled: michael@0: return "compiled"; michael@0: } michael@0: return "(unknown status)"; michael@0: } michael@0: michael@0: static unsigned michael@0: NumberOfDigits(unsigned n) michael@0: { michael@0: if (n == 0) michael@0: return 1; michael@0: unsigned d = 0; michael@0: while (n != 0) { michael@0: d++; michael@0: n /= 10; michael@0: } michael@0: return d; michael@0: } michael@0: michael@0: static const size_t BufferSize = 4096; michael@0: michael@0: class ParallelSpewer michael@0: { michael@0: uint32_t depth; michael@0: bool colorable; michael@0: bool active[NumSpewChannels]; michael@0: michael@0: const char *color(const char *colorCode) { michael@0: if (!colorable) michael@0: return ""; michael@0: return colorCode; michael@0: } michael@0: michael@0: const char *reset() { return color("\x1b[0m"); } michael@0: const char *bold() { return color("\x1b[1m"); } michael@0: const char *red() { return color("\x1b[31m"); } michael@0: const char *green() { return color("\x1b[32m"); } michael@0: const char *yellow() { return color("\x1b[33m"); } michael@0: const char *cyan() { return color("\x1b[36m"); } michael@0: const char *workerColor(uint32_t id) { michael@0: static const char *colors[] = { michael@0: "\x1b[7m\x1b[31m", "\x1b[7m\x1b[32m", "\x1b[7m\x1b[33m", michael@0: "\x1b[7m\x1b[34m", "\x1b[7m\x1b[35m", "\x1b[7m\x1b[36m", michael@0: "\x1b[7m\x1b[37m", michael@0: "\x1b[31m", "\x1b[32m", "\x1b[33m", michael@0: "\x1b[34m", "\x1b[35m", "\x1b[36m", michael@0: "\x1b[37m" michael@0: }; michael@0: return color(colors[id % 14]); michael@0: } michael@0: michael@0: public: michael@0: ParallelSpewer() michael@0: : depth(0) michael@0: { michael@0: const char *env; michael@0: michael@0: mozilla::PodArrayZero(active); michael@0: env = getenv("PAFLAGS"); michael@0: if (env) { michael@0: if (strstr(env, "ops")) michael@0: active[SpewOps] = true; michael@0: if (strstr(env, "compile")) michael@0: active[SpewCompile] = true; michael@0: if (strstr(env, "bailouts")) michael@0: active[SpewBailouts] = true; michael@0: if (strstr(env, "full")) { michael@0: for (uint32_t i = 0; i < NumSpewChannels; i++) michael@0: active[i] = true; michael@0: } michael@0: } michael@0: michael@0: env = getenv("TERM"); michael@0: if (env && isatty(fileno(stderr))) { michael@0: if (strcmp(env, "xterm-color") == 0 || strcmp(env, "xterm-256color") == 0) michael@0: colorable = true; michael@0: } michael@0: } michael@0: michael@0: bool isActive(js::parallel::SpewChannel channel) { michael@0: return active[channel]; michael@0: } michael@0: michael@0: void spewVA(js::parallel::SpewChannel channel, const char *fmt, va_list ap) { michael@0: if (!active[channel]) michael@0: return; michael@0: michael@0: // Print into a buffer first so we use one fprintf, which usually michael@0: // doesn't get interrupted when running with multiple threads. michael@0: char buf[BufferSize]; michael@0: michael@0: if (ForkJoinContext *cx = ForkJoinContext::current()) { michael@0: // Print the format first into a buffer to right-justify the michael@0: // worker ids. michael@0: char bufbuf[BufferSize]; michael@0: JS_snprintf(bufbuf, BufferSize, "[%%sParallel:%%0%du%%s] ", michael@0: NumberOfDigits(cx->maxWorkerId)); michael@0: JS_snprintf(buf, BufferSize, bufbuf, workerColor(cx->workerId()), michael@0: cx->workerId(), reset()); michael@0: } else { michael@0: JS_snprintf(buf, BufferSize, "[Parallel:M] "); michael@0: } michael@0: michael@0: for (uint32_t i = 0; i < depth; i++) michael@0: JS_snprintf(buf + strlen(buf), BufferSize, " "); michael@0: michael@0: JS_vsnprintf(buf + strlen(buf), BufferSize, fmt, ap); michael@0: JS_snprintf(buf + strlen(buf), BufferSize, "\n"); michael@0: michael@0: fprintf(stderr, "%s", buf); michael@0: } michael@0: michael@0: void spew(js::parallel::SpewChannel channel, const char *fmt, ...) { michael@0: va_list ap; michael@0: va_start(ap, fmt); michael@0: spewVA(channel, fmt, ap); michael@0: va_end(ap); michael@0: } michael@0: michael@0: void beginOp(JSContext *cx, const char *name) { michael@0: if (!active[SpewOps]) michael@0: return; michael@0: michael@0: if (cx) { michael@0: jsbytecode *pc; michael@0: RootedScript script(cx, cx->currentScript(&pc)); michael@0: if (script && pc) { michael@0: NonBuiltinScriptFrameIter iter(cx); michael@0: if (iter.done()) { michael@0: spew(SpewOps, "%sBEGIN %s%s (%s:%u)", bold(), name, reset(), michael@0: script->filename(), PCToLineNumber(script, pc)); michael@0: } else { michael@0: spew(SpewOps, "%sBEGIN %s%s (%s:%u -> %s:%u)", bold(), name, reset(), michael@0: iter.script()->filename(), PCToLineNumber(iter.script(), iter.pc()), michael@0: script->filename(), PCToLineNumber(script, pc)); michael@0: } michael@0: } else { michael@0: spew(SpewOps, "%sBEGIN %s%s", bold(), name, reset()); michael@0: } michael@0: } else { michael@0: spew(SpewOps, "%sBEGIN %s%s", bold(), name, reset()); michael@0: } michael@0: michael@0: depth++; michael@0: } michael@0: michael@0: void endOp(ExecutionStatus status) { michael@0: if (!active[SpewOps]) michael@0: return; michael@0: michael@0: JS_ASSERT(depth > 0); michael@0: depth--; michael@0: michael@0: const char *statusColor; michael@0: switch (status) { michael@0: case ExecutionFatal: michael@0: statusColor = red(); michael@0: break; michael@0: case ExecutionSequential: michael@0: statusColor = yellow(); michael@0: break; michael@0: case ExecutionParallel: michael@0: statusColor = green(); michael@0: break; michael@0: default: michael@0: statusColor = reset(); michael@0: break; michael@0: } michael@0: michael@0: spew(SpewOps, "%sEND %s%s%s", bold(), michael@0: statusColor, ExecutionStatusToString(status), reset()); michael@0: } michael@0: michael@0: void bailout(uint32_t count, HandleScript script, michael@0: jsbytecode *pc, ParallelBailoutCause cause) { michael@0: if (!active[SpewOps]) michael@0: return; michael@0: michael@0: const char *filename = ""; michael@0: unsigned line=0, column=0; michael@0: if (script) { michael@0: line = PCToLineNumber(script, pc, &column); michael@0: filename = script->filename(); michael@0: } michael@0: michael@0: spew(SpewOps, "%s%sBAILOUT %d%s: %s (%d) at %s:%d:%d", bold(), yellow(), count, reset(), michael@0: BailoutExplanation(cause), cause, filename, line, column); michael@0: } michael@0: michael@0: void beginCompile(HandleScript script) { michael@0: if (!active[SpewCompile]) michael@0: return; michael@0: michael@0: spew(SpewCompile, "COMPILE %p:%s:%u", script.get(), script->filename(), script->lineno()); michael@0: depth++; michael@0: } michael@0: michael@0: void endCompile(MethodStatus status) { michael@0: if (!active[SpewCompile]) michael@0: return; michael@0: michael@0: JS_ASSERT(depth > 0); michael@0: depth--; michael@0: michael@0: const char *statusColor; michael@0: switch (status) { michael@0: case Method_Error: michael@0: case Method_CantCompile: michael@0: statusColor = red(); michael@0: break; michael@0: case Method_Skipped: michael@0: statusColor = yellow(); michael@0: break; michael@0: case Method_Compiled: michael@0: statusColor = green(); michael@0: break; michael@0: default: michael@0: statusColor = reset(); michael@0: break; michael@0: } michael@0: michael@0: spew(SpewCompile, "END %s%s%s", statusColor, MethodStatusToString(status), reset()); michael@0: } michael@0: michael@0: void spewMIR(MDefinition *mir, const char *fmt, va_list ap) { michael@0: if (!active[SpewCompile]) michael@0: return; michael@0: michael@0: char buf[BufferSize]; michael@0: JS_vsnprintf(buf, BufferSize, fmt, ap); michael@0: michael@0: JSScript *script = mir->block()->info().script(); michael@0: spew(SpewCompile, "%s%s%s: %s (%s:%u)", cyan(), mir->opName(), reset(), buf, michael@0: script->filename(), PCToLineNumber(script, mir->trackedPc())); michael@0: } michael@0: michael@0: void spewBailoutIR(IonLIRTraceData *data) { michael@0: if (!active[SpewBailouts]) michael@0: return; michael@0: michael@0: // If we didn't bail from a LIR/MIR but from a propagated parallel michael@0: // bailout, don't bother printing anything since we've printed it michael@0: // elsewhere. michael@0: if (data->mirOpName && data->script) { michael@0: spew(SpewBailouts, "%sBailout%s: %s / %s%s%s (block %d lir %d) (%s:%u)", yellow(), reset(), michael@0: data->lirOpName, cyan(), data->mirOpName, reset(), michael@0: data->blockIndex, data->lirIndex, data->script->filename(), michael@0: PCToLineNumber(data->script, data->pc)); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: // Singleton instance of the spewer. michael@0: static ParallelSpewer spewer; michael@0: michael@0: bool michael@0: parallel::SpewEnabled(SpewChannel channel) michael@0: { michael@0: return spewer.isActive(channel); michael@0: } michael@0: michael@0: void michael@0: parallel::Spew(SpewChannel channel, const char *fmt, ...) michael@0: { michael@0: va_list ap; michael@0: va_start(ap, fmt); michael@0: spewer.spewVA(channel, fmt, ap); michael@0: va_end(ap); michael@0: } michael@0: michael@0: void michael@0: parallel::SpewBeginOp(JSContext *cx, const char *name) michael@0: { michael@0: spewer.beginOp(cx, name); michael@0: } michael@0: michael@0: ExecutionStatus michael@0: parallel::SpewEndOp(ExecutionStatus status) michael@0: { michael@0: spewer.endOp(status); michael@0: return status; michael@0: } michael@0: michael@0: void michael@0: parallel::SpewBailout(uint32_t count, HandleScript script, michael@0: jsbytecode *pc, ParallelBailoutCause cause) michael@0: { michael@0: spewer.bailout(count, script, pc, cause); michael@0: } michael@0: michael@0: void michael@0: parallel::SpewBeginCompile(HandleScript script) michael@0: { michael@0: spewer.beginCompile(script); michael@0: } michael@0: michael@0: MethodStatus michael@0: parallel::SpewEndCompile(MethodStatus status) michael@0: { michael@0: spewer.endCompile(status); michael@0: return status; michael@0: } michael@0: michael@0: void michael@0: parallel::SpewMIR(MDefinition *mir, const char *fmt, ...) michael@0: { michael@0: va_list ap; michael@0: va_start(ap, fmt); michael@0: spewer.spewMIR(mir, fmt, ap); michael@0: va_end(ap); michael@0: } michael@0: michael@0: void michael@0: parallel::SpewBailoutIR(IonLIRTraceData *data) michael@0: { michael@0: spewer.spewBailoutIR(data); michael@0: } michael@0: michael@0: #endif // DEBUG michael@0: michael@0: bool michael@0: js::InExclusiveParallelSection() michael@0: { michael@0: return InParallelSection() && ForkJoinContext::current()->hasAcquiredJSContext(); michael@0: } michael@0: michael@0: bool michael@0: js::ParallelTestsShouldPass(JSContext *cx) michael@0: { michael@0: return jit::IsIonEnabled(cx) && michael@0: jit::IsBaselineEnabled(cx) && michael@0: !jit::js_JitOptions.eagerCompilation && michael@0: jit::js_JitOptions.baselineUsesBeforeCompile != 0 && michael@0: cx->runtime()->gcZeal() == 0; michael@0: } michael@0: michael@0: void michael@0: js::RequestInterruptForForkJoin(JSRuntime *rt, JSRuntime::InterruptMode mode) michael@0: { michael@0: if (mode != JSRuntime::RequestInterruptAnyThreadDontStopIon) michael@0: rt->interruptPar = true; michael@0: } michael@0: michael@0: bool michael@0: js::intrinsic_SetForkJoinTargetRegion(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: // This version of SetForkJoinTargetRegion is called during michael@0: // sequential execution. It is a no-op. The parallel version michael@0: // is intrinsic_SetForkJoinTargetRegionPar(), below. michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: intrinsic_SetForkJoinTargetRegionPar(ForkJoinContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: // Sets the *target region*, which is the portion of the output michael@0: // buffer that the current iteration is permitted to write to. michael@0: // michael@0: // Note: it is important that the target region should be an michael@0: // entire element (or several elements) of the output array and michael@0: // not some region that spans from the middle of one element into michael@0: // the middle of another. This is because the guarding code michael@0: // assumes that handles, which never straddle across elements, michael@0: // will either be contained entirely within the target region or michael@0: // be contained entirely without of the region, and not straddling michael@0: // the region nor encompassing it. michael@0: michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: JS_ASSERT(argc == 3); michael@0: JS_ASSERT(args[0].isObject() && args[0].toObject().is()); michael@0: JS_ASSERT(args[1].isInt32()); michael@0: JS_ASSERT(args[2].isInt32()); michael@0: michael@0: uint8_t *mem = args[0].toObject().as().typedMem(); michael@0: int32_t start = args[1].toInt32(); michael@0: int32_t end = args[2].toInt32(); michael@0: michael@0: cx->targetRegionStart = mem + start; michael@0: cx->targetRegionEnd = mem + end; michael@0: return true; michael@0: } michael@0: michael@0: JS_JITINFO_NATIVE_PARALLEL(js::intrinsic_SetForkJoinTargetRegionInfo, michael@0: intrinsic_SetForkJoinTargetRegionPar); michael@0: michael@0: bool michael@0: js::intrinsic_ClearThreadLocalArenas(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: intrinsic_ClearThreadLocalArenasPar(ForkJoinContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: cx->allocator()->arenas.wipeDuringParallelExecution(cx->runtime()); michael@0: return true; michael@0: } michael@0: michael@0: JS_JITINFO_NATIVE_PARALLEL(js::intrinsic_ClearThreadLocalArenasInfo, michael@0: intrinsic_ClearThreadLocalArenasPar); michael@0: michael@0: #endif // JS_THREADSAFE && JS_ION