Bug 862922 - Track causes and locations of parallel bailouts and issue a somewhat obscure warning r=jandem

This commit is contained in:
Nicholas D. Matsakis 2013-05-09 12:20:55 -04:00
parent 7a055e0974
commit e61ede7e17
13 changed files with 659 additions and 225 deletions

View File

@ -1262,25 +1262,17 @@ function CheckParallel(mode) {
if (!mode || !ParallelTestsShouldPass())
return null;
return function(bailouts) {
return function(result, bailouts, causes) {
if (!("expect" in mode) || mode.expect === "any") {
return; // Ignore result when unspecified or unimportant.
} else if (mode.expect === "mixed" && result !== "disqualified") {
return; // "mixed" means that it may bailout, may succeed
} else if (result === mode.expect) {
return;
}
var result;
if (bailouts === 0)
result = "success";
else if (bailouts === global.Infinity)
result = "disqualified";
else
result = "bailout";
if (mode.expect === "mixed") {
if (result === "disqualified")
ThrowError(JSMSG_WRONG_VALUE, mode.expect, result);
} else if (result !== mode.expect) {
ThrowError(JSMSG_WRONG_VALUE, mode.expect, result);
}
ThrowError(JSMSG_WRONG_VALUE, mode.expect,
result+":"+bailouts+":"+causes);
};
}

View File

@ -691,7 +691,7 @@ CodeGenerator::visitParLambda(LParLambda *lir)
JS_ASSERT(scopeChainReg != resultReg);
emitParAllocateGCThing(resultReg, parSliceReg, tempReg1, tempReg2, fun);
emitParAllocateGCThing(lir, resultReg, parSliceReg, tempReg1, tempReg2, fun);
emitLambdaInit(resultReg, scopeChainReg, fun);
return true;
}
@ -1107,12 +1107,12 @@ CodeGenerator::visitParWriteGuard(LParWriteGuard *lir)
masm.passABIArg(ToRegister(lir->object()));
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, ParWriteGuard));
Label *bail;
if (!ensureOutOfLineParallelAbort(&bail))
OutOfLineParallelAbort *bail = oolParallelAbort(ParallelBailoutIllegalWrite, lir);
if (!bail)
return false;
masm.branchIfFalseBool(ReturnReg, bail);
// branch to the OOL failure code if false is returned
masm.branchIfFalseBool(ReturnReg, bail->entry());
return true;
}
@ -1365,11 +1365,11 @@ CodeGenerator::visitCallGetIntrinsicValue(LCallGetIntrinsicValue *lir)
}
case ParallelExecution: {
Label *bail;
if (!ensureOutOfLineParallelAbort(&bail))
OutOfLineParallelAbort *bail = oolParallelAbort(ParallelBailoutAccessToIntrinsic, lir);
if (!bail)
return false;
masm.jump(bail);
masm.jump(bail->entry());
return true;
}
@ -1485,7 +1485,7 @@ CodeGenerator::visitCallGeneric(LCallGeneric *call)
break;
case ParallelExecution:
if (!emitParCallToUncompiledScript(calleereg))
if (!emitParCallToUncompiledScript(call, calleereg))
return false;
break;
}
@ -1501,7 +1501,7 @@ CodeGenerator::visitCallGeneric(LCallGeneric *call)
masm.bind(&notPrimitive);
}
if (!checkForParallelBailout())
if (!checkForParallelBailout(call))
return false;
dropArguments(call->numStackArgs() + 1);
@ -1511,17 +1511,18 @@ CodeGenerator::visitCallGeneric(LCallGeneric *call)
// Generates a call to ParCallToUncompiledScript() and then bails out.
// |calleeReg| should contain the JSFunction*.
bool
CodeGenerator::emitParCallToUncompiledScript(Register calleeReg)
CodeGenerator::emitParCallToUncompiledScript(LInstruction *lir,
Register calleeReg)
{
Label *bail;
if (!ensureOutOfLineParallelAbort(&bail))
OutOfLineCode *bail = oolParallelAbort(ParallelBailoutCalledToUncompiledScript, lir);
if (!bail)
return false;
masm.movePtr(calleeReg, CallTempReg0);
masm.setupUnalignedABICall(1, CallTempReg1);
masm.passABIArg(CallTempReg0);
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, ParCallToUncompiledScript));
masm.jump(bail);
masm.jump(bail->entry());
return true;
}
@ -1605,14 +1606,14 @@ CodeGenerator::visitCallKnown(LCallKnown *call)
break;
case ParallelExecution:
if (!emitParCallToUncompiledScript(calleereg))
if (!emitParCallToUncompiledScript(call, calleereg))
return false;
break;
}
masm.bind(&end);
if (!checkForParallelBailout())
if (!checkForParallelBailout(call))
return false;
// If the return value of the constructing function is Primitive,
@ -1629,17 +1630,17 @@ CodeGenerator::visitCallKnown(LCallKnown *call)
}
bool
CodeGenerator::checkForParallelBailout()
CodeGenerator::checkForParallelBailout(LInstruction *lir)
{
// In parallel mode, if we call another ion-compiled function and
// it returns JS_ION_ERROR, that indicates a bailout that we have
// to propagate up the stack.
ExecutionMode executionMode = gen->info().executionMode();
if (executionMode == ParallelExecution) {
Label *bail;
if (!ensureOutOfLineParallelAbort(&bail))
OutOfLinePropagateParallelAbort *bail = oolPropagateParallelAbort(lir);
if (!bail)
return false;
masm.branchTestMagic(Assembler::Equal, JSReturnOperand, bail);
masm.branchTestMagic(Assembler::Equal, JSReturnOperand, bail->entry());
}
return true;
}
@ -2124,8 +2125,8 @@ CodeGenerator::visitParCheckOverRecursed(LParCheckOverRecursed *lir)
bool
CodeGenerator::visitParCheckOverRecursedFailure(ParCheckOverRecursedFailure *ool)
{
Label *bail;
if (!ensureOutOfLineParallelAbort(&bail))
OutOfLinePropagateParallelAbort *bail = oolPropagateParallelAbort(ool->lir());
if (!bail)
return false;
// Avoid saving/restoring the temp register since we will put the
@ -2143,7 +2144,7 @@ CodeGenerator::visitParCheckOverRecursedFailure(ParCheckOverRecursedFailure *ool
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, ParCheckOverRecursed));
masm.movePtr(ReturnReg, tempReg);
masm.PopRegsInMask(saveSet);
masm.branchIfFalseBool(tempReg, bail);
masm.branchIfFalseBool(tempReg, bail->entry());
masm.jump(ool->rejoin());
return true;
@ -2186,8 +2187,8 @@ CodeGenerator::visitParCheckInterrupt(LParCheckInterrupt *lir)
bool
CodeGenerator::visitOutOfLineParCheckInterrupt(OutOfLineParCheckInterrupt *ool)
{
Label *bail;
if (!ensureOutOfLineParallelAbort(&bail))
OutOfLinePropagateParallelAbort *bail = oolPropagateParallelAbort(ool->lir);
if (!bail)
return false;
// Avoid saving/restoring the temp register since we will put the
@ -2205,7 +2206,7 @@ CodeGenerator::visitOutOfLineParCheckInterrupt(OutOfLineParCheckInterrupt *ool)
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, ParCheckInterrupt));
masm.movePtr(ReturnReg, tempReg);
masm.PopRegsInMask(saveSet);
masm.branchIfFalseBool(tempReg, bail);
masm.branchIfFalseBool(tempReg, bail->entry());
masm.jump(ool->rejoin());
return true;
@ -2711,7 +2712,7 @@ CodeGenerator::visitParNewCallObject(LParNewCallObject *lir)
Register tempReg2 = ToRegister(lir->getTemp1());
JSObject *templateObj = lir->mir()->templateObj();
emitParAllocateGCThing(resultReg, parSliceReg, tempReg1, tempReg2, templateObj);
emitParAllocateGCThing(lir, resultReg, parSliceReg, tempReg1, tempReg2, templateObj);
// NB: !lir->slots()->isRegister() implies that there is no slots
// array at all, and the memory is already zeroed when copying
@ -2738,7 +2739,7 @@ CodeGenerator::visitParNewDenseArray(LParNewDenseArray *lir)
// Allocate the array into tempReg2. Don't use resultReg because it
// may alias parSliceReg etc.
emitParAllocateGCThing(tempReg2, parSliceReg, tempReg0, tempReg1, templateObj);
emitParAllocateGCThing(lir, tempReg2, parSliceReg, tempReg0, tempReg1, templateObj);
// Invoke a C helper to allocate the elements. For convenience,
// this helper also returns the array back to us, or NULL, which
@ -2756,10 +2757,10 @@ CodeGenerator::visitParNewDenseArray(LParNewDenseArray *lir)
Register resultReg = ToRegister(lir->output());
JS_ASSERT(resultReg == ReturnReg);
Label *bail;
if (!ensureOutOfLineParallelAbort(&bail))
OutOfLineParallelAbort *bail = oolParallelAbort(ParallelBailoutOutOfMemory, lir);
if (!bail)
return false;
masm.branchTestPtr(Assembler::Zero, resultReg, resultReg, bail);
masm.branchTestPtr(Assembler::Zero, resultReg, resultReg, bail->entry());
return true;
}
@ -2801,19 +2802,19 @@ CodeGenerator::visitParNew(LParNew *lir)
Register tempReg1 = ToRegister(lir->getTemp0());
Register tempReg2 = ToRegister(lir->getTemp1());
JSObject *templateObject = lir->mir()->templateObject();
emitParAllocateGCThing(objReg, parSliceReg, tempReg1, tempReg2,
templateObject);
emitParAllocateGCThing(lir, objReg, parSliceReg, tempReg1, tempReg2, templateObject);
return true;
}
class OutOfLineParNewGCThing : public OutOfLineCodeBase<CodeGenerator>
{
public:
LInstruction *lir;
gc::AllocKind allocKind;
Register objReg;
OutOfLineParNewGCThing(gc::AllocKind allocKind, Register objReg)
: allocKind(allocKind), objReg(objReg)
OutOfLineParNewGCThing(LInstruction *lir, gc::AllocKind allocKind, Register objReg)
: lir(lir), allocKind(allocKind), objReg(objReg)
{}
bool accept(CodeGenerator *codegen) {
@ -2822,14 +2823,15 @@ public:
};
bool
CodeGenerator::emitParAllocateGCThing(const Register &objReg,
CodeGenerator::emitParAllocateGCThing(LInstruction *lir,
const Register &objReg,
const Register &parSliceReg,
const Register &tempReg1,
const Register &tempReg2,
JSObject *templateObj)
{
gc::AllocKind allocKind = templateObj->tenuredGetAllocKind();
OutOfLineParNewGCThing *ool = new OutOfLineParNewGCThing(allocKind, objReg);
OutOfLineParNewGCThing *ool = new OutOfLineParNewGCThing(lir, allocKind, objReg);
if (!ool || !addOutOfLineCode(ool))
return false;
@ -2869,10 +2871,10 @@ CodeGenerator::visitOutOfLineParNewGCThing(OutOfLineParNewGCThing *ool)
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, ParNewGCThing));
masm.movePtr(ReturnReg, ool->objReg);
masm.PopRegsInMask(saveSet);
Label *bail;
if (!ensureOutOfLineParallelAbort(&bail))
OutOfLineParallelAbort *bail = oolParallelAbort(ParallelBailoutOutOfMemory, ool->lir);
if (!bail)
return false;
masm.branchTestPtr(Assembler::Zero, ool->objReg, ool->objReg, bail);
masm.branchTestPtr(Assembler::Zero, ool->objReg, ool->objReg, bail->entry());
masm.jump(ool->rejoin());
return true;
}
@ -2880,10 +2882,10 @@ CodeGenerator::visitOutOfLineParNewGCThing(OutOfLineParNewGCThing *ool)
bool
CodeGenerator::visitParBailout(LParBailout *lir)
{
Label *bail;
if (!ensureOutOfLineParallelAbort(&bail))
OutOfLineParallelAbort *bail = oolParallelAbort(ParallelBailoutUnsupported, lir);
if (!bail)
return false;
masm.jump(bail);
masm.jump(bail->entry());
return true;
}
@ -4335,10 +4337,6 @@ CodeGenerator::visitOutOfLineStoreElementHole(OutOfLineStoreElementHole *ool)
return true;
case ParallelExecution:
Label *bail;
if (!ensureOutOfLineParallelAbort(&bail))
return false;
//////////////////////////////////////////////////////////////
// If the problem is that we do not have sufficient capacity,
// try to reallocate the elements array and then branch back
@ -4349,6 +4347,11 @@ CodeGenerator::visitOutOfLineStoreElementHole(OutOfLineStoreElementHole *ool)
// pass in a Value to a C function!).
masm.bind(&indexWouldExceedCapacity);
OutOfLineParallelAbort *bail = oolParallelAbort(
ParallelBailoutOutOfMemory, ins);
if (!bail)
return false;
// The use of registers here is somewhat subtle. We need to
// save and restore the volatile registers but we also need to
// preserve the ReturnReg. Normally we'd just add a constraint
@ -4375,7 +4378,7 @@ CodeGenerator::visitOutOfLineStoreElementHole(OutOfLineStoreElementHole *ool)
masm.freeStack(sizeof(ParPushArgs));
masm.movePtr(ReturnReg, object);
masm.PopRegsInMask(saveSet);
masm.branchTestPtr(Assembler::Zero, object, object, bail);
masm.branchTestPtr(Assembler::Zero, object, object, bail->entry());
masm.jump(ool->rejoin());
//////////////////////////////////////////////////////////////
@ -4384,7 +4387,11 @@ CodeGenerator::visitOutOfLineStoreElementHole(OutOfLineStoreElementHole *ool)
// sparse array, and since we don't want to think about that
// case right now, we just bail out.
masm.bind(&indexNotInitLen);
masm.jump(bail);
OutOfLineParallelAbort *bail1 =
oolParallelAbort(ParallelBailoutUnsupportedSparseArray, ins);
if (!bail1)
return false;
masm.jump(bail1->entry());
return true;
}
@ -6423,16 +6430,19 @@ CodeGenerator::visitFunctionBoundary(LFunctionBoundary *lir)
bool
CodeGenerator::visitOutOfLineParallelAbort(OutOfLineParallelAbort *ool)
{
// Subtle: Do not pass the script associated with `current`, which
// is often an inlined script or something like that, but rather the
// "outermost" JSScript.
ParallelBailoutCause cause = ool->cause();
jsbytecode *bytecode = ool->bytecode();
MIRGraph &graph = current->mir()->graph();
MBasicBlock *entryBlock = graph.entryBlock();
masm.move32(Imm32(cause), CallTempReg0);
loadOutermostJSScript(CallTempReg1);
loadJSScriptForBlock(ool->basicBlock(), CallTempReg2);
masm.movePtr(ImmWord((void *) bytecode), CallTempReg3);
masm.movePtr(ImmWord((void *) entryBlock->info().script()), CallTempReg0);
masm.setupUnalignedABICall(1, CallTempReg1);
masm.setupUnalignedABICall(4, CallTempReg4);
masm.passABIArg(CallTempReg0);
masm.passABIArg(CallTempReg1);
masm.passABIArg(CallTempReg2);
masm.passABIArg(CallTempReg3);
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, ParallelAbort));
masm.moveValue(MagicValue(JS_ION_ERROR), JSReturnOperand);
@ -6462,6 +6472,44 @@ CodeGenerator::visitIsCallable(LIsCallable *ins)
return true;
}
void
CodeGenerator::loadOutermostJSScript(Register reg)
{
// The "outermost" JSScript means the script that we are compiling
// basically; this is not always the script associated with the
// current basic block, which might be an inlined script.
MIRGraph &graph = current->mir()->graph();
MBasicBlock *entryBlock = graph.entryBlock();
masm.movePtr(ImmGCPtr(entryBlock->info().script()), reg);
}
void
CodeGenerator::loadJSScriptForBlock(MBasicBlock *block, Register reg)
{
// The current JSScript means the script for the current
// basic block. This may be an inlined script.
JSScript *script = block->info().script();
masm.movePtr(ImmGCPtr(script), reg);
}
bool
CodeGenerator::visitOutOfLinePropagateParallelAbort(OutOfLinePropagateParallelAbort *ool)
{
loadOutermostJSScript(CallTempReg0);
loadJSScriptForBlock(ool->lir()->mirRaw()->block(), CallTempReg1);
masm.setupUnalignedABICall(2, CallTempReg2);
masm.passABIArg(CallTempReg0);
masm.passABIArg(CallTempReg1);
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, PropagateParallelAbort));
masm.moveValue(MagicValue(JS_ION_ERROR), JSReturnOperand);
masm.jump(returnLabel_);
return true;
}
bool
CodeGenerator::visitAsmJSCall(LAsmJSCall *ins)
{

View File

@ -248,6 +248,9 @@ class CodeGenerator : public CodeGeneratorSpecific
bool visitOutOfLineParNewGCThing(OutOfLineParNewGCThing *ool);
bool visitOutOfLineParallelAbort(OutOfLineParallelAbort *ool);
bool visitOutOfLinePropagateParallelAbort(OutOfLinePropagateParallelAbort *ool);
void loadJSScriptForBlock(MBasicBlock *block, Register reg);
void loadOutermostJSScript(Register reg);
// Inline caches visitors.
bool visitOutOfLineCache(OutOfLineUpdateCache *ool);
@ -281,17 +284,19 @@ class CodeGenerator : public CodeGeneratorSpecific
bool addGetPropertyCache(LInstruction *ins, RegisterSet liveRegs, Register objReg,
PropertyName *name, TypedOrValueRegister output,
bool allowGetters);
bool checkForParallelBailout(LInstruction *lir);
bool checkForParallelBailout();
bool generateBranchV(const ValueOperand &value, Label *ifTrue, Label *ifFalse, FloatRegister fr);
bool emitParAllocateGCThing(const Register &objReg,
bool emitParAllocateGCThing(LInstruction *lir,
const Register &objReg,
const Register &threadContextReg,
const Register &tempReg1,
const Register &tempReg2,
JSObject *templateObj);
bool emitParCallToUncompiledScript(Register calleeReg);
bool emitParCallToUncompiledScript(LInstruction *lir,
Register calleeReg);
void emitLambdaInit(const Register &resultReg,
const Register &scopeChainReg,

View File

@ -538,8 +538,17 @@ HandleParallelFailure(ResumeFromException *rfe)
while (!iter.isEntry()) {
parallel::Spew(parallel::SpewBailouts, "Bailing from VM reentry");
if (!slice->abortedScript && iter.isScripted())
slice->abortedScript = iter.script();
if (iter.isScripted()) {
slice->bailoutRecord->setCause(ParallelBailoutFailedIC,
iter.script(), iter.script(), NULL);
break;
}
++iter;
}
while (!iter.isEntry()) {
if (iter.isScripted())
PropagateParallelAbort(iter.script(), iter.script());
++iter;
}

View File

@ -118,7 +118,7 @@ LIRGenerator::visitParCheckOverRecursed(MParCheckOverRecursed *ins)
LParCheckOverRecursed *lir = new LParCheckOverRecursed(
useRegister(ins->parSlice()),
temp());
if (!add(lir))
if (!add(lir, ins))
return false;
if (!assignSafepoint(lir, ins))
return false;
@ -293,8 +293,7 @@ LIRGenerator::visitPassArg(MPassArg *arg)
// Known types can move constant types and/or payloads.
LStackArgT *stack = new LStackArgT(argslot, useRegisterOrConstant(opd));
stack->setMir(arg);
return add(stack);
return add(stack, arg);
}
bool
@ -1640,9 +1639,11 @@ LIRGenerator::visitParSlice(MParSlice *ins)
bool
LIRGenerator::visitParWriteGuard(MParWriteGuard *ins)
{
return add(new LParWriteGuard(useFixed(ins->parSlice(), CallTempReg0),
useFixed(ins->object(), CallTempReg1),
tempFixed(CallTempReg2)));
LParWriteGuard *lir = new LParWriteGuard(useFixed(ins->parSlice(), CallTempReg0),
useFixed(ins->object(), CallTempReg1),
tempFixed(CallTempReg2));
lir->setMir(ins);
return add(lir, ins);
}
bool
@ -1651,7 +1652,7 @@ LIRGenerator::visitParCheckInterrupt(MParCheckInterrupt *ins)
LParCheckInterrupt *lir = new LParCheckInterrupt(
useRegister(ins->parSlice()),
temp());
if (!add(lir))
if (!add(lir, ins))
return false;
if (!assignSafepoint(lir, ins))
return false;
@ -2610,8 +2611,7 @@ LIRGenerator::visitAsmJSCall(MAsmJSCall *ins)
LInstruction *lir = new LAsmJSCall(args, ins->numOperands());
if (ins->type() == MIRType_None) {
lir->setMir(ins);
return add(lir);
return add(lir, ins);
}
return defineReturn(lir, ins);
}

View File

@ -119,24 +119,30 @@ bool
ion::ParCheckOverRecursed(ForkJoinSlice *slice)
{
JS_ASSERT(ForkJoinSlice::Current() == slice);
int stackDummy_;
// When an interrupt is triggered, we currently overwrite the
// stack limit with a sentinel value that brings us here.
// When an interrupt is triggered, the main thread stack limit is
// overwritten with a sentinel value that brings us here.
// Therefore, we must check whether this is really a stack overrun
// and, if not, check whether an interrupt is needed.
if (slice->isMainThread()) {
int stackDummy_;
if (!JS_CHECK_STACK_SIZE(js::GetNativeStackLimit(slice->runtime()), &stackDummy_))
return false;
return ParCheckInterrupt(slice);
} else {
// FIXME---we don't ovewrite the stack limit for worker
// threads, which means that technically they can recurse
// forever---or at least a long time---without ever checking
// the interrupt. it also means that if we get here on a
// worker thread, this is a real stack overrun!
//
// When not on the main thread, we don't overwrite the stack
// limit, but we do still call into this routine if the interrupt
// flag is set, so we still need to double check.
uintptr_t realStackLimit;
if (slice->isMainThread())
realStackLimit = js::GetNativeStackLimit(slice->runtime());
else
realStackLimit = slice->perThreadData->ionStackLimit;
if (!JS_CHECK_STACK_SIZE(realStackLimit, &stackDummy_)) {
slice->bailoutRecord->setCause(ParallelBailoutOverRecursed,
NULL, NULL, NULL);
return false;
}
return ParCheckInterrupt(slice);
}
bool
@ -144,8 +150,14 @@ ion::ParCheckInterrupt(ForkJoinSlice *slice)
{
JS_ASSERT(ForkJoinSlice::Current() == slice);
bool result = slice->check();
if (!result)
if (!result) {
// Do not set the cause here. Either it was set by this
// thread already by some code that then triggered an abort,
// or else we are just picking up an abort from some other
// thread. Either way we have nothing useful to contribute so
// we might as well leave our bailout case unset.
return false;
}
return true;
}
@ -362,23 +374,48 @@ js::ion::ParStringsUnequal(ForkJoinSlice *slice, HandleString v1, HandleString v
}
void
ion::ParallelAbort(JSScript *script)
ion::ParallelAbort(ParallelBailoutCause cause,
JSScript *outermostScript,
JSScript *currentScript,
jsbytecode *bytecode)
{
// Spew before asserts to help with diagnosing failures.
Spew(SpewBailouts,
"Parallel abort with cause %d in %p:%s:%d "
"(%p:%s:%d at line %d)",
cause,
outermostScript, outermostScript->filename(), outermostScript->lineno,
currentScript, currentScript->filename(), currentScript->lineno);
JS_ASSERT(InParallelSection());
JS_ASSERT(outermostScript != NULL);
JS_ASSERT(currentScript != NULL);
JS_ASSERT(outermostScript->hasParallelIonScript());
ForkJoinSlice *slice = ForkJoinSlice::Current();
Spew(SpewBailouts, "Parallel abort in %p:%s:%d (hasParallelIonScript:%d)",
script, script->filename(), script->lineno,
script->hasParallelIonScript());
JS_ASSERT(slice->bailoutRecord->depth == 0);
slice->bailoutRecord->setCause(cause, outermostScript,
currentScript, bytecode);
}
// Otherwise what the heck are we executing?
JS_ASSERT(script->hasParallelIonScript());
void
ion::PropagateParallelAbort(JSScript *outermostScript,
JSScript *currentScript)
{
Spew(SpewBailouts,
"Propagate parallel abort via %p:%s:%d (%p:%s:%d)",
outermostScript, outermostScript->filename(), outermostScript->lineno,
currentScript, currentScript->filename(), currentScript->lineno);
if (!slice->abortedScript)
slice->abortedScript = script;
else
script->parallelIonScript()->setHasInvalidatedCallTarget();
JS_ASSERT(InParallelSection());
JS_ASSERT(outermostScript->hasParallelIonScript());
outermostScript->parallelIonScript()->setHasInvalidatedCallTarget();
ForkJoinSlice *slice = ForkJoinSlice::Current();
if (currentScript)
slice->bailoutRecord->addTrace(currentScript, NULL);
}
void

View File

@ -55,7 +55,13 @@ ParallelResult ParGreaterThanOrEqual(ForkJoinSlice *slice, MutableHandleValue v1
ParallelResult ParStringsEqual(ForkJoinSlice *slice, HandleString v1, HandleString v2, JSBool *);
ParallelResult ParStringsUnequal(ForkJoinSlice *slice, HandleString v1, HandleString v2, JSBool *);
void ParallelAbort(JSScript *script);
void ParallelAbort(ParallelBailoutCause cause,
JSScript *outermostScript,
JSScript *currentScript,
jsbytecode *bytecode);
void PropagateParallelAbort(JSScript *outermostScript,
JSScript *currentScript);
void TraceLIR(uint32_t bblock, uint32_t lir, uint32_t execModeInt,
const char *lirOpName, const char *mirOpName,

View File

@ -205,11 +205,11 @@ CodeGeneratorARM::bailoutFrom(Label *label, LSnapshot *snapshot)
CompileInfo &info = snapshot->mir()->block()->info();
switch (info.executionMode()) {
case ParallelExecution: {
// In parallel mode, make no attempt to recover, just signal an error.
Label *ool;
if (!ensureOutOfLineParallelAbort(&ool))
return false;
masm.retarget(label, ool);
// in parallel mode, make no attempt to recover, just signal an error.
OutOfLineParallelAbort *ool = oolParallelAbort(ParallelBailoutUnsupported,
snapshot->mir()->block(),
snapshot->mir()->pc());
masm.retarget(label, ool->entry());
return true;
}
case SequentialExecution:

View File

@ -35,7 +35,6 @@ CodeGeneratorShared::ensureMasm(MacroAssembler *masmArg)
CodeGeneratorShared::CodeGeneratorShared(MIRGenerator *gen, LIRGraph *graph, MacroAssembler *masmArg)
: oolIns(NULL),
oolParallelAbort_(NULL),
maybeMasm_(),
masm(ensureMasm(masmArg)),
gen(gen),
@ -541,17 +540,40 @@ CodeGeneratorShared::markArgumentSlots(LSafepoint *safepoint)
return true;
}
bool
CodeGeneratorShared::ensureOutOfLineParallelAbort(Label **result)
OutOfLineParallelAbort *
CodeGeneratorShared::oolParallelAbort(ParallelBailoutCause cause,
MBasicBlock *basicBlock,
jsbytecode *bytecode)
{
if (!oolParallelAbort_) {
oolParallelAbort_ = new OutOfLineParallelAbort();
if (!addOutOfLineCode(oolParallelAbort_))
return false;
}
OutOfLineParallelAbort *ool = new OutOfLineParallelAbort(cause, basicBlock, bytecode);
if (!ool || !addOutOfLineCode(ool))
return NULL;
return ool;
}
*result = oolParallelAbort_->entry();
return true;
OutOfLineParallelAbort *
CodeGeneratorShared::oolParallelAbort(ParallelBailoutCause cause,
LInstruction *lir)
{
MDefinition *mir = lir->mirRaw();
MBasicBlock *block = mir->block();
jsbytecode *pc = mir->trackedPc();
if (!pc) {
if (lir->snapshot())
pc = lir->snapshot()->mir()->pc();
else
pc = block->pc();
}
return oolParallelAbort(cause, block, pc);
}
OutOfLinePropagateParallelAbort *
CodeGeneratorShared::oolPropagateParallelAbort(LInstruction *lir)
{
OutOfLinePropagateParallelAbort *ool = new OutOfLinePropagateParallelAbort(lir);
if (!ool || !addOutOfLineCode(ool))
return NULL;
return ool;
}
bool
@ -561,6 +583,13 @@ OutOfLineParallelAbort::generate(CodeGeneratorShared *codegen)
return codegen->visitOutOfLineParallelAbort(this);
}
bool
OutOfLinePropagateParallelAbort::generate(CodeGeneratorShared *codegen)
{
codegen->callTraceLIR(0xDEADBEEF, NULL, "ParallelBailout");
return codegen->visitOutOfLinePropagateParallelAbort(this);
}
bool
CodeGeneratorShared::callTraceLIR(uint32_t blockIndex, LInstruction *lir,
const char *bailoutName)

View File

@ -26,6 +26,7 @@ class CodeGenerator;
class MacroAssembler;
class IonCache;
class OutOfLineParallelAbort;
class OutOfLinePropagateParallelAbort;
template <class ArgSeq, class StoreOutputTo>
class OutOfLineCallVM;
@ -36,7 +37,6 @@ class CodeGeneratorShared : public LInstructionVisitor
{
js::Vector<OutOfLineCode *, 0, SystemAllocPolicy> outOfLineCode_;
OutOfLineCode *oolIns;
OutOfLineParallelAbort *oolParallelAbort_;
MacroAssembler &ensureMasm(MacroAssembler *masm);
mozilla::Maybe<MacroAssembler> maybeMasm_;
@ -344,13 +344,28 @@ class CodeGeneratorShared : public LInstructionVisitor
bool visitOutOfLineTruncateSlow(OutOfLineTruncateSlow *ool);
public:
// When compiling parallel code, all bailouts just abort funnel to
// this same point and hence abort execution altogether:
virtual bool visitOutOfLineParallelAbort(OutOfLineParallelAbort *ool) = 0;
bool callTraceLIR(uint32_t blockIndex, LInstruction *lir, const char *bailoutName = NULL);
protected:
bool ensureOutOfLineParallelAbort(Label **result);
// Parallel aborts:
//
// Parallel aborts work somewhat differently from sequential
// bailouts. When an abort occurs, we first invoke
// ParReportBailout() and then we return JS_ION_ERROR. Each
// call on the stack will check for this error return and
// propagate it upwards until the C++ code that invoked the ion
// code is reached.
//
// The snapshot that is provided to `oolParallelAbort` is currently
// only used for error reporting, so that we can provide feedback
// to the user about which instruction aborted and (perhaps) why.
OutOfLineParallelAbort *oolParallelAbort(ParallelBailoutCause cause,
MBasicBlock *basicBlock,
jsbytecode *bytecode);
OutOfLineParallelAbort *oolParallelAbort(ParallelBailoutCause cause,
LInstruction *lir);
OutOfLinePropagateParallelAbort *oolPropagateParallelAbort(LInstruction *lir);
virtual bool visitOutOfLineParallelAbort(OutOfLineParallelAbort *ool) = 0;
virtual bool visitOutOfLinePropagateParallelAbort(OutOfLinePropagateParallelAbort *ool) = 0;
};
// An out-of-line path is generated at the end of the function.
@ -592,14 +607,52 @@ CodeGeneratorShared::visitOutOfLineCallVM(OutOfLineCallVM<ArgSeq, StoreOutputTo>
return true;
}
// An out-of-line parallel abort thunk.
// Initiate a parallel abort. The snapshot is used to record the
// cause.
class OutOfLineParallelAbort : public OutOfLineCode
{
private:
ParallelBailoutCause cause_;
MBasicBlock *basicBlock_;
jsbytecode *bytecode_;
public:
OutOfLineParallelAbort()
OutOfLineParallelAbort(ParallelBailoutCause cause,
MBasicBlock *basicBlock,
jsbytecode *bytecode)
: cause_(cause),
basicBlock_(basicBlock),
bytecode_(bytecode)
{ }
ParallelBailoutCause cause() {
return cause_;
}
MBasicBlock *basicBlock() {
return basicBlock_;
}
jsbytecode *bytecode() {
return bytecode_;
}
bool generate(CodeGeneratorShared *codegen);
};
// Used when some callee has aborted.
class OutOfLinePropagateParallelAbort : public OutOfLineCode
{
private:
LInstruction *lir_;
public:
OutOfLinePropagateParallelAbort(LInstruction *lir)
: lir_(lir)
{ }
LInstruction *lir() { return lir_; }
bool generate(CodeGeneratorShared *codegen);
};

View File

@ -283,11 +283,11 @@ CodeGeneratorX86Shared::bailout(const T &binder, LSnapshot *snapshot)
CompileInfo &info = snapshot->mir()->block()->info();
switch (info.executionMode()) {
case ParallelExecution: {
// In parallel mode, make no attempt to recover, just signal an error.
Label *ool;
if (!ensureOutOfLineParallelAbort(&ool))
return false;
binder(masm, ool);
// in parallel mode, make no attempt to recover, just signal an error.
OutOfLineParallelAbort *ool = oolParallelAbort(ParallelBailoutUnsupported,
snapshot->mir()->block(),
snapshot->mir()->pc());
binder(masm, ool->entry());
return true;
}
case SequentialExecution:

View File

@ -109,6 +109,22 @@ 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");
}
#endif // !JS_THREADSAFE || !JS_ION
///////////////////////////////////////////////////////////////////////////
@ -158,7 +174,11 @@ class ParallelDo
// For tests, make sure to keep this in sync with minItemsTestingThreshold.
const static uint32_t MAX_BAILOUTS = 3;
uint32_t bailouts;
AutoScriptVector pendingInvalidations_;
// Information about the bailout:
ParallelBailoutCause bailoutCause;
RootedScript bailoutScript;
jsbytecode *bailoutBytecode;
ParallelDo(JSContext *cx, HandleObject fun);
ExecutionStatus apply();
@ -166,11 +186,13 @@ class ParallelDo
private:
JSContext *cx_;
HeapPtrObject fun_;
Vector<ParallelBailoutRecord, 16> bailoutRecords;
inline bool executeSequentially();
MethodStatus compileForParallelExecution();
ExecutionStatus disqualifyFromParallelExecution();
void determineBailoutCause();
bool invalidateBailedOutScripts();
bool warmupForParallelExecution();
ParallelResult executeInParallel();
@ -190,7 +212,7 @@ class ForkJoinShared : public TaskExecutor, public Monitor
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.
AutoScriptVector &pendingInvalidations_; // From ParallelDo
ParallelBailoutRecord *const records_; // Bailout records for each slice
/////////////////////////////////////////////////////////////////////////
// Per-thread arenas
@ -258,7 +280,7 @@ class ForkJoinShared : public TaskExecutor, public Monitor
HandleObject fun,
uint32_t numSlices,
uint32_t uncompleted,
AutoScriptVector &pendingInvalidations);
ParallelBailoutRecord *records);
~ForkJoinShared();
bool init();
@ -353,14 +375,25 @@ js::ForkJoin(JSContext *cx, CallArgs &args)
RootedObject feedback(cx, &args[1].toObject());
if (feedback && feedback->isFunction()) {
InvokeArgsGuard feedbackArgs;
if (!cx->stack.pushInvokeArgs(cx, 1, &feedbackArgs))
if (!cx->stack.pushInvokeArgs(cx, 3, &feedbackArgs))
return false;
const char *resultString;
switch (status) {
case ExecutionParallel:
resultString = (op.bailouts == 0 ? "success" : "bailout");
break;
case ExecutionFatal:
case ExecutionSequential:
resultString = "disqualified";
break;
}
feedbackArgs.setCallee(ObjectValue(*feedback));
feedbackArgs.setThis(UndefinedValue());
if (status == ExecutionParallel)
feedbackArgs[0].setInt32(op.bailouts);
else
feedbackArgs[0] = cx->runtime->positiveInfinityValue;
feedbackArgs[0].setString(JS_NewStringCopyZ(cx, resultString));
feedbackArgs[1].setInt32(op.bailouts);
feedbackArgs[2].setInt32(op.bailoutCause);
if (!Invoke(cx, feedbackArgs))
return false;
}
@ -371,9 +404,12 @@ js::ForkJoin(JSContext *cx, CallArgs &args)
js::ParallelDo::ParallelDo(JSContext *cx, HandleObject fun)
: bailouts(0),
pendingInvalidations_(cx),
bailoutCause(ParallelBailoutNone),
bailoutScript(cx),
bailoutBytecode(NULL),
cx_(cx),
fun_(fun)
fun_(fun),
bailoutRecords(cx)
{ }
ExecutionStatus
@ -381,15 +417,23 @@ js::ParallelDo::apply()
{
SpewBeginOp(cx_, "ParallelDo");
uint32_t slices = ForkJoinSlices(cx_);
if (!ion::IsEnabled(cx_))
return SpewEndOp(disqualifyFromParallelExecution());
if (!pendingInvalidations_.resize(ForkJoinSlices(cx_)))
if (!bailoutRecords.resize(slices))
return SpewEndOp(ExecutionFatal);
for (uint32_t i = 0; i < slices; i++)
bailoutRecords[i].init(cx_);
// Try to execute in parallel. If a bailout occurs, re-warmup
// and then try again. Repeat this a few times.
while (bailouts < MAX_BAILOUTS) {
for (uint32_t i = 0; i < slices; i++)
bailoutRecords[i].reset(cx_);
MethodStatus status = compileForParallelExecution();
if (status == Method_Error)
return SpewEndOp(ExecutionFatal);
@ -414,8 +458,9 @@ js::ParallelDo::apply()
}
bailouts += 1;
determineBailoutCause();
SpewBailout(bailouts);
SpewBailout(bailouts, bailoutScript, bailoutBytecode, bailoutCause);
if (!invalidateBailedOutScripts())
return SpewEndOp(ExecutionFatal);
@ -493,6 +538,73 @@ js::ParallelDo::disqualifyFromParallelExecution()
return ExecutionSequential;
}
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";
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);
} else {
JS_ReportWarning(cx_, "Bailed out of parallel operation: %s",
causeStr);
}
}
}
bool
js::ParallelDo::invalidateBailedOutScripts()
{
@ -501,19 +613,35 @@ js::ParallelDo::invalidateBailedOutScripts()
// Sometimes the script is collected or invalidated already,
// for example when a full GC runs at an inconvenient time.
if (!script->hasParallelIonScript()) {
JS_ASSERT(hasNoPendingInvalidations());
return true;
}
Vector<types::RecompileInfo> invalid(cx_);
for (uint32_t i = 0; i < pendingInvalidations_.length(); i++) {
JSScript *script = pendingInvalidations_[i];
if (script && !hasScript(invalid, script)) {
JS_ASSERT(script->hasParallelIonScript());
if (!invalid.append(script->parallelIonScript()->recompileInfo()))
return false;
for (uint32_t i = 0; i < bailoutRecords.length(); i++) {
JSScript *script = bailoutRecords[i].topScript;
// No script to invalidate.
if (!script || !script->hasParallelIonScript())
continue;
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;
}
pendingInvalidations_[i] = NULL;
// Already invalidated.
if (hasScript(invalid, script))
continue;
if (!invalid.append(script->parallelIonScript()->recompileInfo()))
return false;
}
Invalidate(cx_, invalid);
return true;
@ -568,8 +696,7 @@ js::ParallelDo::executeInParallel()
uint32_t numSlices = ForkJoinSlices(cx_);
RootedObject rootedFun(cx_, fun_);
ForkJoinShared shared(cx_, threadPool, rootedFun, numSlices, numSlices - 1,
pendingInvalidations_);
ForkJoinShared shared(cx_, threadPool, rootedFun, numSlices, numSlices - 1, &bailoutRecords[0]);
if (!shared.init())
return TP_RETRY_SEQUENTIALLY;
@ -586,15 +713,6 @@ js::ParallelDo::hasScript(Vector<types::RecompileInfo> &scripts, JSScript *scrip
return false;
}
bool
js::ParallelDo::hasNoPendingInvalidations() {
for (uint32_t i = 0; i < pendingInvalidations_.length(); i++) {
if (pendingInvalidations_[i] != NULL)
return false;
}
return true;
}
// Can only enter callees with a valid IonScript.
template <uint32_t maxArgc>
class ParallelIonInvoke
@ -644,14 +762,14 @@ ForkJoinShared::ForkJoinShared(JSContext *cx,
HandleObject fun,
uint32_t numSlices,
uint32_t uncompleted,
AutoScriptVector &pendingInvalidations)
ParallelBailoutRecord *records)
: cx_(cx),
threadPool_(threadPool),
fun_(fun),
numSlices_(numSlices),
rendezvousEnd_(NULL),
cxLock_(NULL),
pendingInvalidations_(pendingInvalidations),
records_(records),
allocators_(cx),
uncompleted_(uncompleted),
blocked_(0),
@ -805,7 +923,8 @@ ForkJoinShared::executePortion(PerThreadData *perThread,
// Therefore, it should NOT access `cx_` in any way!
Allocator *allocator = allocators_[threadId];
ForkJoinSlice slice(perThread, threadId, numSlices_, allocator, this);
ForkJoinSlice slice(perThread, threadId, numSlices_, allocator,
this, &records_[threadId]);
AutoSetForkJoinSlice autoContext(&slice);
Spew(SpewOps, "Up");
@ -814,7 +933,7 @@ ForkJoinShared::executePortion(PerThreadData *perThread,
// re-enter the VM.
IonContext icx(cx_->compartment, NULL);
JS_ASSERT(pendingInvalidations_[slice.sliceId] == NULL);
JS_ASSERT(slice.bailoutRecord->topScript == NULL);
RootedObject fun(perThread, fun_);
JS_ASSERT(fun->isFunction());
@ -825,6 +944,8 @@ ForkJoinShared::executePortion(PerThreadData *perThread,
// 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);
@ -834,15 +955,9 @@ ForkJoinShared::executePortion(PerThreadData *perThread,
fii.args[2] = BooleanValue(false);
bool ok = fii.invoke(perThread);
JS_ASSERT(ok == !slice.abortedScript);
if (!ok) {
JSScript *script = slice.abortedScript;
Spew(SpewBailouts, "Aborted script: %p (hasParallelIonScript? %d)",
script, script->hasParallelIonScript());
JS_ASSERT(script->hasParallelIonScript());
pendingInvalidations_[slice.sliceId] = script;
JS_ASSERT(ok == !slice.bailoutRecord->topScript);
if (!ok)
setAbortFlag(false);
}
}
Spew(SpewOps, "Down");
@ -1008,12 +1123,13 @@ ForkJoinShared::requestZoneGC(JS::Zone *zone, JS::gcreason::Reason reason)
ForkJoinSlice::ForkJoinSlice(PerThreadData *perThreadData,
uint32_t sliceId, uint32_t numSlices,
Allocator *allocator, ForkJoinShared *shared)
Allocator *allocator, ForkJoinShared *shared,
ParallelBailoutRecord *bailoutRecord)
: perThreadData(perThreadData),
sliceId(sliceId),
numSlices(numSlices),
allocator(allocator),
abortedScript(NULL),
bailoutRecord(bailoutRecord),
shared(shared)
{ }
@ -1084,7 +1200,61 @@ js::ForkJoinSlices(JSContext *cx)
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;
}
}
//////////////////////////////////////////////////////////////////////////////
@ -1273,11 +1443,19 @@ class ParallelSpewer
statusColor, ExecutionStatusToString(status), reset());
}
void bailout(uint32_t count) {
void bailout(uint32_t count, HandleScript script,
jsbytecode *pc, ParallelBailoutCause cause) {
if (!active[SpewOps])
return;
spew(SpewOps, "%s%sBAILOUT %d%s", bold(), yellow(), count, reset());
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) {
@ -1376,9 +1554,10 @@ parallel::SpewEndOp(ExecutionStatus status)
}
void
parallel::SpewBailout(uint32_t count)
parallel::SpewBailout(uint32_t count, HandleScript script,
jsbytecode *pc, ParallelBailoutCause cause)
{
spewer.bailout(count);
spewer.bailout(count, script, pc, cause);
}
void

View File

@ -35,10 +35,10 @@
// ThreadPool.h---by default, N is the number of cores on the
// computer).
//
// Typically there will be one call to |parallel()| from each worker thread,
// but that is not something you should rely upon---if we implement
// work-stealing, for example, then it could be that a single worker thread
// winds up handling multiple slices.
// Typically, each of the N slices will execute from a different
// worker thread, but that is not something you should rely upon---if
// we implement work-stealing, for example, then it could be that a
// single worker thread winds up handling multiple slices.
//
// The second argument, |feedback|, is an optional callback that will
// receiver information about how execution proceeded. This is
@ -112,36 +112,38 @@
// parallelization and just invoke |func()| N times in a row (once
// for each worker) but with |warmup| set to false.
//
// Operation callback
// ------------------
// Operation callback:
//
// During parallel execution, you should periodically invoke |slice.check()|,
// which will handle the operation callback. If the operation callback is
// necessary, |slice.check()| will arrange a rendezvous---that is, as each
// active worker invokes |check()|, it will come to a halt until everyone is
// blocked (Stop The World). At this point, we perform the callback on the
// main thread, and then resume execution. If a worker thread terminates
// before calling |check()|, that's fine too. We assume that you do not do
// unbounded work without invoking |check()|.
// During parallel execution, |slice.check()| must be periodically
// invoked to check for the operation callback. This is automatically
// done by the ion-generated code. If the operation callback is
// necessary, |slice.check()| will arrange a rendezvous---that is, as
// each active worker invokes |check()|, it will come to a halt until
// everyone is blocked (Stop The World). At this point, we perform
// the callback on the main thread, and then resume execution. If a
// worker thread terminates before calling |check()|, that's fine too.
// We assume that you do not do unbounded work without invoking
// |check()|.
//
// Sequential Fallback:
// Bailout tracing and recording:
//
// It is assumed that anyone using this API must be prepared for a sequential
// fallback. Therefore, the |ExecuteForkJoinOp()| returns a status code
// indicating whether a fatal error occurred (in which case you should just
// stop) or whether you should retry the operation, but executing
// sequentially. An example of where the fallback would be useful is if the
// parallel code encountered an unexpected path that cannot safely be executed
// in parallel (writes to shared state, say).
// When a bailout occurs, we record a bit of state so that we can
// recover with grace. Each |ForkJoinSlice| has a pointer to a
// |ParallelBailoutRecord| pre-allocated for this purpose. This
// structure is used to record the cause of the bailout, the JSScript
// which was executing, as well as the location in the source where
// the bailout occurred (in principle, we can record a full stack
// trace, but right now we only record the top-most frame). Note that
// the error location might not be in the same JSScript as the one
// which was executing due to inlining.
//
// Garbage collection and allocation:
//
// Code which executes on these parallel threads must be very careful
// with respect to garbage collection and allocation. Currently, we
// do not permit GC to occur when executing in parallel. Furthermore,
// the typical allocation paths are UNSAFE in parallel code because
// they access shared state (the compartment's arena lists and so
// forth) without any synchronization.
// with respect to garbage collection and allocation. The typical
// allocation paths are UNSAFE in parallel code because they access
// shared state (the compartment's arena lists and so forth) without
// any synchronization. They can also trigger GC in an ad-hoc way.
//
// To deal with this, the forkjoin code creates a distinct |Allocator|
// object for each slice. You can access the appropriate object via
@ -166,17 +168,18 @@
// Current Limitations:
//
// - The API does not support recursive or nested use. That is, the
// |parallel()| callback of a |ForkJoinOp| may not itself invoke
// |ExecuteForkJoinOp()|. We may lift this limitation in the future.
// JavaScript function given to |ForkJoin| should not itself invoke
// |ForkJoin()|. Instead, use the intrinsic |InParallelSection()| to
// check for recursive use and execute a sequential fallback.
//
// - No load balancing is performed between worker threads. That means that
// the fork-join system is best suited for problems that can be slice into
// uniform bits.
//
///////////////////////////////////////////////////////////////////////////
namespace js {
struct ForkJoinShared;
struct ForkJoinSlice;
bool ForkJoin(JSContext *cx, CallArgs &args);
@ -203,6 +206,68 @@ struct IonLIRTraceData {
// Different subcategories of the "bail" state are encoded as variants of
// TP_RETRY_*.
enum ParallelResult { TP_SUCCESS, TP_RETRY_SEQUENTIALLY, TP_RETRY_AFTER_GC, TP_FATAL };
///////////////////////////////////////////////////////////////////////////
// Bailout tracking
enum ParallelBailoutCause {
ParallelBailoutNone,
// compiler returned Method_Skipped
ParallelBailoutCompilationSkipped,
// compiler returned Method_CantCompile
ParallelBailoutCompilationFailure,
// the periodic interrupt failed, which can mean that either
// another thread canceled, the user interrupted us, etc
ParallelBailoutInterrupt,
// an IC update failed
ParallelBailoutFailedIC,
// Heap busy flag was set during interrupt
ParallelBailoutHeapBusy,
ParallelBailoutMainScriptNotPresent,
ParallelBailoutCalledToUncompiledScript,
ParallelBailoutIllegalWrite,
ParallelBailoutAccessToIntrinsic,
ParallelBailoutOverRecursed,
ParallelBailoutOutOfMemory,
ParallelBailoutUnsupported,
ParallelBailoutUnsupportedStringComparison,
ParallelBailoutUnsupportedSparseArray,
};
struct ParallelBailoutTrace {
JSScript *script;
jsbytecode *bytecode;
};
// See "Bailouts" section in comment above.
struct ParallelBailoutRecord {
JSScript *topScript;
ParallelBailoutCause cause;
// Eventually we will support deeper traces,
// but for now we gather at most a single frame.
static const uint32_t MaxDepth = 1;
uint32_t depth;
ParallelBailoutTrace trace[MaxDepth];
void init(JSContext *cx);
void reset(JSContext *cx);
void setCause(ParallelBailoutCause cause,
JSScript *outermostScript, // inliner (if applicable)
JSScript *currentScript, // inlinee (if applicable)
jsbytecode *currentPc);
void addTrace(JSScript *script,
jsbytecode *pc);
};
struct ForkJoinShared;
struct ForkJoinSlice
{
public:
@ -220,8 +285,8 @@ struct ForkJoinSlice
// |perThreadData|.
Allocator *const allocator;
// If we took a parallel bailout, the script that bailed out is stored here.
JSScript *abortedScript;
// Bailout record used to record the reason this thread stopped executing
ParallelBailoutRecord *const bailoutRecord;
#ifdef DEBUG
// Records the last instr. to execute on this thread.
@ -229,7 +294,8 @@ struct ForkJoinSlice
#endif
ForkJoinSlice(PerThreadData *perThreadData, uint32_t sliceId, uint32_t numSlices,
Allocator *arenaLists, ForkJoinShared *shared);
Allocator *arenaLists, ForkJoinShared *shared,
ParallelBailoutRecord *bailoutRecord);
// True if this is the main thread, false if it is one of the parallel workers.
bool isMainThread();
@ -286,7 +352,15 @@ struct ForkJoinSlice
ForkJoinShared *const shared;
};
// Locks a JSContext for its scope.
// Locks a JSContext for its scope. Be very careful, because locking a
// JSContext does *not* allow you to safely mutate the data in the
// JSContext unless you can guarantee that any of the other threads
// that want to access that data will also acquire the lock, which is
// generally not the case. For example, the lock is used in the IC
// code to allow us to atomically patch up the dispatch table, but we
// must be aware that other threads may be reading from the table even
// as we write to it (though they cannot be writing, since they must
// hold the lock to write).
class LockedJSContext
{
#if defined(JS_THREADSAFE) && defined(JS_ION)
@ -353,7 +427,8 @@ enum SpewChannel {
bool SpewEnabled(SpewChannel channel);
void Spew(SpewChannel channel, const char *fmt, ...);
void SpewBeginOp(JSContext *cx, const char *name);
void SpewBailout(uint32_t count);
void SpewBailout(uint32_t count, HandleScript script, jsbytecode *pc,
ParallelBailoutCause cause);
ExecutionStatus SpewEndOp(ExecutionStatus status);
void SpewBeginCompile(HandleScript script);
ion::MethodStatus SpewEndCompile(ion::MethodStatus status);
@ -366,7 +441,8 @@ void SpewBailoutIR(uint32_t bblockId, uint32_t lirId,
static inline bool SpewEnabled(SpewChannel channel) { return false; }
static inline void Spew(SpewChannel channel, const char *fmt, ...) { }
static inline void SpewBeginOp(JSContext *cx, const char *name) { }
static inline void SpewBailout(uint32_t count) {}
static inline void SpewBailout(uint32_t count, HandleScript script,
jsbytecode *pc, ParallelBailoutCause cause) {}
static inline ExecutionStatus SpewEndOp(ExecutionStatus status) { return status; }
static inline void SpewBeginCompile(HandleScript script) { }
#ifdef JS_ION