зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
df7ee101da
Коммит
19123619ac
|
@ -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");
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче