Bug 716647 - Part 2: Bailout in place instead of directly to catch on Ion exception when Debugger is on. (r=jandem)

This commit is contained in:
Shu-yu Guo 2014-04-24 01:59:37 -07:00
Родитель df7ee101da
Коммит 19123619ac
6 изменённых файлов: 191 добавлений и 83 удалений

Просмотреть файл

@ -167,23 +167,49 @@ IonBailoutIterator::IonBailoutIterator(const JitActivationIterator &activations,
uint32_t
jit::ExceptionHandlerBailout(JSContext *cx, const InlineFrameIterator &frame,
ResumeFromException *rfe,
const ExceptionBailoutInfo &excInfo,
BaselineBailoutInfo **bailoutInfo)
bool *overrecursed)
{
JS_ASSERT(cx->isExceptionPending());
// We can be propagating debug mode exceptions without there being an
// actual exception pending. For instance, when we return false from an
// operation callback like a timeout handler.
MOZ_ASSERT_IF(!excInfo.propagatingIonExceptionForDebugMode(), cx->isExceptionPending());
cx->mainThread().ionTop = nullptr;
JitActivationIterator jitActivations(cx->runtime());
IonBailoutIterator iter(jitActivations, frame.frame());
JitActivation *activation = jitActivations.activation()->asJit();
*bailoutInfo = nullptr;
uint32_t retval = BailoutIonToBaseline(cx, activation, iter, true, bailoutInfo, &excInfo);
JS_ASSERT(retval == BAILOUT_RETURN_OK ||
retval == BAILOUT_RETURN_FATAL_ERROR ||
retval == BAILOUT_RETURN_OVERRECURSED);
BaselineBailoutInfo *bailoutInfo = nullptr;
uint32_t retval = BailoutIonToBaseline(cx, activation, iter, true, &bailoutInfo, &excInfo);
JS_ASSERT((retval == BAILOUT_RETURN_OK) == (*bailoutInfo != nullptr));
if (retval == BAILOUT_RETURN_OK) {
MOZ_ASSERT(bailoutInfo);
// Overwrite the kind so HandleException after the bailout returns
// false, jumping directly to the exception tail.
if (excInfo.propagatingIonExceptionForDebugMode())
bailoutInfo->bailoutKind = Bailout_IonExceptionDebugMode;
rfe->kind = ResumeFromException::RESUME_BAILOUT;
rfe->target = cx->runtime()->jitRuntime()->getBailoutTail()->raw();
rfe->bailoutInfo = bailoutInfo;
} else {
// Bailout failed. If there was a fatal error, clear the
// exception to turn this into an uncatchable error. If the
// overrecursion check failed, continue popping all inline
// frames and have the caller report an overrecursion error.
MOZ_ASSERT(!bailoutInfo);
if (!excInfo.propagatingIonExceptionForDebugMode())
cx->clearPendingException();
if (retval == BAILOUT_RETURN_OVERRECURSED)
*overrecursed = true;
else
MOZ_ASSERT(retval == BAILOUT_RETURN_FATAL_ERROR);
}
return retval;
}

Просмотреть файл

@ -155,18 +155,52 @@ uint32_t Bailout(BailoutStack *sp, BaselineBailoutInfo **info);
uint32_t InvalidationBailout(InvalidationBailoutStack *sp, size_t *frameSizeOut,
BaselineBailoutInfo **info);
struct ExceptionBailoutInfo
class ExceptionBailoutInfo
{
size_t frameNo;
jsbytecode *resumePC;
size_t numExprSlots;
size_t frameNo_;
jsbytecode *resumePC_;
size_t numExprSlots_;
public:
ExceptionBailoutInfo(size_t frameNo, jsbytecode *resumePC, size_t numExprSlots)
: frameNo_(frameNo),
resumePC_(resumePC),
numExprSlots_(numExprSlots)
{ }
ExceptionBailoutInfo()
: frameNo_(0),
resumePC_(nullptr),
numExprSlots_(0)
{ }
bool catchingException() const {
return !!resumePC_;
}
bool propagatingIonExceptionForDebugMode() const {
return !resumePC_;
}
size_t frameNo() const {
MOZ_ASSERT(catchingException());
return frameNo_;
}
jsbytecode *resumePC() const {
MOZ_ASSERT(catchingException());
return resumePC_;
}
size_t numExprSlots() const {
MOZ_ASSERT(catchingException());
return numExprSlots_;
}
};
// Called from the exception handler to enter a catch or finally block.
// Returns a BAILOUT_* error code.
uint32_t ExceptionHandlerBailout(JSContext *cx, const InlineFrameIterator &frame,
ResumeFromException *rfe,
const ExceptionBailoutInfo &excInfo,
BaselineBailoutInfo **bailoutInfo);
bool *overrecursed);
uint32_t FinishBailoutToBaseline(BaselineBailoutInfo *bailoutInfo);

