/* * Given a frame that is about to return, make sure its return value and * activation objects are fixed up. Then, pop the frame and advance the * current PC. Note that while we could enter the JIT at this point, the * logic would still be necessary for the interpreter, so it's easier * (and faster) to finish frames in C++ even if at a safe point here. */ static bool HandleFinishedFrame(VMFrame &f, StackFrame *entryFrame) { JSContext *cx = f.cx; JS_ASSERT(FrameIsFinished(cx)); /* * This is the most difficult and complicated piece of the tracer * integration, and historically has been very buggy. The problem is that * although this frame has to be popped (see RemoveExcessFrames), it may * be at a JSOP_RETURN opcode, and it might not have ever been executed. * That is, fp->rval may not be set to the top of the stack, and if it * has, the stack has already been decremented. Note that fp->rval is not * the only problem: the epilogue may never have been executed. * * Here are the edge cases and whether the frame has been exited cleanly: * 1. No: A trace exited directly before a RETURN op, and the * interpreter never ran. * 2. Yes: The interpreter exited cleanly. * 3. No: The interpreter exited on a safe point. LEAVE_ON_SAFE_POINT * is not used in between JSOP_RETURN and advancing the PC, * therefore, it cannot have been run if at a safe point. * 4. No: Somewhere in the RunTracer call tree, we removed a frame, * and we returned to a JSOP_RETURN opcode. Note carefully * that in this situation, FrameIsFinished() returns true! * 5. Yes: The function exited in the method JIT, during * FinishExcessFrames() However, in this case, we'll never enter * HandleFinishedFrame(): we always immediately pop JIT'd frames. * * Since the only scenario where this fixup is NOT needed is a normal exit * from the interpreter, we can cleanly check for this scenario by checking * a bit it sets in the frame. */ bool returnOK = true; if (!cx->fp()->finishedInInterpreter()) { if (JSOp(*cx->regs().pc) == JSOP_RETURN) cx->fp()->setReturnValue(f.regs.sp[-1]); returnOK = ScriptEpilogue(cx, cx->fp(), true); } if (cx->fp() != entryFrame) { InlineReturn(f); } return returnOK; }
uint32 ion::ReflowTypeInfo(uint32 bailoutResult) { JSContext *cx = GetIonContext()->cx; IonActivation *activation = cx->runtime->ionActivation; IonSpew(IonSpew_Bailouts, "reflowing type info"); if (bailoutResult == BAILOUT_RETURN_ARGUMENT_CHECK) { IonSpew(IonSpew_Bailouts, "reflowing type info at argument-checked entry"); ReflowArgTypes(cx); return true; } RootedScript script(cx, cx->fp()->script()); jsbytecode *pc = activation->bailout()->bailoutPc(); JS_ASSERT(js_CodeSpec[*pc].format & JOF_TYPESET); IonSpew(IonSpew_Bailouts, "reflowing type info at %s:%d pcoff %d", script->filename, script->lineno, pc - script->code); types::AutoEnterTypeInference enter(cx); if (bailoutResult == BAILOUT_RETURN_TYPE_BARRIER) script->analysis()->breakTypeBarriers(cx, pc - script->code, false); else JS_ASSERT(bailoutResult == BAILOUT_RETURN_MONITOR); // When a type barrier fails, the bad value is at the top of the stack. Value &result = cx->regs().sp[-1]; types::TypeScript::Monitor(cx, script, pc, result); return true; }
/* * Given a frame newer than the entry frame, try to finish it. If it's at a * return position, pop the frame. If it's at a safe point, execute it in * Jaeger code. Otherwise, try to interpret until a safe point. * * While this function is guaranteed to make progress, it may not actually * finish or pop the current frame. It can either: * 1) Finalize a finished frame, or * 2) Finish and finalize the frame in the Method JIT, or * 3) Interpret, which can: * a) Propagate an error, or * b) Finish the frame, but not finalize it, or * c) Abruptly leave at any point in the frame, or in a newer frame * pushed by a call, that has method JIT'd code. */ static bool EvaluateExcessFrame(VMFrame &f, JSStackFrame *entryFrame) { JSContext *cx = f.cx; JSStackFrame *fp = cx->fp(); /* * A "finished" frame is when the interpreter rested on a STOP, * RETURN, RETRVAL, etc. We check for finished frames BEFORE looking * for a safe point. If the frame was finished, we could have already * called ScriptEpilogue(), and entering the JIT could call it twice. */ if (!fp->hasImacropc() && FrameIsFinished(cx)) return HandleFinishedFrame(f, entryFrame); if (void *ncode = AtSafePoint(cx)) { if (!JaegerShotAtSafePoint(cx, ncode)) return false; InlineReturn(f); AdvanceReturnPC(cx); return true; } return PartialInterpret(f); }
static inline bool UncachedInlineCall(VMFrame &f, uint32 flags, void **pret, bool *unjittable, uint32 argc) { JSContext *cx = f.cx; Value *vp = f.regs.sp - (argc + 2); JSObject &callee = vp->toObject(); JSFunction *newfun = callee.getFunctionPrivate(); JSScript *newscript = newfun->script(); /* Get pointer to new frame/slots, prepare arguments. */ StackSpace &stack = cx->stack(); JSStackFrame *newfp = stack.getInlineFrameWithinLimit(cx, f.regs.sp, argc, newfun, newscript, &flags, f.entryfp, &f.stackLimit); if (JS_UNLIKELY(!newfp)) return false; /* Initialize frame, locals. */ newfp->initCallFrame(cx, callee, newfun, argc, flags); SetValueRangeToUndefined(newfp->slots(), newscript->nfixed); /* Officially push the frame. */ stack.pushInlineFrame(cx, newscript, newfp, &f.regs); JS_ASSERT(newfp == f.regs.fp); /* Scope with a call object parented by callee's parent. */ if (newfun->isHeavyweight() && !js::CreateFunCallObject(cx, newfp)) return false; /* Try to compile if not already compiled. */ if (newscript->getJITStatus(newfp->isConstructing()) == JITScript_None) { CompileStatus status = CanMethodJIT(cx, newscript, newfp, CompileRequest_Interpreter); if (status == Compile_Error) { /* A runtime exception was thrown, get out. */ InlineReturn(f); return false; } if (status == Compile_Abort) *unjittable = true; } /* If newscript was successfully compiled, run it. */ if (JITScript *jit = newscript->getJIT(newfp->isConstructing())) { *pret = jit->invokeEntry; return true; } /* Otherwise, run newscript in the interpreter. */ bool ok = !!Interpret(cx, cx->fp()); InlineReturn(f); *pret = NULL; return ok; }
/* * Evaluate frames newer than the entry frame until all are gone. This will * always leave f.regs.fp == entryFrame. */ static bool FinishExcessFrames(VMFrame &f, JSStackFrame *entryFrame) { JSContext *cx = f.cx; while (cx->fp() != entryFrame || entryFrame->hasImacropc()) { if (!EvaluateExcessFrame(f, entryFrame)) { if (!HandleErrorInExcessFrame(f, entryFrame)) return false; } } return true; }
/* * Interprets until either a safe point is reached that has method JIT'd * code, or the current frame tries to return. */ static inline JSBool PartialInterpret(VMFrame &f) { JSContext *cx = f.cx; JSStackFrame *fp = cx->fp(); #ifdef DEBUG JSScript *script = fp->script(); JS_ASSERT(!fp->finishedInInterpreter()); JS_ASSERT(fp->hasImacropc() || !script->maybeNativeCodeForPC(fp->isConstructing(), cx->regs->pc)); #endif JSBool ok = JS_TRUE; ok = Interpret(cx, fp, 0, JSINTERP_SAFEPOINT); return ok; }
uint32 ion::RecompileForInlining() { JSContext *cx = GetIonContext()->cx; RawScript script = cx->fp()->script().unsafeGet(); IonSpew(IonSpew_Inlining, "Recompiling script to inline calls %s:%d", script->filename, script->lineno); // Invalidate the script to force a recompile. if (!Invalidate(cx, script, /* resetUses */ false)) return BAILOUT_RETURN_FATAL_ERROR; // Invalidation should not reset the use count. JS_ASSERT(script->getUseCount() >= js_IonOptions.usesBeforeInlining); return true; }
static inline bool UncachedInlineCall(VMFrame &f, MaybeConstruct construct, void **pret, bool *unjittable, uint32 argc) { JSContext *cx = f.cx; CallArgs args = CallArgsFromSp(argc, f.regs.sp); JSObject &callee = args.callee(); JSFunction *newfun = callee.getFunctionPrivate(); JSScript *newscript = newfun->script(); /* Get pointer to new frame/slots, prepare arguments. */ if (!cx->stack.pushInlineFrame(cx, f.regs, args, callee, newfun, newscript, construct, &f.stackLimit)) return false; /* Scope with a call object parented by callee's parent. */ if (newfun->isHeavyweight() && !js::CreateFunCallObject(cx, f.fp())) return false; /* Try to compile if not already compiled. */ if (newscript->getJITStatus(f.fp()->isConstructing()) == JITScript_None) { CompileStatus status = CanMethodJIT(cx, newscript, f.fp(), CompileRequest_Interpreter); if (status == Compile_Error) { /* A runtime exception was thrown, get out. */ InlineReturn(f); return false; } if (status == Compile_Abort) *unjittable = true; } /* If newscript was successfully compiled, run it. */ if (JITScript *jit = newscript->getJIT(f.fp()->isConstructing())) { *pret = jit->invokeEntry; return true; } /* Otherwise, run newscript in the interpreter. */ bool ok = !!Interpret(cx, cx->fp()); InlineReturn(f); *pret = NULL; return ok; }
uint32 ion::InvalidationBailout(InvalidationBailoutStack *sp, size_t *frameSizeOut) { AssertCanGC(); sp->checkInvariants(); JSContext *cx = GetIonContext()->cx; // We don't have an exit frame. cx->runtime->ionTop = NULL; IonActivationIterator ionActivations(cx); IonBailoutIterator iter(ionActivations, sp); IonActivation *activation = ionActivations.activation(); IonSpew(IonSpew_Bailouts, "Took invalidation bailout! Snapshot offset: %d", iter.snapshotOffset()); // Note: the frame size must be computed before we return from this function. *frameSizeOut = iter.topFrameSize(); uint32 retval = ConvertFrames(cx, activation, iter); { IonJSFrameLayout *frame = iter.jsFrame(); IonSpew(IonSpew_Invalidate, "converting to exit frame"); IonSpew(IonSpew_Invalidate, " orig calleeToken %p", (void *) frame->calleeToken()); IonSpew(IonSpew_Invalidate, " orig frameSize %u", unsigned(frame->prevFrameLocalSize())); IonSpew(IonSpew_Invalidate, " orig ra %p", (void *) frame->returnAddress()); frame->replaceCalleeToken(NULL); EnsureExitFrame(frame); IonSpew(IonSpew_Invalidate, " new calleeToken %p", (void *) frame->calleeToken()); IonSpew(IonSpew_Invalidate, " new frameSize %u", unsigned(frame->prevFrameLocalSize())); IonSpew(IonSpew_Invalidate, " new ra %p", (void *) frame->returnAddress()); } iter.ionScript()->decref(cx->runtime->defaultFreeOp()); if (cx->runtime->hasIonReturnOverride()) cx->regs().sp[-1] = cx->runtime->takeIonReturnOverride(); if (retval != BAILOUT_RETURN_FATAL_ERROR) { if (activation->entryfp()) { if (void *annotation = activation->entryfp()->annotation()) { // If the entry frame has an annotation, then we invalidated and have // immediately returned into this bailout. Transfer the annotation to // the new topmost frame. activation->entryfp()->setAnnotation(NULL); cx->fp()->setAnnotation(annotation); } } // If invalidation was triggered inside a stub call, we may still have to // monitor the result, since the bailout happens before the MMonitorTypes // instruction is executed. jsbytecode *pc = activation->bailout()->bailoutPc(); // If this is not a ResumeAfter bailout, there's nothing to monitor, // we will redo the op in the interpreter. bool isResumeAfter = GetNextPc(pc) == cx->regs().pc; if ((js_CodeSpec[*pc].format & JOF_TYPESET) && isResumeAfter) { JS_ASSERT(retval == BAILOUT_RETURN_OK); return BAILOUT_RETURN_MONITOR; } return retval; } return BAILOUT_RETURN_FATAL_ERROR; }
uint32 ion::ThunkToInterpreter(Value *vp) { JSContext *cx = GetIonContext()->cx; IonActivation *activation = cx->runtime->ionActivation; BailoutClosure *br = activation->takeBailout(); if (!EnsureHasCallObject(cx, cx->fp())) return Interpret_Error; // By default we set the forbidOsr flag on the ion script, but if a GC // happens just after we re-enter the interpreter, the ion script get // invalidated and we do not see the forbidOsr flag. This may cause a loop // which apear with eager compilation and gc zeal enabled. This code is a // workaround to avoid recompiling with OSR just after a bailout followed by // a GC. (see Bug 746691 & Bug 751383) jsbytecode *pc = cx->regs().pc; while (JSOp(*pc) == JSOP_GOTO) pc += GET_JUMP_OFFSET(pc); if (JSOp(*pc) == JSOP_LOOPENTRY) cx->regs().pc = GetNextPc(pc); if (activation->entryfp() == br->entryfp()) { // If the bailout entry fp is the same as the activation entryfp, then // there are no scripted frames below us. In this case, just shortcut // out with a special return code, and resume interpreting in the // original Interpret activation. vp->setMagic(JS_ION_BAILOUT); js_delete(br); return Interpret_Ok; } InterpretStatus status = Interpret(cx, br->entryfp(), JSINTERP_BAILOUT); if (status == Interpret_OSR) { // The interpreter currently does not ask to perform inline OSR, so // this path is unreachable. JS_NOT_REACHED("invalid"); IonSpew(IonSpew_Bailouts, "Performing inline OSR %s:%d", cx->fp()->script()->filename, PCToLineNumber(cx->fp()->script(), cx->regs().pc)); // We want to OSR again. We need to avoid the problem where frequent // bailouts cause recursive nestings of Interpret and EnterIon. The // interpreter therefore shortcuts out, and now we're responsible for // completing the OSR inline. // // Note that we set runningInIon so that if we re-enter C++ from within // the inlined OSR, StackIter will know to traverse these frames. StackFrame *fp = cx->fp(); fp->setRunningInIon(); vp->setPrivate(fp); js_delete(br); return Interpret_OSR; } if (status == Interpret_Ok) *vp = br->entryfp()->returnValue(); // The BailoutFrameGuard's destructor will ensure that the frame is // removed. js_delete(br); return status; }
extern "C" void * js_InternalThrow(VMFrame &f) { JSContext *cx = f.cx; ExpandInlineFrames(cx->compartment); // The current frame may have an associated orphaned native, if the native // or SplatApplyArgs threw an exception. RemoveOrphanedNative(cx, f.fp()); JS_ASSERT(!f.fp()->finishedInInterpreter()); // Make sure sp is up to date. JS_ASSERT(&cx->regs() == &f.regs); jsbytecode *pc = NULL; for (;;) { if (cx->isExceptionPending()) { // Call the throw hook if necessary JSThrowHook handler = cx->runtime->debugHooks.throwHook; if (handler || !cx->compartment->getDebuggees().empty()) { Value rval; JSTrapStatus st = Debugger::onExceptionUnwind(cx, &rval); if (st == JSTRAP_CONTINUE && handler) { RootedScript fscript(cx, cx->fp()->script()); st = handler(cx, fscript, cx->regs().pc, &rval, cx->runtime->debugHooks.throwHookData); } switch (st) { case JSTRAP_ERROR: cx->clearPendingException(); break; case JSTRAP_CONTINUE: break; case JSTRAP_RETURN: cx->clearPendingException(); cx->fp()->setReturnValue(rval); return cx->jaegerRuntime().forceReturnFromExternC(); case JSTRAP_THROW: cx->setPendingException(rval); break; default: JS_NOT_REACHED("bad onExceptionUnwind status"); } } } pc = FindExceptionHandler(cx); if (pc) break; // The JIT guarantees that ScriptDebugEpilogue() and ScriptEpilogue() // have always been run upon exiting to its caller. This is important // for consistency, where execution modes make similar guarantees about // prologues and epilogues. Interpret(), and Invoke() all rely on this // property. JS_ASSERT(!f.fp()->finishedInInterpreter()); UnwindScope(cx, 0); f.regs.setToEndOfScript(); if (cx->compartment->debugMode()) { // This can turn a throw or error into a healthy return. Note that // we will run ScriptDebugEpilogue again (from AnyFrameEpilogue); // ScriptDebugEpilogue is prepared for this eventuality. if (js::ScriptDebugEpilogue(cx, f.fp(), false)) return cx->jaegerRuntime().forceReturnFromExternC(); } f.fp()->epilogue(f.cx); // Don't remove the last frame, this is the responsibility of // JaegerShot()'s caller. We only guarantee that ScriptEpilogue() // has been run. if (f.entryfp == f.fp()) break; f.cx->stack.popInlineFrame(f.regs); DebugOnly<JSOp> op = JSOp(*f.regs.pc); JS_ASSERT(op == JSOP_CALL || op == JSOP_NEW || op == JSOP_EVAL || op == JSOP_FUNCALL || op == JSOP_FUNAPPLY); f.regs.pc += JSOP_CALL_LENGTH; } JS_ASSERT(&cx->regs() == &f.regs); if (!pc) return NULL; StackFrame *fp = cx->fp(); RootedScript script(cx, fp->script()); /* * Fall back to EnterMethodJIT and finish the frame in the interpreter. * With type inference enabled, we may wipe out all JIT code on the * stack without patching ncode values to jump to the interpreter, and * thus can only enter JIT code via EnterMethodJIT (which overwrites * its entry frame's ncode). See ClearAllFrames. */ cx->jaegerRuntime().setLastUnfinished(Jaeger_Unfinished); if (!JSScript::ensureRanAnalysis(cx, script)) { js_ReportOutOfMemory(cx); return NULL; } analyze::AutoEnterAnalysis enter(cx); /* * Interpret the ENTERBLOCK and EXCEPTION opcodes, so that we don't go * back into the interpreter with a pending exception. This will cause * it to immediately rethrow. */ if (cx->isExceptionPending()) { JS_ASSERT(JSOp(*pc) == JSOP_ENTERBLOCK); StaticBlockObject &blockObj = script->getObject(GET_UINT32_INDEX(pc))->asStaticBlock(); Value *vp = cx->regs().sp + blockObj.slotCount(); SetValueRangeToUndefined(cx->regs().sp, vp); cx->regs().sp = vp; if (!cx->regs().fp()->pushBlock(cx, blockObj)) return NULL; JS_ASSERT(JSOp(pc[JSOP_ENTERBLOCK_LENGTH]) == JSOP_EXCEPTION); cx->regs().sp[0] = cx->getPendingException(); cx->clearPendingException(); cx->regs().sp++; cx->regs().pc = pc + JSOP_ENTERBLOCK_LENGTH + JSOP_EXCEPTION_LENGTH; } *f.oldregs = f.regs; return NULL; }
static inline bool UncachedInlineCall(VMFrame &f, InitialFrameFlags initial, void **pret, bool *unjittable, uint32_t argc) { AssertCanGC(); JSContext *cx = f.cx; CallArgs args = CallArgsFromSp(argc, f.regs.sp); RootedFunction newfun(cx, args.callee().toFunction()); RootedScript newscript(cx, newfun->getOrCreateScript(cx)); if (!newscript) return false; bool construct = InitialFrameFlagsAreConstructing(initial); RootedScript fscript(cx, f.script()); bool newType = construct && cx->typeInferenceEnabled() && types::UseNewType(cx, fscript, f.pc()); if (!types::TypeMonitorCall(cx, args, construct)) return false; /* Try to compile if not already compiled. */ if (ShouldJaegerCompileCallee(cx, f.script(), newscript, f.jit())) { CompileStatus status = CanMethodJIT(cx, newscript, newscript->code, construct, CompileRequest_JIT, f.fp()); if (status == Compile_Error) { /* A runtime exception was thrown, get out. */ return false; } if (status == Compile_Abort) *unjittable = true; } /* * Make sure we are not calling from an inline frame if we need to make a * call object for the callee, as doing so could trigger GC and cause * jitcode discarding / frame expansion. */ if (f.regs.inlined() && newfun->isHeavyweight()) { ExpandInlineFrames(cx->compartment); JS_ASSERT(!f.regs.inlined()); } /* * Preserve f.regs.fp while pushing the new frame, for the invariant that * f.regs reflects the state when we entered the stub call. This handoff is * tricky: we need to make sure that f.regs is not updated to the new * frame, and we also need to ensure that cx->regs still points to f.regs * when space is reserved, in case doing so throws an exception. */ FrameRegs regs = f.regs; /* Get pointer to new frame/slots, prepare arguments. */ if (!cx->stack.pushInlineFrame(cx, regs, args, *newfun, newscript, initial, &f.stackLimit)) return false; /* Finish the handoff to the new frame regs. */ PreserveRegsGuard regsGuard(cx, regs); /* * If newscript was successfully compiled, run it. Skip for calls which * will be constructing a new type object for 'this'. */ if (!newType) { if (JITScript *jit = newscript->getJIT(regs.fp()->isConstructing(), cx->compartment->compileBarriers())) { if (jit->invokeEntry) { *pret = jit->invokeEntry; /* Restore the old fp around and let the JIT code repush the new fp. */ regs.popFrame((Value *) regs.fp()); return true; } } } /* * Otherwise, run newscript in the interpreter. Expand any inlined frame we * are calling from, as the new frame is not associated with the VMFrame * and will not have its prevpc info updated if frame expansion is * triggered while interpreting. */ if (f.regs.inlined()) { ExpandInlineFrames(cx->compartment); JS_ASSERT(!f.regs.inlined()); regs.fp()->resetInlinePrev(f.fp(), f.regs.pc); } JS_CHECK_RECURSION(cx, return false); RootedScript script(cx, newscript); bool ok = RunScript(cx, script, cx->fp()); f.cx->stack.popInlineFrame(regs); if (ok) { RootedScript fscript(cx, f.script()); types::TypeScript::Monitor(f.cx, fscript, f.pc(), args.rval()); } *pret = NULL; return ok; }
void * RunTracer(VMFrame &f) #endif { JSContext *cx = f.cx; JSStackFrame *entryFrame = f.fp(); TracePointAction tpa; /* :TODO: nuke PIC? */ if (!cx->traceJitEnabled) return NULL; /* * Force initialization of the entry frame's scope chain and return value, * if necessary. The tracer can query the scope chain without needing to * check the HAS_SCOPECHAIN flag, and the frame is guaranteed to have the * correct return value stored if we trace/interpret through to the end * of the frame. */ entryFrame->scopeChain(); entryFrame->returnValue(); bool blacklist; uintN inlineCallCount = 0; void **traceData; uintN *traceEpoch; uint32 *loopCounter; uint32 hits; #if JS_MONOIC traceData = &ic.traceData; traceEpoch = &ic.traceEpoch; loopCounter = &ic.loopCounter; *loopCounter = 1; hits = ic.loopCounterStart; #else traceData = NULL; traceEpoch = NULL; loopCounter = NULL; hits = 1; #endif tpa = MonitorTracePoint(f.cx, inlineCallCount, &blacklist, traceData, traceEpoch, loopCounter, hits); JS_ASSERT(!TRACE_RECORDER(cx)); #if JS_MONOIC ic.loopCounterStart = *loopCounter; if (blacklist) DisableTraceHint(entryFrame->jit(), ic); #endif // Even though ExecuteTree() bypasses the interpreter, it should propagate // error failures correctly. JS_ASSERT_IF(cx->isExceptionPending(), tpa == TPA_Error); f.fp() = cx->fp(); JS_ASSERT(f.fp() == cx->fp()); switch (tpa) { case TPA_Nothing: return NULL; case TPA_Error: if (!HandleErrorInExcessFrame(f, entryFrame, f.fp()->finishedInInterpreter())) THROWV(NULL); JS_ASSERT(!cx->fp()->hasImacropc()); break; case TPA_RanStuff: case TPA_Recorded: break; } /* * The tracer could have dropped us off on any frame at any position. * Well, it could not have removed frames (recursion is disabled). * * Frames after the entryFrame cannot be entered via JaegerShotAtSafePoint() * unless each is at a safe point. We can JaegerShotAtSafePoint these * frames individually, but we must unwind to the entryFrame. * * Note carefully that JaegerShotAtSafePoint can resume methods at * arbitrary safe points whereas JaegerShot cannot. * * If we land on entryFrame without a safe point in sight, we'll end up * at the RETURN op. This is an edge case with two paths: * * 1) The entryFrame is the last inline frame. If it fell on a RETURN, * move the return value down. * 2) The entryFrame is NOT the last inline frame. Pop the frame. * * In both cases, we hijack the stub to return to the force-return * trampoline. This trampoline simulates the frame-popping portion of * emitReturn (except without the benefit of the FrameState) and will * produce the necessary register state to return to the caller. */ restart: /* Step 1. Finish frames created after the entry frame. */ if (!FinishExcessFrames(f, entryFrame)) THROWV(NULL); /* IMacros are guaranteed to have been removed by now. */ JS_ASSERT(f.fp() == entryFrame); JS_ASSERT(!entryFrame->hasImacropc()); /* Step 2. If entryFrame is done, use a special path to return to EnterMethodJIT(). */ if (FrameIsFinished(cx)) { if (!HandleFinishedFrame(f, entryFrame)) THROWV(NULL); void *retPtr = JS_FUNC_TO_DATA_PTR(void *, cx->jaegerCompartment()->forceReturnTrampoline()); *f.returnAddressLocation() = retPtr; return NULL; } /* Step 3. If entryFrame is at a safe point, just leave. */ if (void *ncode = AtSafePoint(cx)) return ncode; /* Step 4. Do a partial interp, then restart the whole process. */ if (!PartialInterpret(f)) { if (!HandleErrorInExcessFrame(f, entryFrame)) THROWV(NULL); } goto restart; }
/* * Called when an error is in progress and the topmost frame could not handle * it. This will unwind to a given frame, or find and align to an exception * handler in the process. */ static inline bool HandleErrorInExcessFrame(VMFrame &f, JSStackFrame *stopFp, bool searchedTopmostFrame = true) { JSContext *cx = f.cx; /* * Callers of this called either Interpret() or JaegerShot(), which would * have searched for exception handlers already. If we see stopFp, just * return false. Otherwise, pop the frame, since it's guaranteed useless. * * Note that this also guarantees ScriptEpilogue() has been called. */ JSStackFrame *fp = cx->fp(); if (searchedTopmostFrame) { /* * This is a special case meaning that fp->finishedInInterpreter() is * true. If so, and fp == stopFp, our only choice is to propagate this * error up, back to the method JIT, and then to js_InternalThrow, * where this becomes a special case. See the comment there and bug * 624100. */ if (fp == stopFp) return false; /* * Otherwise, the protocol here (like Invoke) is to assume that the * execution mode finished the frame, and to just pop it. */ InlineReturn(f); } /* Remove the bottom frame. */ bool returnOK = false; for (;;) { fp = cx->fp(); /* Clear imacros. */ if (fp->hasImacropc()) { cx->regs->pc = fp->imacropc(); fp->clearImacropc(); } JS_ASSERT(!fp->hasImacropc()); /* If there's an exception and a handler, set the pc and leave. */ if (cx->isExceptionPending()) { jsbytecode *pc = FindExceptionHandler(cx); if (pc) { cx->regs->pc = pc; returnOK = true; break; } } /* Don't unwind if this was the entry frame. */ if (fp == stopFp) break; /* Unwind and return. */ returnOK &= bool(js_UnwindScope(cx, 0, returnOK || cx->isExceptionPending())); returnOK = ScriptEpilogue(cx, fp, returnOK); InlineReturn(f); } JS_ASSERT(&f.regs == cx->regs); JS_ASSERT_IF(!returnOK, cx->fp() == stopFp); return returnOK; }
extern "C" void * js_InternalThrow(VMFrame &f) { JSContext *cx = f.cx; // It's possible that from within RunTracer(), Interpret() returned with // an error and finished the frame (i.e., called ScriptEpilogue), but has // not yet performed an inline return. // // In this case, RunTracer() has no choice but to propagate the error // up to the method JIT, and thus to this function. But ScriptEpilogue() // has already been called. Detect this, and avoid double-finishing the // frame. See HandleErrorInExcessFrame() and bug 624100. if (f.fp()->finishedInInterpreter()) { // If it's the last frame, just propagate the failure up again. if (f.fp() == f.entryfp) return NULL; InlineReturn(f); } // Make sure sp is up to date. JS_ASSERT(cx->regs == &f.regs); // Call the throw hook if necessary JSThrowHook handler = f.cx->debugHooks->throwHook; if (handler) { Value rval; switch (handler(cx, cx->fp()->script(), cx->regs->pc, Jsvalify(&rval), cx->debugHooks->throwHookData)) { case JSTRAP_ERROR: cx->clearPendingException(); return NULL; case JSTRAP_RETURN: cx->clearPendingException(); cx->fp()->setReturnValue(rval); return JS_FUNC_TO_DATA_PTR(void *, cx->jaegerCompartment()->forceReturnTrampoline()); case JSTRAP_THROW: cx->setPendingException(rval); break; default: break; } } jsbytecode *pc = NULL; for (;;) { pc = FindExceptionHandler(cx); if (pc) break; // The JIT guarantees that ScriptEpilogue() has always been run // upon exiting to its caller. This is important for consistency, // where execution modes make similar guarantees about prologues // and epilogues. RunTracer(), Interpret(), and Invoke() all // rely on this property. JS_ASSERT(!f.fp()->finishedInInterpreter()); js_UnwindScope(cx, 0, cx->isExceptionPending()); ScriptEpilogue(f.cx, f.fp(), false); // Don't remove the last frame, this is the responsibility of // JaegerShot()'s caller. We only guarantee that ScriptEpilogue() // has been run. if (f.entryfp == f.fp()) break; JS_ASSERT(f.regs.sp == cx->regs->sp); InlineReturn(f); } JS_ASSERT(f.regs.sp == cx->regs->sp); if (!pc) return NULL; JSStackFrame *fp = cx->fp(); JSScript *script = fp->script(); return script->nativeCodeForPC(fp->isConstructing(), pc); }
uint32 ion::ThunkToInterpreter(Value *vp) { JSContext *cx = GetIonContext()->cx; IonActivation *activation = cx->runtime->ionActivation; BailoutClosure *br = activation->takeBailout(); if (!EnsureHasCallObject(cx, cx->fp())) return Interpret_Error; // By default we set the forbidOsr flag on the ion script, but if a GC // happens just after we re-enter the interpreter, the ion script get // invalidated and we do not see the forbidOsr flag. This may cause a loop // which apear with eager compilation and gc zeal enabled. This code is a // workaround to avoid recompiling with OSR just after a bailout followed by // a GC. (see Bug 746691 & Bug 751383) jsbytecode *pc = cx->regs().pc; while (JSOp(*pc) == JSOP_GOTO) pc += GET_JUMP_OFFSET(pc); if (JSOp(*pc) == JSOP_LOOPENTRY) cx->regs().pc = GetNextPc(pc); // When JSScript::argumentsOptimizationFailed, we cannot access Ion frames // in order to create an arguments object for them. However, there is an // invariant that script->needsArgsObj() implies fp->hasArgsObj() (after the // prologue), so we must create one now for each inlined frame which needs // one. { br->entryfp()->clearRunningInIon(); ScriptFrameIter iter(cx); StackFrame *fp = NULL; Rooted<JSScript*> script(cx, NULL); do { fp = iter.interpFrame(); script = iter.script(); if (script->needsArgsObj()) { // Currently IonMonkey does not compile if the script needs an // arguments object, so the frame should not have any argument // object yet. JS_ASSERT(!fp->hasArgsObj()); ArgumentsObject *argsobj = ArgumentsObject::createExpected(cx, fp); if (!argsobj) return Interpret_Error; InternalBindingsHandle bindings(script, &script->bindings); const unsigned var = Bindings::argumentsVarIndex(cx, bindings); // The arguments is a local binding and needsArgsObj does not // check if it is clobbered. Ensure that the local binding // restored during bailout before storing the arguments object // to the slot. if (fp->unaliasedLocal(var).isMagic(JS_OPTIMIZED_ARGUMENTS)) fp->unaliasedLocal(var) = ObjectValue(*argsobj); } ++iter; } while (fp != br->entryfp()); } if (activation->entryfp() == br->entryfp()) { // If the bailout entry fp is the same as the activation entryfp, then // there are no scripted frames below us. In this case, just shortcut // out with a special return code, and resume interpreting in the // original Interpret activation. vp->setMagic(JS_ION_BAILOUT); js_delete(br); return Interpret_Ok; } InterpretStatus status = Interpret(cx, br->entryfp(), JSINTERP_BAILOUT); if (status == Interpret_OSR) { // The interpreter currently does not ask to perform inline OSR, so // this path is unreachable. JS_NOT_REACHED("invalid"); IonSpew(IonSpew_Bailouts, "Performing inline OSR %s:%d", cx->fp()->script()->filename, PCToLineNumber(cx->fp()->script(), cx->regs().pc)); // We want to OSR again. We need to avoid the problem where frequent // bailouts cause recursive nestings of Interpret and EnterIon. The // interpreter therefore shortcuts out, and now we're responsible for // completing the OSR inline. // // Note that we set runningInIon so that if we re-enter C++ from within // the inlined OSR, StackIter will know to traverse these frames. StackFrame *fp = cx->fp(); fp->setRunningInIon(); vp->setPrivate(fp); js_delete(br); return Interpret_OSR; } if (status == Interpret_Ok) *vp = br->entryfp()->returnValue(); // The BailoutFrameGuard's destructor will ensure that the frame is // removed. js_delete(br); return status; }
extern "C" void * js_InternalThrow(VMFrame &f) { JSContext *cx = f.cx; ExpandInlineFrames(cx->compartment); // The current frame may have an associated orphaned native, if the native // or SplatApplyArgs threw an exception. RemoveOrphanedNative(cx, f.fp()); JS_ASSERT(!f.fp()->finishedInInterpreter()); // Make sure sp is up to date. JS_ASSERT(&cx->regs() == &f.regs); jsbytecode *pc = NULL; for (;;) { if (cx->isExceptionPending()) { // Call the throw hook if necessary JSThrowHook handler = cx->debugHooks->throwHook; if (handler || !cx->compartment->getDebuggees().empty()) { Value rval; JSTrapStatus st = Debugger::onExceptionUnwind(cx, &rval); if (st == JSTRAP_CONTINUE && handler) { st = handler(cx, cx->fp()->script(), cx->regs().pc, &rval, cx->debugHooks->throwHookData); } switch (st) { case JSTRAP_ERROR: cx->clearPendingException(); return NULL; case JSTRAP_CONTINUE: break; case JSTRAP_RETURN: cx->clearPendingException(); cx->fp()->setReturnValue(rval); return cx->jaegerCompartment()->forceReturnFromExternC(); case JSTRAP_THROW: cx->setPendingException(rval); break; default: JS_NOT_REACHED("bad onExceptionUnwind status"); } } } pc = FindExceptionHandler(cx); if (pc) break; // The JIT guarantees that ScriptEpilogue() has always been run // upon exiting to its caller. This is important for consistency, // where execution modes make similar guarantees about prologues // and epilogues. RunTracer(), Interpret(), and Invoke() all // rely on this property. JS_ASSERT(!f.fp()->finishedInInterpreter()); UnwindScope(cx, 0, cx->isExceptionPending()); if (cx->compartment->debugMode()) js::ScriptDebugEpilogue(cx, f.fp(), false); ScriptEpilogue(f.cx, f.fp(), false); // Don't remove the last frame, this is the responsibility of // JaegerShot()'s caller. We only guarantee that ScriptEpilogue() // has been run. if (f.entryfp == f.fp()) break; JS_ASSERT(f.regs.sp == cx->regs().sp); InlineReturn(f); } JS_ASSERT(f.regs.sp == cx->regs().sp); if (!pc) return NULL; StackFrame *fp = cx->fp(); JSScript *script = fp->script(); /* * Fall back to EnterMethodJIT and finish the frame in the interpreter. * With type inference enabled, we may wipe out all JIT code on the * stack without patching ncode values to jump to the interpreter, and * thus can only enter JIT code via EnterMethodJIT (which overwrites * its entry frame's ncode). See ClearAllFrames. */ cx->compartment->jaegerCompartment()->setLastUnfinished(Jaeger_Unfinished); if (!script->ensureRanAnalysis(cx, NULL)) { js_ReportOutOfMemory(cx); return NULL; } analyze::AutoEnterAnalysis enter(cx); cx->regs().pc = pc; cx->regs().sp = fp->base() + script->analysis()->getCode(pc).stackDepth; /* * Interpret the ENTERBLOCK and EXCEPTION opcodes, so that we don't go * back into the interpreter with a pending exception. This will cause * it to immediately rethrow. */ if (cx->isExceptionPending()) { JS_ASSERT(JSOp(*pc) == JSOP_ENTERBLOCK); JSObject *obj = script->getObject(GET_SLOTNO(pc)); Value *vp = cx->regs().sp + OBJ_BLOCK_COUNT(cx, obj); SetValueRangeToUndefined(cx->regs().sp, vp); cx->regs().sp = vp; JS_ASSERT(JSOp(pc[JSOP_ENTERBLOCK_LENGTH]) == JSOP_EXCEPTION); cx->regs().sp[0] = cx->getPendingException(); cx->clearPendingException(); cx->regs().sp++; cx->regs().pc = pc + JSOP_ENTERBLOCK_LENGTH + JSOP_EXCEPTION_LENGTH; } *f.oldregs = f.regs; return NULL; }