blob: e0ce6153f65e5f3786646660a947b553db346304 [file] [log] [blame]
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "jscntxt.h"
#ifdef JS_THREADSAFE
#if defined(STARBOARD)
#include "starboard/client_porting/pr_starboard/pr_starboard.h"
#else // defined(STARBOARD)
# include "prthread.h"
# include "prprf.h"
#endif // defined(STARBOARD)
#endif
#include "vm/ForkJoin.h"
#if defined(JS_THREADSAFE)
#include "jit/BaselineJIT.h"
#include "vm/Monitor.h"
#endif
#if defined(DEBUG) && defined(JS_THREADSAFE) && defined(JS_ION)
# include "jit/Ion.h"
# include "jit/MIR.h"
# include "jit/MIRGraph.h"
# include "jit/IonCompartment.h"
#endif // DEBUG && THREADSAFE && ION
#include "vm/Interpreter-inl.h"
using namespace js;
using namespace js::parallel;
using namespace js::jit;
///////////////////////////////////////////////////////////////////////////
// Degenerate configurations
//
// When JS_THREADSAFE or JS_ION is not defined, we simply run the
// |func| callback sequentially. We also forego the feedback
// altogether.
static bool
ExecuteSequentially(JSContext *cx_, HandleValue funVal, bool *complete);
#if !defined(JS_THREADSAFE) || !defined(JS_ION)
bool
js::ForkJoin(JSContext *cx, CallArgs &args)
{
RootedValue argZero(cx, args[0]);
bool complete = false; // since warmup is false, will always complete
return ExecuteSequentially(cx, argZero, &complete);
}
uint32_t
js::ForkJoinSlices(JSContext *cx)
{
return 1; // just the main thread
}
JSContext *
ForkJoinSlice::acquireContext()
{
return NULL;
}
void
ForkJoinSlice::releaseContext()
{
}
bool
ForkJoinSlice::isMainThread() const
{
return true;
}
bool
ForkJoinSlice::InitializeTLS()
{
return true;
}
JSRuntime *
ForkJoinSlice::runtime()
{
JS_NOT_REACHED("Not THREADSAFE build");
}
bool
ForkJoinSlice::check()
{
JS_NOT_REACHED("Not THREADSAFE build");
return true;
}
void
ForkJoinSlice::requestGC(JS::gcreason::Reason reason)
{
JS_NOT_REACHED("Not THREADSAFE build");
}
void
ForkJoinSlice::requestZoneGC(JS::Zone *zone, JS::gcreason::Reason reason)
{
JS_NOT_REACHED("Not THREADSAFE build");
}
void
ParallelBailoutRecord::setCause(ParallelBailoutCause cause,
JSScript *outermostScript,
JSScript *currentScript,
jsbytecode *currentPc)
{
JS_NOT_REACHED("Not THREADSAFE build");
}
void
ParallelBailoutRecord::addTrace(JSScript *script,
jsbytecode *pc)
{
JS_NOT_REACHED("Not THREADSAFE build");
}
bool
js::InSequentialOrExclusiveParallelSection()
{
return true;
}
bool
js::ParallelTestsShouldPass(JSContext *cx)
{
return false;
}
#endif // !JS_THREADSAFE || !JS_ION
///////////////////////////////////////////////////////////////////////////
// All configurations
//
// Some code that is shared between degenerate and parallel configurations.
static bool
ExecuteSequentially(JSContext *cx, HandleValue funVal, bool *complete)
{
uint32_t numSlices = ForkJoinSlices(cx);
FastInvokeGuard fig(cx, funVal);
bool allComplete = true;
for (uint32_t i = 0; i < numSlices; i++) {
InvokeArgs &args = fig.args();
if (!args.init(3))
return false;
args.setCallee(funVal);
args.setThis(UndefinedValue());
args[0].setInt32(i);
args[1].setInt32(numSlices);
args[2].setBoolean(!!cx->runtime()->parallelWarmup);
if (!fig.invoke(cx))
return false;
allComplete = allComplete & args.rval().toBoolean();
}
*complete = allComplete;
return true;
}
///////////////////////////////////////////////////////////////////////////
// Parallel configurations
//
// The remainder of this file is specific to cases where both
// JS_THREADSAFE and JS_ION are enabled.
#if defined(JS_THREADSAFE) && defined(JS_ION)
///////////////////////////////////////////////////////////////////////////
// Class Declarations and Function Prototypes
namespace js {
// When writing tests, it is often useful to specify different modes
// of operation.
enum ForkJoinMode {
// WARNING: If you change this enum, you MUST update
// ForkJoinMode() in ParallelArray.js
// The "normal" behavior: attempt parallel, fallback to
// sequential. If compilation is ongoing in a helper thread, then
// run sequential warmup iterations in the meantime. If those
// iterations wind up completing all the work, just abort.
ForkJoinModeNormal,
// Like normal, except that we will keep running warmup iterations
// until compilations are complete, even if there is no more work
// to do. This is useful in tests as a "setup" run.
ForkJoinModeCompile,
// Requires that compilation has already completed. Expects parallel
// execution to proceed without a hitch. (Reports an error otherwise)
ForkJoinModeParallel,
// Requires that compilation has already completed. Expects
// parallel execution to bailout once but continue after that without
// further bailouts. (Reports an error otherwise)
ForkJoinModeRecover,
// Expects all parallel executions to yield a bailout. If this is not
// the case, reports an error.
ForkJoinModeBailout,
NumForkJoinModes
};
#if defined(STARBOARD)
PRTLSIndex ForkJoinSlice::ThreadPrivateIndex;
#else // defined(STARBOARD)
unsigned ForkJoinSlice::ThreadPrivateIndex;
#endif // defined(STARBOARD)
bool ForkJoinSlice::TLSInitialized;
class ParallelDo
{
public:
// For tests, make sure to keep this in sync with minItemsTestingThreshold.
const static uint32_t MAX_BAILOUTS = 3;
uint32_t bailouts;
// Information about the bailout:
ParallelBailoutCause bailoutCause;
RootedScript bailoutScript;
jsbytecode *bailoutBytecode;
ParallelDo(JSContext *cx, HandleObject fun, ForkJoinMode mode);
ExecutionStatus apply();
private:
// Most of the functions involved in managing the parallel
// compilation follow a similar control-flow. They return RedLight
// if they have either encountered a fatal error or completed the
// execution, such that no further work is needed. In that event,
// they take an `ExecutionStatus*` which they use to report
// whether execution was successful or not. If the function
// returns `GreenLight`, then the parallel operation is not yet
// fully completed, so the state machine should carry on.
enum TrafficLight {
RedLight,
GreenLight
};
struct WorklistData {
// True if we enqueued the callees from the ion-compiled
// version of this entry
bool calleesEnqueued;
// Last record useCount; updated after warmup
// iterations;
uint32_t useCount;
// Number of continuous "stalls" --- meaning warmups
// where useCount did not increase.
uint32_t stallCount;
void reset() {
calleesEnqueued = false;
useCount = 0;
stallCount = 0;
}
};
JSContext *cx_;
HandleObject fun_;
Vector<ParallelBailoutRecord, 16> bailoutRecords_;
AutoScriptVector worklist_;
Vector<WorklistData, 16> worklistData_;
ForkJoinMode mode_;
TrafficLight enqueueInitialScript(ExecutionStatus *status);
TrafficLight compileForParallelExecution(ExecutionStatus *status);
TrafficLight warmupExecution(bool stopIfComplete,
ExecutionStatus *status);
TrafficLight parallelExecution(ExecutionStatus *status);
TrafficLight sequentialExecution(bool disqualified, ExecutionStatus *status);
TrafficLight recoverFromBailout(ExecutionStatus *status);
TrafficLight fatalError(ExecutionStatus *status);
void determineBailoutCause();
bool invalidateBailedOutScripts();
ExecutionStatus sequentialExecution(bool disqualified);
TrafficLight appendCallTargetsToWorklist(uint32_t index,
ExecutionStatus *status);
TrafficLight appendCallTargetToWorklist(HandleScript script,
ExecutionStatus *status);
bool addToWorklist(HandleScript script);
inline bool hasScript(Vector<types::RecompileInfo> &scripts, JSScript *script);
}; // class ParallelDo
class ForkJoinShared : public TaskExecutor, public Monitor
{
/////////////////////////////////////////////////////////////////////////
// Constant fields
JSContext *const cx_; // Current context
ThreadPool *const threadPool_; // The thread pool.
HandleObject fun_; // The JavaScript function to execute.
const uint32_t numSlices_; // Total number of threads.
PRCondVar *rendezvousEnd_; // Cond. var used to signal end of rendezvous.
PRLock *cxLock_; // Locks cx_ for parallel VM calls.
ParallelBailoutRecord *const records_; // Bailout records for each slice
/////////////////////////////////////////////////////////////////////////
// Per-thread arenas
//
// Each worker thread gets an arena to use when allocating.
Vector<Allocator *, 16> allocators_;
/////////////////////////////////////////////////////////////////////////
// Locked Fields
//
// Only to be accessed while holding the lock.
uint32_t uncompleted_; // Number of uncompleted worker threads
uint32_t blocked_; // Number of threads that have joined rendezvous
uint32_t rendezvousIndex_; // Number of rendezvous attempts
bool gcRequested_; // True if a worker requested a GC
JS::gcreason::Reason gcReason_; // Reason given to request GC
Zone *gcZone_; // Zone for GC, or NULL for full
/////////////////////////////////////////////////////////////////////////
// Asynchronous Flags
//
// These can be read without the lock (hence the |volatile| declaration).
// All fields should be *written with the lock*, however.
// Set to true when parallel execution should abort.
volatile bool abort_;
// Set to true when a worker bails for a fatal reason.
volatile bool fatal_;
// The main thread has requested a rendezvous.
volatile bool rendezvous_;
// Invoked only from the main thread:
void executeFromMainThread();
// Executes slice #threadId of the work, either from a worker or
// the main thread.
void executePortion(PerThreadData *perThread, uint32_t threadId);
// Rendezvous protocol:
//
// Use AutoRendezvous rather than invoking initiateRendezvous() and
// endRendezvous() directly.
friend class AutoRendezvous;
// Requests that the other threads stop. Must be invoked from the main
// thread.
void initiateRendezvous(ForkJoinSlice &threadCx);
// If a rendezvous has been requested, blocks until the main thread says
// we may continue.
void joinRendezvous(ForkJoinSlice &threadCx);
// Permits other threads to resume execution. Must be invoked from the
// main thread after a call to initiateRendezvous().
void endRendezvous(ForkJoinSlice &threadCx);
public:
ForkJoinShared(JSContext *cx,
ThreadPool *threadPool,
HandleObject fun,
uint32_t numSlices,
uint32_t uncompleted,
ParallelBailoutRecord *records);
~ForkJoinShared();
bool init();
ParallelResult execute();
// Invoked from parallel worker threads:
virtual void executeFromWorker(uint32_t threadId, uintptr_t stackLimit);
// Moves all the per-thread arenas into the main compartment and
// processes any pending requests for a GC. This can only safely
// be invoked on the main thread, either during a rendezvous or
// after the workers have completed.
void transferArenasToCompartmentAndProcessGCRequests();
// Invoked during processing by worker threads to "check in".
bool check(ForkJoinSlice &threadCx);
// Requests a GC, either full or specific to a zone.
void requestGC(JS::gcreason::Reason reason);
void requestZoneGC(JS::Zone *zone, JS::gcreason::Reason reason);
// Requests that computation abort.
void setAbortFlag(bool fatal);
JSRuntime *runtime() { return cx_->runtime(); }
JS::Zone *zone() { return cx_->zone(); }
JSContext *acquireContext() { PR_Lock(cxLock_); return cx_; }
void releaseContext() { PR_Unlock(cxLock_); }
};
class AutoEnterWarmup
{
JSRuntime *runtime_;
public:
AutoEnterWarmup(JSRuntime *runtime) : runtime_(runtime) { runtime_->parallelWarmup++; }
~AutoEnterWarmup() { runtime_->parallelWarmup--; }
};
class AutoRendezvous
{
private:
ForkJoinSlice &threadCx;
public:
AutoRendezvous(ForkJoinSlice &threadCx)
: threadCx(threadCx)
{
threadCx.shared->initiateRendezvous(threadCx);
}
~AutoRendezvous() {
threadCx.shared->endRendezvous(threadCx);
}
};
class AutoSetForkJoinSlice
{
public:
AutoSetForkJoinSlice(ForkJoinSlice *threadCx) {
PR_SetThreadPrivate(ForkJoinSlice::ThreadPrivateIndex, threadCx);
}
~AutoSetForkJoinSlice() {
PR_SetThreadPrivate(ForkJoinSlice::ThreadPrivateIndex, NULL);
}
};
} // namespace js
///////////////////////////////////////////////////////////////////////////
// js::ForkJoin() and ParallelDo class
//
// These are the top-level objects that manage the parallel execution.
// They handle parallel compilation (if necessary), triggering
// parallel execution, and recovering from bailouts.
static const char *ForkJoinModeString(ForkJoinMode mode);
bool
js::ForkJoin(JSContext *cx, CallArgs &args)
{
JS_ASSERT(args[0].isObject()); // else the self-hosted code is wrong
JS_ASSERT(args[0].toObject().is<JSFunction>());
ForkJoinMode mode = ForkJoinModeNormal;
if (args.length() > 1) {
JS_ASSERT(args[1].isInt32()); // else the self-hosted code is wrong
JS_ASSERT(args[1].toInt32() < NumForkJoinModes);
mode = (ForkJoinMode) args[1].toInt32();
}
RootedObject fun(cx, &args[0].toObject());
ParallelDo op(cx, fun, mode);
ExecutionStatus status = op.apply();
if (status == ExecutionFatal)
return false;
switch (mode) {
case ForkJoinModeNormal:
case ForkJoinModeCompile:
return true;
case ForkJoinModeParallel:
if (status == ExecutionParallel && op.bailouts == 0)
return true;
break;
case ForkJoinModeRecover:
if (status != ExecutionSequential && op.bailouts > 0)
return true;
break;
case ForkJoinModeBailout:
if (status != ExecutionParallel)
return true;
break;
case NumForkJoinModes:
break;
}
const char *statusString = "?";
switch (status) {
case ExecutionSequential: statusString = "seq"; break;
case ExecutionParallel: statusString = "par"; break;
case ExecutionWarmup: statusString = "warmup"; break;
case ExecutionFatal: statusString = "fatal"; break;
}
if (ParallelTestsShouldPass(cx)) {
JS_ReportError(cx, "ForkJoin: mode=%s status=%s bailouts=%d",
ForkJoinModeString(mode), statusString, op.bailouts);
return false;
} else {
return true;
}
}
static const char *
ForkJoinModeString(ForkJoinMode mode) {
switch (mode) {
case ForkJoinModeNormal: return "normal";
case ForkJoinModeCompile: return "compile";
case ForkJoinModeParallel: return "parallel";
case ForkJoinModeRecover: return "recover";
case ForkJoinModeBailout: return "bailout";
case NumForkJoinModes: return "max";
}
return "???";
}
js::ParallelDo::ParallelDo(JSContext *cx,
HandleObject fun,
ForkJoinMode mode)
: bailouts(0),
bailoutCause(ParallelBailoutNone),
bailoutScript(cx),
bailoutBytecode(NULL),
cx_(cx),
fun_(fun),
bailoutRecords_(cx),
worklist_(cx),
worklistData_(cx),
mode_(mode)
{ }
ExecutionStatus
js::ParallelDo::apply()
{
ExecutionStatus status;
// High level outline of the procedure:
//
// - As we enter, we check for parallel script without "uncompiled" flag.
// - If present, skip initial enqueue.
// - While not too many bailouts:
// - While all scripts in worklist are not compiled:
// - For each script S in worklist:
// - Compile S if not compiled
// -> Error: fallback
// - If compiled, add call targets to worklist w/o checking uncompiled
// flag
// - If some compilations pending, run warmup iteration
// - Otherwise, clear "uncompiled targets" flag on main script and
// break from loop
// - Attempt parallel execution
// - If successful: return happily
// - If error: abort sadly
// - If bailout:
// - Invalidate any scripts that may need to be invalidated
// - Re-enqueue main script and any uncompiled scripts that were called
// - Too many bailouts: Fallback to sequential
JS_ASSERT_IF(!jit::IsBaselineEnabled(cx_), !jit::IsIonEnabled(cx_));
if (!jit::IsBaselineEnabled(cx_) || !jit::IsIonEnabled(cx_))
return sequentialExecution(true);
SpewBeginOp(cx_, "ParallelDo");
uint32_t slices = ForkJoinSlices(cx_);
if (!bailoutRecords_.resize(slices))
return SpewEndOp(ExecutionFatal);
for (uint32_t i = 0; i < slices; i++)
bailoutRecords_[i].init(cx_);
if (enqueueInitialScript(&status) == RedLight)
return SpewEndOp(status);
Spew(SpewOps, "Execution mode: %s", ForkJoinModeString(mode_));
switch (mode_) {
case ForkJoinModeNormal:
case ForkJoinModeCompile:
case ForkJoinModeBailout:
break;
case ForkJoinModeParallel:
case ForkJoinModeRecover:
// These two modes are used to check that every iteration can
// be executed in parallel. They expect compilation to have
// been done. But, when using gc zeal, it's possible that
// compiled scripts were collected.
if (ParallelTestsShouldPass(cx_) && worklist_.length() != 0) {
JS_ReportError(cx_, "ForkJoin: compilation required in par or bailout mode");
return ExecutionFatal;
}
break;
case NumForkJoinModes:
JS_NOT_REACHED("Invalid mode");
}
while (bailouts < MAX_BAILOUTS) {
for (uint32_t i = 0; i < slices; i++)
bailoutRecords_[i].reset(cx_);
if (compileForParallelExecution(&status) == RedLight)
return SpewEndOp(status);
JS_ASSERT(worklist_.length() == 0);
if (parallelExecution(&status) == RedLight)
return SpewEndOp(status);
if (recoverFromBailout(&status) == RedLight)
return SpewEndOp(status);
}
// After enough tries, just execute sequentially.
return SpewEndOp(sequentialExecution(true));
}
js::ParallelDo::TrafficLight
js::ParallelDo::enqueueInitialScript(ExecutionStatus *status)
{
// GreenLight: script successfully enqueued if necessary
// RedLight: fatal error or fell back to sequential
// The kernel should be a self-hosted function.
if (!fun_->is<JSFunction>())
return sequentialExecution(true, status);
RootedFunction callee(cx_, &fun_->as<JSFunction>());
if (!callee->isInterpreted() || !callee->isSelfHostedBuiltin())
return sequentialExecution(true, status);
// If the main script is already compiled, and we have no reason
// to suspect any of its callees are not compiled, then we can
// just skip the compilation step.
RootedScript script(cx_, callee->getOrCreateScript(cx_));
if (!script)
return RedLight;
if (script->hasParallelIonScript()) {
if (!script->parallelIonScript()->hasUncompiledCallTarget()) {
Spew(SpewOps, "Script %p:%s:%d already compiled, no uncompiled callees",
script.get(), script->filename(), script->lineno);
return GreenLight;
}
Spew(SpewOps, "Script %p:%s:%d already compiled, may have uncompiled callees",
script.get(), script->filename(), script->lineno);
}
// Otherwise, add to the worklist of scripts to process.
if (addToWorklist(script) == RedLight)
return fatalError(status);
return GreenLight;
}
js::ParallelDo::TrafficLight
js::ParallelDo::compileForParallelExecution(ExecutionStatus *status)
{
// GreenLight: all scripts compiled
// RedLight: fatal error or completed work via warmups or fallback
// This routine attempts to do whatever compilation is necessary
// to execute a single parallel attempt. When it returns, either
// (1) we have fallen back to sequential; (2) we have run enough
// warmup runs to complete all the work; or (3) we have compiled
// all scripts we think likely to be executed during a parallel
// execution.
RootedFunction fun(cx_);
RootedScript script(cx_);
// After 3 stalls, we stop waiting for a script to gather type
// info and move on with execution.
const uint32_t stallThreshold = 3;
// This loop continues to iterate until the full contents of
// `worklist` have been successfully compiled for parallel
// execution. The compilations themselves typically occur on
// helper threads. While we wait for the compilations to complete,
// or for sufficient type information to be gathered, we execute
// warmup iterations.
while (true) {
bool offMainThreadCompilationsInProgress = false;
bool gatheringTypeInformation = false;
// Walk over the worklist to check on the status of each entry.
for (uint32_t i = 0; i < worklist_.length(); i++) {
script = worklist_[i];
fun = script->function();
// No baseline script means no type information, hence we
// will not be able to compile very well. In such cases,
// we continue to run baseline iterations until either (1)
// the potential callee *has* a baseline script or (2) the
// potential callee's use count stops increasing,
// indicating that they are not in fact a callee.
if (!script->hasBaselineScript()) {
uint32_t previousUseCount = worklistData_[i].useCount;
uint32_t currentUseCount = script->getUseCount();
if (previousUseCount < currentUseCount) {
worklistData_[i].useCount = currentUseCount;
worklistData_[i].stallCount = 0;
gatheringTypeInformation = true;
Spew(SpewCompile,
"Script %p:%s:%d has no baseline script, "
"but use count grew from %d to %d",
script.get(), script->filename(), script->lineno,
previousUseCount, currentUseCount);
} else {
uint32_t stallCount = ++worklistData_[i].stallCount;
if (stallCount < stallThreshold) {
gatheringTypeInformation = true;
}
Spew(SpewCompile,
"Script %p:%s:%d has no baseline script, "
"and use count has %u stalls at %d",
script.get(), script->filename(), script->lineno,
stallCount, previousUseCount);
}
continue;
}
if (!script->hasParallelIonScript()) {
// Script has not yet been compiled. Attempt to compile it.
SpewBeginCompile(script);
MethodStatus mstatus = jit::CanEnterInParallel(cx_, script);
SpewEndCompile(mstatus);
switch (mstatus) {
case Method_Error:
return fatalError(status);
case Method_CantCompile:
Spew(SpewCompile,
"Script %p:%s:%d cannot be compiled, "
"falling back to sequential execution",
script.get(), script->filename(), script->lineno);
return sequentialExecution(true, status);
case Method_Skipped:
// A "skipped" result either means that we are compiling
// in parallel OR some other transient error occurred.
if (script->isParallelIonCompilingOffThread()) {
Spew(SpewCompile,
"Script %p:%s:%d compiling off-thread",
script.get(), script->filename(), script->lineno);
offMainThreadCompilationsInProgress = true;
continue;
}
return sequentialExecution(false, status);
case Method_Compiled:
Spew(SpewCompile,
"Script %p:%s:%d compiled",
script.get(), script->filename(), script->lineno);
JS_ASSERT(script->hasParallelIonScript());
break;
}
}
// At this point, either the script was already compiled
// or we just compiled it. Check whether its "uncompiled
// call target" flag is set and add the targets to our
// worklist if so. Clear the flag after that, since we
// will be compiling the call targets.
JS_ASSERT(script->hasParallelIonScript());
if (appendCallTargetsToWorklist(i, status) == RedLight)
return RedLight;
}
// If there is compilation occurring in a helper thread, then
// run a warmup iterations in the main thread while we wait.
// There is a chance that this warmup will finish all the work
// we have to do, so we should stop then, unless we are in
// compile mode, in which case we'll continue to block.
//
// Note that even in compile mode, we can't block *forever*:
// - OMTC compiles will finish;
// - no work is being done, so use counts on not-yet-baselined
// scripts will not increase.
if (offMainThreadCompilationsInProgress || gatheringTypeInformation) {
bool stopIfComplete = (mode_ != ForkJoinModeCompile);
if (warmupExecution(stopIfComplete, status) == RedLight)
return RedLight;
continue;
}
// All compilations are complete. However, be careful: it is
// possible that a garbage collection occurred while we were
// iterating and caused some of the scripts we thought we had
// compiled to be collected. In that case, we will just have
// to begin again.
bool allScriptsPresent = true;
for (uint32_t i = 0; i < worklist_.length(); i++) {
if (!worklist_[i]->hasParallelIonScript()) {
if (worklistData_[i].stallCount < stallThreshold) {
worklistData_[i].reset();
allScriptsPresent = false;
Spew(SpewCompile,
"Script %p:%s:%d is not stalled, "
"but no parallel ion script found, "
"restarting loop",
script.get(), script->filename(), script->lineno);
}
}
}
if (allScriptsPresent)
break;
}
Spew(SpewCompile, "Compilation complete (final worklist length %d)",
worklist_.length());
// At this point, all scripts and their transitive callees are
// either stalled (indicating they are unlikely to be called) or
// in a compiled state. Therefore we can clear the
// "hasUncompiledCallTarget" flag on them and then clear the
// worklist.
for (uint32_t i = 0; i < worklist_.length(); i++) {
if (worklist_[i]->hasParallelIonScript()) {
JS_ASSERT(worklistData_[i].calleesEnqueued);
worklist_[i]->parallelIonScript()->clearHasUncompiledCallTarget();
} else {
JS_ASSERT(worklistData_[i].stallCount >= stallThreshold);
}
}
worklist_.clear();
worklistData_.clear();
return GreenLight;
}
js::ParallelDo::TrafficLight
js::ParallelDo::appendCallTargetsToWorklist(uint32_t index,
ExecutionStatus *status)
{
// GreenLight: call targets appended
// RedLight: fatal error or completed work via warmups or fallback
JS_ASSERT(worklist_[index]->hasParallelIonScript());
// Check whether we have already enqueued the targets for
// this entry and avoid doing it again if so.
if (worklistData_[index].calleesEnqueued)
return GreenLight;
worklistData_[index].calleesEnqueued = true;
// Iterate through the callees and enqueue them.
RootedScript target(cx_);
IonScript *ion = worklist_[index]->parallelIonScript();
for (uint32_t i = 0; i < ion->callTargetEntries(); i++) {
target = ion->callTargetList()[i];
parallel::Spew(parallel::SpewCompile,
"Adding call target %s:%u",
target->filename(), target->lineno);
if (appendCallTargetToWorklist(target, status) == RedLight)
return RedLight;
}
return GreenLight;
}
js::ParallelDo::TrafficLight
js::ParallelDo::appendCallTargetToWorklist(HandleScript script,
ExecutionStatus *status)
{
// GreenLight: call target appended if necessary
// RedLight: fatal error or completed work via warmups or fallback
JS_ASSERT(script);
// Fallback to sequential if disabled.
if (!script->canParallelIonCompile()) {
Spew(SpewCompile, "Skipping %p:%s:%u, canParallelIonCompile() is false",
script.get(), script->filename(), script->lineno);
return sequentialExecution(true, status);
}
if (script->hasParallelIonScript()) {
// Skip if the code is expected to result in a bailout.
if (script->parallelIonScript()->bailoutExpected()) {
Spew(SpewCompile, "Skipping %p:%s:%u, bailout expected",
script.get(), script->filename(), script->lineno);
return sequentialExecution(false, status);
}
}
if (!addToWorklist(script))
return fatalError(status);
return GreenLight;
}
bool
js::ParallelDo::addToWorklist(HandleScript script)
{
for (uint32_t i = 0; i < worklist_.length(); i++) {
if (worklist_[i] == script) {
Spew(SpewCompile, "Skipping %p:%s:%u, already in worklist",
script.get(), script->filename(), script->lineno);
return true;
}
}
Spew(SpewCompile, "Enqueued %p:%s:%u",
script.get(), script->filename(), script->lineno);
// Note that we add all possibly compilable functions to the worklist,
// even if they're already compiled. This is so that we can return
// Method_Compiled and not Method_Skipped if we have a worklist full of
// already-compiled functions.
if (!worklist_.append(script))
return false;
// we have not yet enqueued the callees of this script
if (!worklistData_.append(WorklistData()))
return false;
worklistData_[worklistData_.length() - 1].reset();
return true;
}
js::ParallelDo::TrafficLight
js::ParallelDo::sequentialExecution(bool disqualified, ExecutionStatus *status)
{
// RedLight: fatal error or completed work
*status = sequentialExecution(disqualified);
return RedLight;
}
ExecutionStatus
js::ParallelDo::sequentialExecution(bool disqualified)
{
// XXX use disqualified to set parallelIon to ION_DISABLED_SCRIPT?
Spew(SpewOps, "Executing sequential execution (disqualified=%d).",
disqualified);
bool complete = false;
RootedValue funVal(cx_, ObjectValue(*fun_));
if (!ExecuteSequentially(cx_, funVal, &complete))
return ExecutionFatal;
// When invoked without the warmup flag set to true, the kernel
// function OUGHT to complete successfully, barring an exception.
JS_ASSERT(complete);
return ExecutionSequential;
}
js::ParallelDo::TrafficLight
js::ParallelDo::fatalError(ExecutionStatus *status)
{
// RedLight: fatal error
*status = ExecutionFatal;
return RedLight;
}
static const char *
BailoutExplanation(ParallelBailoutCause cause)
{
switch (cause) {
case ParallelBailoutNone:
return "no particular reason";
case ParallelBailoutCompilationSkipped:
return "compilation failed (method skipped)";
case ParallelBailoutCompilationFailure:
return "compilation failed";
case ParallelBailoutInterrupt:
return "interrupted";
case ParallelBailoutFailedIC:
return "at runtime, the behavior changed, invalidating compiled code (IC update)";
case ParallelBailoutHeapBusy:
return "heap busy flag set during interrupt";
case ParallelBailoutMainScriptNotPresent:
return "main script not present";
case ParallelBailoutCalledToUncompiledScript:
return "called to uncompiled script";
case ParallelBailoutIllegalWrite:
return "illegal write";
case ParallelBailoutAccessToIntrinsic:
return "access to intrinsic";
case ParallelBailoutOverRecursed:
return "over recursed";
case ParallelBailoutOutOfMemory:
return "out of memory";
case ParallelBailoutUnsupported:
return "unsupported";
case ParallelBailoutUnsupportedStringComparison:
return "unsupported string comparison";
case ParallelBailoutUnsupportedSparseArray:
return "unsupported sparse array";
case ParallelBailoutRequestedGC:
return "requested GC";
case ParallelBailoutRequestedZoneGC:
return "requested zone GC";
default:
return "no known reason";
}
}
void
js::ParallelDo::determineBailoutCause()
{
bailoutCause = ParallelBailoutNone;
for (uint32_t i = 0; i < bailoutRecords_.length(); i++) {
if (bailoutRecords_[i].cause == ParallelBailoutNone)
continue;
if (bailoutRecords_[i].cause == ParallelBailoutInterrupt)
continue;
bailoutCause = bailoutRecords_[i].cause;
const char *causeStr = BailoutExplanation(bailoutCause);
if (bailoutRecords_[i].depth) {
bailoutScript = bailoutRecords_[i].trace[0].script;
bailoutBytecode = bailoutRecords_[i].trace[0].bytecode;
const char *filename = bailoutScript->filename();
int line = JS_PCToLineNumber(cx_, bailoutScript, bailoutBytecode);
JS_ReportWarning(cx_, "Bailed out of parallel operation: %s at %s:%d",
causeStr, filename, line);
Spew(SpewBailouts, "Bailout from thread %d: cause %d at loc %s:%d",
i,
bailoutCause,
bailoutScript->filename(),
PCToLineNumber(bailoutScript, bailoutBytecode));
} else {
JS_ReportWarning(cx_, "Bailed out of parallel operation: %s",
causeStr);
Spew(SpewBailouts, "Bailout from thread %d: cause %d, unknown loc",
i,
bailoutCause);
}
}
}
bool
js::ParallelDo::invalidateBailedOutScripts()
{
Vector<types::RecompileInfo> invalid(cx_);
for (uint32_t i = 0; i < bailoutRecords_.length(); i++) {
RootedScript script(cx_, bailoutRecords_[i].topScript);
// No script to invalidate.
if (!script || !script->hasParallelIonScript())
continue;
Spew(SpewBailouts,
"Bailout from thread %d: cause %d, topScript %p:%s:%d",
i,
bailoutRecords_[i].cause,
script.get(), script->filename(), script->lineno);
switch (bailoutRecords_[i].cause) {
// An interrupt is not the fault of the script, so don't
// invalidate it.
case ParallelBailoutInterrupt: continue;
// An illegal write will not be made legal by invalidation.
case ParallelBailoutIllegalWrite: continue;
// For other cases, consider invalidation.
default: break;
}
// Already invalidated.
if (hasScript(invalid, script))
continue;
Spew(SpewBailouts, "Invalidating script %p:%s:%d due to cause %d",
script.get(), script->filename(), script->lineno,
bailoutRecords_[i].cause);
types::RecompileInfo co = script->parallelIonScript()->recompileInfo();
if (!invalid.append(co))
return false;
// any script that we have marked for invalidation will need
// to be recompiled
if (!addToWorklist(script))
return false;
}
Invalidate(cx_, invalid);
return true;
}
js::ParallelDo::TrafficLight
js::ParallelDo::warmupExecution(bool stopIfComplete,
ExecutionStatus *status)
{
// GreenLight: warmup succeeded, still more work to do
// RedLight: fatal error or warmup completed all work (check status)
Spew(SpewOps, "Executing warmup.");
AutoEnterWarmup warmup(cx_->runtime());
RootedValue funVal(cx_, ObjectValue(*fun_));
bool complete;
if (!ExecuteSequentially(cx_, funVal, &complete)) {
*status = ExecutionFatal;
return RedLight;
}
if (complete && stopIfComplete) {
Spew(SpewOps, "Warmup execution finished all the work.");
*status = ExecutionWarmup;
return RedLight;
}
return GreenLight;
}
class AutoEnterParallelSection
{
private:
JSContext *cx_;
uint8_t *prevIonTop_;
public:
AutoEnterParallelSection(JSContext *cx)
: cx_(cx),
prevIonTop_(cx->mainThread().ionTop)
{
// Note: we do not allow GC during parallel sections.
// Moreover, we do not wish to worry about making
// write barriers thread-safe. Therefore, we guarantee
// that there is no incremental GC in progress and force
// a minor GC to ensure no cross-generation pointers get
// created:
if (JS::IsIncrementalGCInProgress(cx->runtime())) {
JS::PrepareForIncrementalGC(cx->runtime());
JS::FinishIncrementalGC(cx->runtime(), JS::gcreason::API);
}
MinorGC(cx->runtime(), JS::gcreason::API);
cx->runtime()->gcHelperThread.waitBackgroundSweepEnd();
}
~AutoEnterParallelSection() {
cx_->mainThread().ionTop = prevIonTop_;
}
};
js::ParallelDo::TrafficLight
js::ParallelDo::parallelExecution(ExecutionStatus *status)
{
// GreenLight: bailout occurred, keep trying
// RedLight: fatal error or all work completed
// Recursive use of the ThreadPool is not supported. Right now we
// cannot get here because parallel code cannot invoke native
// functions such as ForkJoin().
JS_ASSERT(ForkJoinSlice::Current() == NULL);
AutoEnterParallelSection enter(cx_);
ThreadPool *threadPool = &cx_->runtime()->threadPool;
uint32_t numSlices = ForkJoinSlices(cx_);
RootedObject rootedFun(cx_, fun_);
ForkJoinShared shared(cx_, threadPool, rootedFun, numSlices, numSlices - 1,
&bailoutRecords_[0]);
if (!shared.init()) {
*status = ExecutionFatal;
return RedLight;
}
switch (shared.execute()) {
case TP_SUCCESS:
*status = ExecutionParallel;
return RedLight;
case TP_FATAL:
*status = ExecutionFatal;
return RedLight;
case TP_RETRY_SEQUENTIALLY:
case TP_RETRY_AFTER_GC:
break; // bailout
}
return GreenLight;
}
js::ParallelDo::TrafficLight
js::ParallelDo::recoverFromBailout(ExecutionStatus *status)
{
// GreenLight: bailout recovered, try to compile-and-run again
// RedLight: fatal error
bailouts += 1;
determineBailoutCause();
SpewBailout(bailouts, bailoutScript, bailoutBytecode, bailoutCause);
// After any bailout, we always scan over callee list of main
// function, if nothing else
RootedScript mainScript(cx_, fun_->as<JSFunction>().nonLazyScript());
if (!addToWorklist(mainScript))
return fatalError(status);
// Also invalidate and recompile any callees that were implicated
// by the bailout
if (!invalidateBailedOutScripts())
return fatalError(status);
if (warmupExecution(/*stopIfComplete:*/true, status) == RedLight)
return RedLight;
return GreenLight;
}
bool
js::ParallelDo::hasScript(Vector<types::RecompileInfo> &scripts, JSScript *script)
{
for (uint32_t i = 0; i < scripts.length(); i++) {
if (scripts[i] == script->parallelIonScript()->recompileInfo())
return true;
}
return false;
}
// Can only enter callees with a valid IonScript.
template <uint32_t maxArgc>
class ParallelIonInvoke
{
EnterIonCode enter_;
void *jitcode_;
void *calleeToken_;
Value argv_[maxArgc + 2];
uint32_t argc_;
public:
Value *args;
ParallelIonInvoke(JSCompartment *compartment,
HandleFunction callee,
uint32_t argc)
: argc_(argc),
args(argv_ + 2)
{
JS_ASSERT(argc <= maxArgc + 2);
// Set 'callee' and 'this'.
argv_[0] = ObjectValue(*callee);
argv_[1] = UndefinedValue();
// Find JIT code pointer.
IonScript *ion = callee->nonLazyScript()->parallelIonScript();
IonCode *code = ion->method();
jitcode_ = code->raw();
enter_ = compartment->ionCompartment()->enterJIT();
calleeToken_ = CalleeToParallelToken(callee);
}
bool invoke(PerThreadData *perThread) {
RootedValue result(perThread);
enter_(jitcode_, argc_ + 1, argv_ + 1, NULL, calleeToken_, NULL, 0, result.address());
return !result.isMagic();
}
};
/////////////////////////////////////////////////////////////////////////////
// ForkJoinShared
//
ForkJoinShared::ForkJoinShared(JSContext *cx,
ThreadPool *threadPool,
HandleObject fun,
uint32_t numSlices,
uint32_t uncompleted,
ParallelBailoutRecord *records)
: cx_(cx),
threadPool_(threadPool),
fun_(fun),
numSlices_(numSlices),
rendezvousEnd_(NULL),
cxLock_(NULL),
records_(records),
allocators_(cx),
uncompleted_(uncompleted),
blocked_(0),
rendezvousIndex_(0),
gcRequested_(false),
gcReason_(JS::gcreason::NUM_REASONS),
gcZone_(NULL),
abort_(false),
fatal_(false),
rendezvous_(false)
{
}
bool
ForkJoinShared::init()
{
// Create temporary arenas to hold the data allocated during the
// parallel code.
//
// Note: you might think (as I did, initially) that we could use
// compartment |Allocator| for the main thread. This is not true,
// because when executing parallel code we sometimes check what
// arena list an object is in to decide if it is writable. If we
// used the compartment |Allocator| for the main thread, then the
// main thread would be permitted to write to any object it wants.
if (!Monitor::init())
return false;
rendezvousEnd_ = PR_NewCondVar(lock_);
if (!rendezvousEnd_)
return false;
cxLock_ = PR_NewLock();
if (!cxLock_)
return false;
for (unsigned i = 0; i < numSlices_; i++) {
Allocator *allocator = cx_->new_<Allocator>(cx_->zone());
if (!allocator)
return false;
if (!allocators_.append(allocator)) {
js_delete(allocator);
return false;
}
}
return true;
}
ForkJoinShared::~ForkJoinShared()
{
if (rendezvousEnd_)
PR_DestroyCondVar(rendezvousEnd_);
PR_DestroyLock(cxLock_);
while (allocators_.length() > 0)
js_delete(allocators_.popCopy());
}
ParallelResult
ForkJoinShared::execute()
{
// Sometimes a GC request occurs *just before* we enter into the
// parallel section. Rather than enter into the parallel section
// and then abort, we just check here and abort early.
if (cx_->runtime()->interrupt)
return TP_RETRY_SEQUENTIALLY;
AutoLockMonitor lock(*this);
// Notify workers to start and execute one portion on this thread.
{
AutoUnlockMonitor unlock(*this);
if (!threadPool_->submitAll(cx_, this))
return TP_FATAL;
executeFromMainThread();
}
// Wait for workers to complete.
while (uncompleted_ > 0)
lock.wait();
transferArenasToCompartmentAndProcessGCRequests();
// Check if any of the workers failed.
if (abort_) {
if (fatal_)
return TP_FATAL;
else
return TP_RETRY_SEQUENTIALLY;
}
// Everything went swimmingly. Give yourself a pat on the back.
return TP_SUCCESS;
}
void
ForkJoinShared::transferArenasToCompartmentAndProcessGCRequests()
{
JSCompartment *comp = cx_->compartment();
for (unsigned i = 0; i < numSlices_; i++)
comp->adoptWorkerAllocator(allocators_[i]);
if (gcRequested_) {
if (!gcZone_)
TriggerGC(cx_->runtime(), gcReason_);
else
TriggerZoneGC(gcZone_, gcReason_);
gcRequested_ = false;
gcZone_ = NULL;
}
}
void
ForkJoinShared::executeFromWorker(uint32_t workerId, uintptr_t stackLimit)
{
JS_ASSERT(workerId < numSlices_ - 1);
PerThreadData thisThread(cx_->runtime());
TlsPerThreadData.set(&thisThread);
// Don't use setIonStackLimit() because that acquires the ionStackLimitLock, and the
// lock has not been initialized in these cases.
thisThread.ionStackLimit = stackLimit;
executePortion(&thisThread, workerId);
TlsPerThreadData.set(NULL);
AutoLockMonitor lock(*this);
uncompleted_ -= 1;
if (blocked_ == uncompleted_) {
// Signal the main thread that we have terminated. It will be either
// working, arranging a rendezvous, or waiting for workers to
// complete.
lock.notify();
}
}
void
ForkJoinShared::executeFromMainThread()
{
executePortion(&cx_->mainThread(), numSlices_ - 1);
}
void
ForkJoinShared::executePortion(PerThreadData *perThread,
uint32_t threadId)
{
// WARNING: This code runs ON THE PARALLEL WORKER THREAD.
// Therefore, it should NOT access `cx_` in any way!
Allocator *allocator = allocators_[threadId];
ForkJoinSlice slice(perThread, threadId, numSlices_, allocator,
this, &records_[threadId]);
AutoSetForkJoinSlice autoContext(&slice);
Spew(SpewOps, "Up");
// Make a new IonContext for the slice, which is needed if we need to
// re-enter the VM.
IonContext icx(cx_->compartment(), NULL);
JS_ASSERT(slice.bailoutRecord->topScript == NULL);
RootedObject fun(perThread, fun_);
JS_ASSERT(fun->is<JSFunction>());
RootedFunction callee(perThread, &fun->as<JSFunction>());
if (!callee->nonLazyScript()->hasParallelIonScript()) {
// Sometimes, particularly with GCZeal, the parallel ion
// script can be collected between starting the parallel
// op and reaching this point. In that case, we just fail
// and fallback.
Spew(SpewOps, "Down (Script no longer present)");
slice.bailoutRecord->setCause(ParallelBailoutMainScriptNotPresent,
NULL, NULL, NULL);
setAbortFlag(false);
} else {
ParallelIonInvoke<3> fii(cx_->compartment(), callee, 3);
fii.args[0] = Int32Value(slice.sliceId);
fii.args[1] = Int32Value(slice.numSlices);
fii.args[2] = BooleanValue(false);
bool ok = fii.invoke(perThread);
JS_ASSERT(ok == !slice.bailoutRecord->topScript);
if (!ok)
setAbortFlag(false);
}
Spew(SpewOps, "Down");
}
bool
ForkJoinShared::check(ForkJoinSlice &slice)
{
JS_ASSERT(cx_->runtime()->interrupt);
if (abort_)
return false;
if (slice.isMainThread()) {
JS_ASSERT(!cx_->runtime()->gcIsNeeded);
if (cx_->runtime()->interrupt) {
// The GC Needed flag should not be set during parallel
// execution. Instead, one of the requestGC() or
// requestZoneGC() methods should be invoked.
JS_ASSERT(!cx_->runtime()->gcIsNeeded);
// If interrupt is requested, bring worker threads to a halt,
// service the interrupt, then let them start back up again.
// AutoRendezvous autoRendezvous(slice);
// if (!js_HandleExecutionInterrupt(cx_))
// return setAbortFlag(true);
slice.bailoutRecord->setCause(ParallelBailoutInterrupt,
NULL, NULL, NULL);
setAbortFlag(false);
return false;
}
} else if (rendezvous_) {
joinRendezvous(slice);
}
return true;
}
void
ForkJoinShared::initiateRendezvous(ForkJoinSlice &slice)
{
// The rendezvous protocol is always initiated by the main thread. The
// main thread sets the rendezvous flag to true. Seeing this flag, other
// threads will invoke |joinRendezvous()|, which causes them to (1) read
// |rendezvousIndex| and (2) increment the |blocked| counter. Once the
// |blocked| counter is equal to |uncompleted|, all parallel threads have
// joined the rendezvous, and so the main thread is signaled. That will
// cause this function to return.
//
// Some subtle points:
//
// - Worker threads may potentially terminate their work before they see
// the rendezvous flag. In this case, they would decrement
// |uncompleted| rather than incrementing |blocked|. Either way, if the
// two variables become equal, the main thread will be notified
//
// - The |rendezvousIndex| counter is used to detect the case where the
// main thread signals the end of the rendezvous and then starts another
// rendezvous before the workers have a chance to exit. We circumvent
// this by having the workers read the |rendezvousIndex| counter as they
// enter the rendezvous, and then they only block until that counter is
// incremented. Another alternative would be for the main thread to
// block in |endRendezvous()| until all workers have exited, but that
// would be slower and involve unnecessary synchronization.
//
// Note that the main thread cannot ever get more than one rendezvous
// ahead of the workers, because it must wait for all of them to enter
// the rendezvous before it can end it, so the solution of using a
// counter is perfectly general and we need not fear rollover.
JS_ASSERT(slice.isMainThread());
JS_ASSERT(!rendezvous_ && blocked_ == 0);
JS_ASSERT(cx_->runtime()->interrupt);
AutoLockMonitor lock(*this);
// Signal other threads we want to start a rendezvous.
rendezvous_ = true;
// Wait until all the other threads blocked themselves.
while (blocked_ != uncompleted_)
lock.wait();
}
void
ForkJoinShared::joinRendezvous(ForkJoinSlice &slice)
{
JS_ASSERT(!slice.isMainThread());
JS_ASSERT(rendezvous_);
AutoLockMonitor lock(*this);
const uint32_t index = rendezvousIndex_;
blocked_ += 1;
// If we're the last to arrive, let the main thread know about it.
if (blocked_ == uncompleted_)
lock.notify();
// Wait until the main thread terminates the rendezvous. We use a
// separate condition variable here to distinguish between workers
// notifying the main thread that they have completed and the main
// thread notifying the workers to resume.
while (rendezvousIndex_ == index)
PR_WaitCondVar(rendezvousEnd_, PR_INTERVAL_NO_TIMEOUT);
}
void
ForkJoinShared::endRendezvous(ForkJoinSlice &slice)
{
JS_ASSERT(slice.isMainThread());
AutoLockMonitor lock(*this);
rendezvous_ = false;
blocked_ = 0;
rendezvousIndex_++;
// Signal other threads that rendezvous is over.
PR_NotifyAllCondVar(rendezvousEnd_);
}
void
ForkJoinShared::setAbortFlag(bool fatal)
{
AutoLockMonitor lock(*this);
abort_ = true;
fatal_ = fatal_ || fatal;
cx_->runtime()->triggerOperationCallback();
}
void
ForkJoinShared::requestGC(JS::gcreason::Reason reason)
{
AutoLockMonitor lock(*this);
gcZone_ = NULL;
gcReason_ = reason;
gcRequested_ = true;
}
void
ForkJoinShared::requestZoneGC(JS::Zone *zone, JS::gcreason::Reason reason)
{
AutoLockMonitor lock(*this);
if (gcRequested_ && gcZone_ != zone) {
// If a full GC has been requested, or a GC for another zone,
// issue a request for a full GC.
gcZone_ = NULL;
gcReason_ = reason;
gcRequested_ = true;
} else {
// Otherwise, just GC this zone.
gcZone_ = zone;
gcReason_ = reason;
gcRequested_ = true;
}
}
/////////////////////////////////////////////////////////////////////////////
// ForkJoinSlice
//
ForkJoinSlice::ForkJoinSlice(PerThreadData *perThreadData,
uint32_t sliceId, uint32_t numSlices,
Allocator *allocator, ForkJoinShared *shared,
ParallelBailoutRecord *bailoutRecord)
: ThreadSafeContext(shared->runtime(), perThreadData, Context_ForkJoin),
sliceId(sliceId),
numSlices(numSlices),
bailoutRecord(bailoutRecord),
shared(shared),
acquiredContext_(false)
{
/*
* Unsafely set the zone. This is used to track malloc counters and to
* trigger GCs and is otherwise not thread-safe to access.
*/
zone_ = shared->zone();
allocator_ = allocator;
}
bool
ForkJoinSlice::isMainThread() const
{
return perThreadData == &shared->runtime()->mainThread;
}
JSRuntime *
ForkJoinSlice::runtime()
{
return shared->runtime();
}
JSContext *
ForkJoinSlice::acquireContext()
{
JSContext *cx = shared->acquireContext();
JS_ASSERT(!acquiredContext_);
acquiredContext_ = true;
return cx;
}
void
ForkJoinSlice::releaseContext()
{
JS_ASSERT(acquiredContext_);
acquiredContext_ = false;
return shared->releaseContext();
}
bool
ForkJoinSlice::hasAcquiredContext() const
{
return acquiredContext_;
}
bool
ForkJoinSlice::check()
{
if (runtime()->interrupt)
return shared->check(*this);
else
return true;
}
bool
ForkJoinSlice::InitializeTLS()
{
if (!TLSInitialized) {
TLSInitialized = true;
PRStatus status = PR_NewThreadPrivateIndex(&ThreadPrivateIndex, NULL);
return status == PR_SUCCESS;
}
return true;
}
void
ForkJoinSlice::requestGC(JS::gcreason::Reason reason)
{
shared->requestGC(reason);
bailoutRecord->setCause(ParallelBailoutRequestedGC,
NULL, NULL, NULL);
shared->setAbortFlag(false);
}
void
ForkJoinSlice::requestZoneGC(JS::Zone *zone, JS::gcreason::Reason reason)
{
shared->requestZoneGC(zone, reason);
bailoutRecord->setCause(ParallelBailoutRequestedZoneGC,
NULL, NULL, NULL);
shared->setAbortFlag(false);
}
/////////////////////////////////////////////////////////////////////////////
uint32_t
js::ForkJoinSlices(JSContext *cx)
{
// Parallel workers plus this main thread.
return cx->runtime()->threadPool.numWorkers() + 1;
}
//////////////////////////////////////////////////////////////////////////////
// ParallelBailoutRecord
void
js::ParallelBailoutRecord::init(JSContext *cx)
{
reset(cx);
}
void
js::ParallelBailoutRecord::reset(JSContext *cx)
{
topScript = NULL;
cause = ParallelBailoutNone;
depth = 0;
}
void
js::ParallelBailoutRecord::setCause(ParallelBailoutCause cause,
JSScript *outermostScript,
JSScript *currentScript,
jsbytecode *currentPc)
{
JS_ASSERT_IF(outermostScript, currentScript);
JS_ASSERT_IF(outermostScript, outermostScript->hasParallelIonScript());
JS_ASSERT_IF(currentScript, outermostScript);
JS_ASSERT_IF(!currentScript, !currentPc);
this->cause = cause;
if (outermostScript) {
this->topScript = outermostScript;
}
if (currentScript) {
addTrace(currentScript, currentPc);
}
}
void
js::ParallelBailoutRecord::addTrace(JSScript *script,
jsbytecode *pc)
{
// Ideally, this should never occur, because we should always have
// a script when we invoke setCause, but I havent' fully
// refactored things to that point yet:
if (topScript == NULL && script != NULL)
topScript = script;
if (depth < MaxDepth) {
trace[depth].script = script;
trace[depth].bytecode = pc;
depth += 1;
}
}
//////////////////////////////////////////////////////////////////////////////
//
// Debug spew
//
#ifdef DEBUG
static const char *
ExecutionStatusToString(ExecutionStatus status)
{
switch (status) {
case ExecutionFatal:
return "fatal";
case ExecutionSequential:
return "sequential";
case ExecutionWarmup:
return "warmup";
case ExecutionParallel:
return "parallel";
}
return "(unknown status)";
}
static const char *
MethodStatusToString(MethodStatus status)
{
switch (status) {
case Method_Error:
return "error";
case Method_CantCompile:
return "can't compile";
case Method_Skipped:
return "skipped";
case Method_Compiled:
return "compiled";
}
return "(unknown status)";
}
static const size_t BufferSize = 4096;
class ParallelSpewer
{
uint32_t depth;
bool colorable;
bool active[NumSpewChannels];
const char *color(const char *colorCode) {
if (!colorable)
return "";
return colorCode;
}
const char *reset() { return color("\x1b[0m"); }
const char *bold() { return color("\x1b[1m"); }
const char *red() { return color("\x1b[31m"); }
const char *green() { return color("\x1b[32m"); }
const char *yellow() { return color("\x1b[33m"); }
const char *cyan() { return color("\x1b[36m"); }
const char *sliceColor(uint32_t id) {
static const char *colors[] = {
"\x1b[7m\x1b[31m", "\x1b[7m\x1b[32m", "\x1b[7m\x1b[33m",
"\x1b[7m\x1b[34m", "\x1b[7m\x1b[35m", "\x1b[7m\x1b[36m",
"\x1b[7m\x1b[37m",
"\x1b[31m", "\x1b[32m", "\x1b[33m",
"\x1b[34m", "\x1b[35m", "\x1b[36m",
"\x1b[37m"
};
return color(colors[id % 14]);
}
public:
ParallelSpewer()
: depth(0)
{
const char *env;
mozilla::PodArrayZero(active);
env = getenv("PAFLAGS");
if (env) {
if (strstr(env, "ops"))
active[SpewOps] = true;
if (strstr(env, "compile"))
active[SpewCompile] = true;
if (strstr(env, "bailouts"))
active[SpewBailouts] = true;
if (strstr(env, "full")) {
for (uint32_t i = 0; i < NumSpewChannels; i++)
active[i] = true;
}
}
env = getenv("TERM");
if (env) {
if (strcmp(env, "xterm-color") == 0 || strcmp(env, "xterm-256color") == 0)
colorable = true;
}
}
bool isActive(SpewChannel channel) {
return active[channel];
}
void spewVA(SpewChannel channel, const char *fmt, va_list ap) {
if (!active[channel])
return;
// Print into a buffer first so we use one fprintf, which usually
// doesn't get interrupted when running with multiple threads.
char buf[BufferSize];
if (ForkJoinSlice *slice = ForkJoinSlice::Current()) {
PR_snprintf(buf, BufferSize, "[%sParallel:%u%s] ",
sliceColor(slice->sliceId), slice->sliceId, reset());
} else {
PR_snprintf(buf, BufferSize, "[Parallel:M] ");
}
for (uint32_t i = 0; i < depth; i++)
PR_snprintf(buf + strlen(buf), BufferSize, " ");
PR_vsnprintf(buf + strlen(buf), BufferSize, fmt, ap);
PR_snprintf(buf + strlen(buf), BufferSize, "\n");
fprintf(stderr, "%s", buf);
}
void spew(SpewChannel channel, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
spewVA(channel, fmt, ap);
va_end(ap);
}
void beginOp(JSContext *cx, const char *name) {
if (!active[SpewOps])
return;
if (cx) {
jsbytecode *pc;
JSScript *script = cx->currentScript(&pc);
if (script && pc) {
NonBuiltinScriptFrameIter iter(cx);
if (iter.done()) {
spew(SpewOps, "%sBEGIN %s%s (%s:%u)", bold(), name, reset(),
script->filename(), PCToLineNumber(script, pc));
} else {
spew(SpewOps, "%sBEGIN %s%s (%s:%u -> %s:%u)", bold(), name, reset(),
iter.script()->filename(), PCToLineNumber(iter.script(), iter.pc()),
script->filename(), PCToLineNumber(script, pc));
}
} else {
spew(SpewOps, "%sBEGIN %s%s", bold(), name, reset());
}
} else {
spew(SpewOps, "%sBEGIN %s%s", bold(), name, reset());
}
depth++;
}
void endOp(ExecutionStatus status) {
if (!active[SpewOps])
return;
JS_ASSERT(depth > 0);
depth--;
const char *statusColor;
switch (status) {
case ExecutionFatal:
statusColor = red();
break;
case ExecutionSequential:
statusColor = yellow();
break;
case ExecutionParallel:
statusColor = green();
break;
default:
statusColor = reset();
break;
}
spew(SpewOps, "%sEND %s%s%s", bold(),
statusColor, ExecutionStatusToString(status), reset());
}
void bailout(uint32_t count, HandleScript script,
jsbytecode *pc, ParallelBailoutCause cause) {
if (!active[SpewOps])
return;
const char *filename = "";
unsigned line=0, column=0;
if (script) {
line = PCToLineNumber(script, pc, &column);
filename = script->filename();
}
spew(SpewOps, "%s%sBAILOUT %d%s: %d at %s:%d:%d", bold(), yellow(), count, reset(), cause, filename, line, column);
}
void beginCompile(HandleScript script) {
if (!active[SpewCompile])
return;
spew(SpewCompile, "COMPILE %p:%s:%u", script.get(), script->filename(), script->lineno);
depth++;
}
void endCompile(MethodStatus status) {
if (!active[SpewCompile])
return;
JS_ASSERT(depth > 0);
depth--;
const char *statusColor;
switch (status) {
case Method_Error:
case Method_CantCompile:
statusColor = red();
break;
case Method_Skipped:
statusColor = yellow();
break;
case Method_Compiled:
statusColor = green();
break;
default:
statusColor = reset();
break;
}
spew(SpewCompile, "END %s%s%s", statusColor, MethodStatusToString(status), reset());
}
void spewMIR(MDefinition *mir, const char *fmt, va_list ap) {
if (!active[SpewCompile])
return;
char buf[BufferSize];
PR_vsnprintf(buf, BufferSize, fmt, ap);
JSScript *script = mir->block()->info().script();
spew(SpewCompile, "%s%s%s: %s (%s:%u)", cyan(), mir->opName(), reset(), buf,
script->filename(), PCToLineNumber(script, mir->trackedPc()));
}
void spewBailoutIR(uint32_t bblockId, uint32_t lirId,
const char *lir, const char *mir, JSScript *script, jsbytecode *pc) {
if (!active[SpewBailouts])
return;
// If we didn't bail from a LIR/MIR but from a propagated parallel
// bailout, don't bother printing anything since we've printed it
// elsewhere.
if (mir && script) {
spew(SpewBailouts, "%sBailout%s: %s / %s%s%s (block %d lir %d) (%s:%u)", yellow(), reset(),
lir, cyan(), mir, reset(),
bblockId, lirId,
script->filename(), PCToLineNumber(script, pc));
}
}
};
// Singleton instance of the spewer.
static ParallelSpewer spewer;
bool
parallel::SpewEnabled(SpewChannel channel)
{
return spewer.isActive(channel);
}
void
parallel::Spew(SpewChannel channel, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
spewer.spewVA(channel, fmt, ap);
va_end(ap);
}
void
parallel::SpewBeginOp(JSContext *cx, const char *name)
{
spewer.beginOp(cx, name);
}
ExecutionStatus
parallel::SpewEndOp(ExecutionStatus status)
{
spewer.endOp(status);
return status;
}
void
parallel::SpewBailout(uint32_t count, HandleScript script,
jsbytecode *pc, ParallelBailoutCause cause)
{
spewer.bailout(count, script, pc, cause);
}
void
parallel::SpewBeginCompile(HandleScript script)
{
spewer.beginCompile(script);
}
MethodStatus
parallel::SpewEndCompile(MethodStatus status)
{
spewer.endCompile(status);
return status;
}
void
parallel::SpewMIR(MDefinition *mir, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
spewer.spewMIR(mir, fmt, ap);
va_end(ap);
}
void
parallel::SpewBailoutIR(uint32_t bblockId, uint32_t lirId,
const char *lir, const char *mir,
JSScript *script, jsbytecode *pc)
{
spewer.spewBailoutIR(bblockId, lirId, lir, mir, script, pc);
}
#endif // DEBUG
bool
js::InSequentialOrExclusiveParallelSection()
{
return !InParallelSection() || ForkJoinSlice::Current()->hasAcquiredContext();
}
bool
js::ParallelTestsShouldPass(JSContext *cx)
{
return jit::IsIonEnabled(cx) &&
jit::IsBaselineEnabled(cx) &&
!jit::js_IonOptions.eagerCompilation &&
jit::js_IonOptions.baselineUsesBeforeCompile != 0 &&
cx->runtime()->gcZeal() == 0;
}
#endif // JS_THREADSAFE && JS_ION