Просмотреть файл

@ -484,12 +484,16 @@ InitFromBailout(JSContext *cx, HandleScript caller, jsbytecode *callerPC,
{
MOZ_ASSERT(script->hasBaselineScript());
// If excInfo is non-nullptr, we are bailing out to a catch or finally block
// and this is the frame where we will resume. Usually the expression stack
// should be empty in this case but there can be iterators on the stack.
// Are we catching an exception?
bool catchingException = excInfo && excInfo->catchingException();
// If we are catching an exception, we are bailing out to a catch or
// finally block and this is the frame where we will resume. Usually the
// expression stack should be empty in this case but there can be
// iterators on the stack.
uint32_t exprStackSlots;
if (excInfo)
exprStackSlots = excInfo->numExprSlots;
if (catchingException)
exprStackSlots = excInfo->numExprSlots();
else
exprStackSlots = iter.numAllocations() - (script->nfixed() + CountArgSlots(script, fun));
@ -680,8 +684,8 @@ InitFromBailout(JSContext *cx, HandleScript caller, jsbytecode *callerPC,
// Get the pc. If we are handling an exception, resume at the pc of the
// catch or finally block.
jsbytecode *pc = excInfo ? excInfo->resumePC : script->offsetToPC(iter.pcOffset());
bool resumeAfter = excInfo ? false : iter.resumeAfter();
jsbytecode *pc = catchingException ? excInfo->resumePC() : script->offsetToPC(iter.pcOffset());
bool resumeAfter = catchingException ? false : iter.resumeAfter();
JSOp op = JSOp(*pc);
@ -774,16 +778,30 @@ InitFromBailout(JSContext *cx, HandleScript caller, jsbytecode *callerPC,
for (uint32_t i = pushedSlots; i < exprStackSlots; i++) {
Value v;
// If coming from an invalidation bailout, and this is the topmost
// value, and a value override has been specified, don't read from the
// iterator. Otherwise, we risk using a garbage value.
if (!iter.moreFrames() && i == exprStackSlots - 1 &&
cx->runtime()->hasIonReturnOverride())
{
// If coming from an invalidation bailout, and this is the topmost
// value, and a value override has been specified, don't read from the
// iterator. Otherwise, we risk using a garbage value.
JS_ASSERT(invalidate);
iter.skip();
IonSpew(IonSpew_BaselineBailouts, " [Return Override]");
v = cx->runtime()->takeIonReturnOverride();
} else if (excInfo && excInfo->propagatingIonExceptionForDebugMode()) {
// If we are in the middle of propagating an exception from Ion by
// bailing to baseline due to debug mode, we might not have all
// the stack if we are at the newest frame.
//
// For instance, if calling |f()| pushed an Ion frame which threw,
// the snapshot expects the return value to be pushed, but it's
// possible nothing was pushed before we threw. Iterators might
// still be on the stack, so we can't just drop the stack.
MOZ_ASSERT(cx->compartment()->debugMode());
if (iter.moreFrames())
v = iter.read();
else
v = MagicValue(JS_OPTIMIZED_OUT);
} else {
v = iter.read();
}
@ -856,7 +874,7 @@ InitFromBailout(JSContext *cx, HandleScript caller, jsbytecode *callerPC,
// If this was the last inline frame, or we are bailing out to a catch or
// finally block in this frame, then unpacking is almost done.
if (!iter.moreFrames() || excInfo) {
if (!iter.moreFrames() || catchingException) {
// Last frame, so PC for call to next frame is set to nullptr.
*callPC = nullptr;
@ -1320,8 +1338,21 @@ jit::BailoutIonToBaseline(JSContext *cx, JitActivation *activation, IonBailoutIt
iter.script()->filename(), iter.script()->lineno(), (void *) iter.ionScript(),
(int) prevFrameType);
if (excInfo)
IonSpew(IonSpew_BaselineBailouts, "Resuming in catch or finally block");
bool catchingException;
bool propagatingExceptionForDebugMode;
if (excInfo) {
catchingException = excInfo->catchingException();
propagatingExceptionForDebugMode = excInfo->propagatingIonExceptionForDebugMode();
if (catchingException)
IonSpew(IonSpew_BaselineBailouts, "Resuming in catch or finally block");
if (propagatingExceptionForDebugMode)
IonSpew(IonSpew_BaselineBailouts, "Resuming in-place for debug mode");
} else {
catchingException = false;
propagatingExceptionForDebugMode = false;
}
IonSpew(IonSpew_BaselineBailouts, " Reading from snapshot offset %u size %u",
iter.snapshotOffset(), iter.ionScript()->snapshotsListSize());
@ -1376,13 +1407,17 @@ jit::BailoutIonToBaseline(JSContext *cx, JitActivation *activation, IonBailoutIt
// If we are bailing out to a catch or finally block in this frame,
// pass excInfo to InitFromBailout and don't unpack any other frames.
bool handleException = (excInfo && excInfo->frameNo == frameNo);
bool handleException = (catchingException && excInfo->frameNo() == frameNo);
// We also need to pass excInfo if we're bailing out in place for
// debug mode.
bool passExcInfo = handleException || propagatingExceptionForDebugMode;
jsbytecode *callPC = nullptr;
RootedFunction nextCallee(cx, nullptr);
if (!InitFromBailout(cx, caller, callerPC, fun, scr, iter.ionScript(),
snapIter, invalidate, builder, startFrameFormals,
&nextCallee, &callPC, handleException ? excInfo : nullptr))
&nextCallee, &callPC, passExcInfo ? excInfo : nullptr))
{
return BAILOUT_RETURN_FATAL_ERROR;
}
@ -1608,6 +1643,10 @@ jit::FinishBailoutToBaseline(BaselineBailoutInfo *bailoutInfo)
if (!HandleBaselineInfoBailout(cx, outerScript, innerScript))
return false;
break;
case Bailout_IonExceptionDebugMode:
// Return false to resume in HandleException with reconstructed
// baseline frame.
return false;
default:
MOZ_ASSUME_UNREACHABLE("Unknown bailout kind!");
}

Просмотреть файл

@ -3686,34 +3686,39 @@ IonBuilder::processThrow()
{
MDefinition *def = current->pop();
if (graph().hasTryBlock()) {
// MThrow is not marked as effectful. This means when it throws and we
// are inside a try block, we could use an earlier resume point and this
// resume point may not be up-to-date, for example:
//
// (function() {
// try {
// var x = 1;
// foo(); // resume point
// x = 2;
// throw foo;
// } catch(e) {
// print(x);
// }
// ])();
//
// If we use the resume point after the call, this will print 1 instead
// of 2. To fix this, we create a resume point right before the MThrow.
//
// Note that this is not a problem for instructions other than MThrow
// because they are either marked as effectful (have their own resume
// point) or cannot throw a catchable exception.
MNop *ins = MNop::New(alloc());
current->add(ins);
// MThrow is not marked as effectful. This means when it throws and we
// are inside a try block, we could use an earlier resume point and this
// resume point may not be up-to-date, for example:
//
// (function() {
// try {
// var x = 1;
// foo(); // resume point
// x = 2;
// throw foo;
// } catch(e) {
// print(x);
// }
// ])();
//
// If we use the resume point after the call, this will print 1 instead
// of 2. To fix this, we create a resume point right before the MThrow.
//
// Note that this is not a problem for instructions other than MThrow
// because they are either marked as effectful (have their own resume
// point) or cannot throw a catchable exception.
//
// We always install this resume point (instead of only when the function
// has a try block) in order to handle the Debugger onExceptionUnwind
// hook. When we need to handle the hook, we bail out to baseline right
// after the throw and propagate the exception when debug mode is on. This
// is opposed to the normal behavior of resuming directly in the
// associated catch block.
MNop *nop = MNop::New(alloc());
current->add(nop);
if (!resumeAfter(ins))
return ControlStatus_Error;
}
if (!resumeAfter(nop))
return ControlStatus_Error;
MThrow *ins = MThrow::New(alloc(), def);
current->end(ins);

Просмотреть файл

@ -373,6 +373,26 @@ HandleExceptionIon(JSContext *cx, const InlineFrameIterator &frame, ResumeFromEx
RootedScript script(cx, frame.script());
jsbytecode *pc = frame.pc();
bool bailedOutForDebugMode = false;
if (cx->compartment()->debugMode()) {
// If we have an exception from within Ion and the debugger is active,
// we do the following:
//
// 1. Bailout to baseline to reconstruct a baseline frame.
// 2. Resume immediately into the exception tail afterwards, and
// handle the exception again with the top frame now a baseline
// frame.
//
// An empty exception info denotes that we're propagating an Ion
// exception due to debug mode, which BailoutIonToBaseline needs to
// know. This is because we might not be able to fully reconstruct up
// to the stack depth at the snapshot, as we could've thrown in the
// middle of a call.
ExceptionBailoutInfo propagateInfo;
uint32_t retval = ExceptionHandlerBailout(cx, frame, rfe, propagateInfo, overrecursed);
bailedOutForDebugMode = retval == BAILOUT_RETURN_OK;
}
if (!script->hasTrynotes())
return;
@ -400,7 +420,7 @@ HandleExceptionIon(JSContext *cx, const InlineFrameIterator &frame, ResumeFromEx
break;
case JSTRY_CATCH:
if (cx->isExceptionPending()) {
if (cx->isExceptionPending() && !bailedOutForDebugMode) {
// Ion can compile try-catch, but bailing out to catch
// exceptions is slow. Reset the use count so that if we
// catch many exceptions we won't Ion-compile the script.
@ -408,34 +428,13 @@ HandleExceptionIon(JSContext *cx, const InlineFrameIterator &frame, ResumeFromEx
// Bailout at the start of the catch block.
jsbytecode *catchPC = script->main() + tn->start + tn->length;
ExceptionBailoutInfo excInfo;
excInfo.frameNo = frame.frameNo();
excInfo.resumePC = catchPC;
excInfo.numExprSlots = tn->stackDepth;
BaselineBailoutInfo *info = nullptr;
uint32_t retval = ExceptionHandlerBailout(cx, frame, excInfo, &info);
if (retval == BAILOUT_RETURN_OK) {
JS_ASSERT(info);
rfe->kind = ResumeFromException::RESUME_BAILOUT;
rfe->target = cx->runtime()->jitRuntime()->getBailoutTail()->raw();
rfe->bailoutInfo = info;
ExceptionBailoutInfo excInfo(frame.frameNo(), catchPC, tn->stackDepth);
uint32_t retval = ExceptionHandlerBailout(cx, frame, rfe, excInfo, overrecursed);
if (retval == BAILOUT_RETURN_OK)
return;
}
// Bailout failed. If there was a fatal error, clear the
// exception to turn this into an uncatchable error. If the
// overrecursion check failed, continue popping all inline
// frames and have the caller report an overrecursion error.
JS_ASSERT(!info);
cx->clearPendingException();
if (retval == BAILOUT_RETURN_OVERRECURSED)
*overrecursed = true;
else
JS_ASSERT(retval == BAILOUT_RETURN_FATAL_ERROR);
// Error on bailout clears pending exception.
MOZ_ASSERT(!cx->isExceptionPending());
}
break;

Просмотреть файл

@ -48,7 +48,10 @@ enum BailoutKind
Bailout_ShapeGuard,
// A bailout caused by invalid assumptions based on Baseline code.
Bailout_BaselineInfo
Bailout_BaselineInfo,
// A bailout to baseline from Ion on exception to handle Debugger hooks.
Bailout_IonExceptionDebugMode,
};
inline const char *
@ -65,6 +68,8 @@ BailoutKindString(BailoutKind kind)
return "Bailout_ShapeGuard";
case Bailout_BaselineInfo:
return "Bailout_BaselineInfo";
case Bailout_IonExceptionDebugMode:
return "Bailout_IonExceptionDebugMode";
default:
MOZ_ASSUME_UNREACHABLE("Invalid BailoutKind");
}