michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "jit/BaselineDebugModeOSR.h" michael@0: michael@0: #include "mozilla/DebugOnly.h" michael@0: michael@0: #include "jit/IonLinker.h" michael@0: #include "jit/PerfSpewer.h" michael@0: michael@0: #include "jit/IonFrames-inl.h" michael@0: #include "vm/Stack-inl.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace js; michael@0: using namespace js::jit; michael@0: michael@0: struct DebugModeOSREntry michael@0: { michael@0: JSScript *script; michael@0: BaselineScript *oldBaselineScript; michael@0: BaselineDebugModeOSRInfo *recompInfo; michael@0: uint32_t pcOffset; michael@0: ICEntry::Kind frameKind; michael@0: michael@0: // Used for sanity asserts in debug builds. michael@0: DebugOnly stub; michael@0: michael@0: DebugModeOSREntry(JSScript *script) michael@0: : script(script), michael@0: oldBaselineScript(script->baselineScript()), michael@0: recompInfo(nullptr), michael@0: pcOffset(uint32_t(-1)), michael@0: frameKind(ICEntry::Kind_NonOp), michael@0: stub(nullptr) michael@0: { } michael@0: michael@0: DebugModeOSREntry(JSScript *script, const ICEntry &icEntry) michael@0: : script(script), michael@0: oldBaselineScript(script->baselineScript()), michael@0: recompInfo(nullptr), michael@0: pcOffset(icEntry.pcOffset()), michael@0: frameKind(icEntry.kind()), michael@0: stub(nullptr) michael@0: { michael@0: #ifdef DEBUG michael@0: MOZ_ASSERT(pcOffset == icEntry.pcOffset()); michael@0: MOZ_ASSERT(frameKind == icEntry.kind()); michael@0: michael@0: // Assert that if we have a NonOp ICEntry, that there are no unsynced michael@0: // slots, since such a recompile could have only been triggered from michael@0: // either an interrupt check or a debug trap handler. michael@0: // michael@0: // If triggered from an interrupt check, the stack should be fully michael@0: // synced. michael@0: // michael@0: // If triggered from a debug trap handler, we must be recompiling for michael@0: // toggling debug mode on->off, in which case the old baseline script michael@0: // should have fully synced stack at every bytecode. michael@0: if (frameKind == ICEntry::Kind_NonOp) { michael@0: PCMappingSlotInfo slotInfo; michael@0: jsbytecode *pc = script->offsetToPC(pcOffset); michael@0: oldBaselineScript->nativeCodeForPC(script, pc, &slotInfo); michael@0: MOZ_ASSERT(slotInfo.numUnsynced() == 0); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: DebugModeOSREntry(DebugModeOSREntry &&other) michael@0: : script(other.script), michael@0: oldBaselineScript(other.oldBaselineScript), michael@0: recompInfo(other.recompInfo ? other.takeRecompInfo() : nullptr), michael@0: pcOffset(other.pcOffset), michael@0: frameKind(other.frameKind), michael@0: stub(other.stub) michael@0: { } michael@0: michael@0: ~DebugModeOSREntry() { michael@0: // Note that this is nulled out when the recompInfo is taken by the michael@0: // frame. The frame then has the responsibility of freeing the michael@0: // recompInfo. michael@0: js_delete(recompInfo); michael@0: } michael@0: michael@0: bool needsRecompileInfo() const { michael@0: return (frameKind == ICEntry::Kind_CallVM || michael@0: frameKind == ICEntry::Kind_DebugTrap || michael@0: frameKind == ICEntry::Kind_DebugPrologue || michael@0: frameKind == ICEntry::Kind_DebugEpilogue); michael@0: } michael@0: michael@0: BaselineDebugModeOSRInfo *takeRecompInfo() { michael@0: MOZ_ASSERT(recompInfo); michael@0: BaselineDebugModeOSRInfo *tmp = recompInfo; michael@0: recompInfo = nullptr; michael@0: return tmp; michael@0: } michael@0: michael@0: bool allocateRecompileInfo(JSContext *cx) { michael@0: MOZ_ASSERT(needsRecompileInfo()); michael@0: michael@0: // If we are returning to a frame which needs a continuation fixer, michael@0: // allocate the recompile info up front so that the patching function michael@0: // is infallible. michael@0: jsbytecode *pc = script->offsetToPC(pcOffset); michael@0: michael@0: // XXX: Work around compiler error disallowing using bitfields michael@0: // with the template magic of new_. michael@0: ICEntry::Kind kind = frameKind; michael@0: recompInfo = cx->new_(pc, kind); michael@0: return !!recompInfo; michael@0: } michael@0: }; michael@0: michael@0: typedef js::Vector DebugModeOSREntryVector; michael@0: michael@0: static bool michael@0: CollectOnStackScripts(JSContext *cx, const JitActivationIterator &activation, michael@0: DebugModeOSREntryVector &entries) michael@0: { michael@0: DebugOnly prevFrameStubPtr = nullptr; michael@0: bool needsRecompileHandler = false; michael@0: for (JitFrameIterator iter(activation); !iter.done(); ++iter) { michael@0: switch (iter.type()) { michael@0: case JitFrame_BaselineJS: { michael@0: JSScript *script = iter.script(); michael@0: uint8_t *retAddr = iter.returnAddressToFp(); michael@0: ICEntry &entry = script->baselineScript()->icEntryFromReturnAddress(retAddr); michael@0: michael@0: if (!entries.append(DebugModeOSREntry(script, entry))) michael@0: return false; michael@0: michael@0: if (entries.back().needsRecompileInfo()) { michael@0: if (!entries.back().allocateRecompileInfo(cx)) michael@0: return false; michael@0: michael@0: needsRecompileHandler |= true; michael@0: } michael@0: michael@0: entries.back().stub = prevFrameStubPtr; michael@0: prevFrameStubPtr = nullptr; michael@0: break; michael@0: } michael@0: michael@0: case JitFrame_BaselineStub: michael@0: prevFrameStubPtr = michael@0: reinterpret_cast(iter.fp())->maybeStubPtr(); michael@0: break; michael@0: michael@0: case JitFrame_IonJS: { michael@0: JSScript *script = iter.script(); michael@0: if (!entries.append(DebugModeOSREntry(script))) michael@0: return false; michael@0: for (InlineFrameIterator inlineIter(cx, &iter); inlineIter.more(); ++inlineIter) { michael@0: if (!entries.append(DebugModeOSREntry(inlineIter.script()))) michael@0: return false; michael@0: } michael@0: break; michael@0: } michael@0: michael@0: default:; michael@0: } michael@0: } michael@0: michael@0: // Initialize the on-stack recompile handler, which may fail, so that michael@0: // patching the stack is infallible. michael@0: if (needsRecompileHandler) { michael@0: JitRuntime *rt = cx->runtime()->jitRuntime(); michael@0: if (!rt->getBaselineDebugModeOSRHandlerAddress(cx, true)) michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static inline uint8_t * michael@0: GetStubReturnFromStubAddress(JSContext *cx, jsbytecode *pc) michael@0: { michael@0: JitCompartment *comp = cx->compartment()->jitCompartment(); michael@0: void *addr; michael@0: if (IsGetPropPC(pc)) { michael@0: addr = comp->baselineGetPropReturnFromStubAddr(); michael@0: } else if (IsSetPropPC(pc)) { michael@0: addr = comp->baselineSetPropReturnFromStubAddr(); michael@0: } else { michael@0: JS_ASSERT(IsCallPC(pc)); michael@0: addr = comp->baselineCallReturnFromStubAddr(); michael@0: } michael@0: return reinterpret_cast(addr); michael@0: } michael@0: michael@0: static const char * michael@0: ICEntryKindToString(ICEntry::Kind kind) michael@0: { michael@0: switch (kind) { michael@0: case ICEntry::Kind_Op: michael@0: return "IC"; michael@0: case ICEntry::Kind_NonOp: michael@0: return "non-op IC"; michael@0: case ICEntry::Kind_CallVM: michael@0: return "callVM"; michael@0: case ICEntry::Kind_DebugTrap: michael@0: return "debug trap"; michael@0: case ICEntry::Kind_DebugPrologue: michael@0: return "debug prologue"; michael@0: case ICEntry::Kind_DebugEpilogue: michael@0: return "debug epilogue"; michael@0: default: michael@0: MOZ_ASSUME_UNREACHABLE("bad ICEntry kind"); michael@0: } michael@0: } michael@0: michael@0: static void michael@0: SpewPatchBaselineFrame(uint8_t *oldReturnAddress, uint8_t *newReturnAddress, michael@0: JSScript *script, ICEntry::Kind frameKind, jsbytecode *pc) michael@0: { michael@0: IonSpew(IonSpew_BaselineDebugModeOSR, michael@0: "Patch return %#016llx -> %#016llx to BaselineJS (%s:%d) from %s at %s", michael@0: uintptr_t(oldReturnAddress), uintptr_t(newReturnAddress), michael@0: script->filename(), script->lineno(), michael@0: ICEntryKindToString(frameKind), js_CodeName[(JSOp)*pc]); michael@0: } michael@0: michael@0: static void michael@0: SpewPatchStubFrame(uint8_t *oldReturnAddress, uint8_t *newReturnAddress, michael@0: ICStub *oldStub, ICStub *newStub) michael@0: { michael@0: IonSpew(IonSpew_BaselineDebugModeOSR, michael@0: "Patch return %#016llx -> %#016llx", michael@0: uintptr_t(oldReturnAddress), uintptr_t(newReturnAddress)); michael@0: IonSpew(IonSpew_BaselineDebugModeOSR, michael@0: "Patch stub %#016llx -> %#016llx to BaselineStub", michael@0: uintptr_t(oldStub), uintptr_t(newStub)); michael@0: } michael@0: michael@0: static void michael@0: PatchBaselineFramesForDebugMode(JSContext *cx, const JitActivationIterator &activation, michael@0: DebugModeOSREntryVector &entries, size_t *start) michael@0: { michael@0: // michael@0: // Recompile Patching Overview michael@0: // michael@0: // When toggling debug mode with live baseline scripts on the stack, we michael@0: // could have entered the VM via the following ways from the baseline michael@0: // script. michael@0: // michael@0: // Off to On: michael@0: // A. From a "can call" stub. michael@0: // B. From a VM call (interrupt handler, debugger statement handler). michael@0: // michael@0: // On to Off: michael@0: // - All the ways above. michael@0: // C. From the debug trap handler. michael@0: // D. From the debug prologue. michael@0: // E. From the debug epilogue. michael@0: // michael@0: // In general, we patch the return address from the VM call to return to a michael@0: // "continuation fixer" to fix up machine state (registers and stack michael@0: // state). Specifics on what need to be done are documented below. michael@0: // michael@0: michael@0: IonCommonFrameLayout *prev = nullptr; michael@0: size_t entryIndex = *start; michael@0: DebugOnly expectedDebugMode = cx->compartment()->debugMode(); michael@0: michael@0: for (JitFrameIterator iter(activation); !iter.done(); ++iter) { michael@0: switch (iter.type()) { michael@0: case JitFrame_BaselineJS: { michael@0: JSScript *script = entries[entryIndex].script; michael@0: uint32_t pcOffset = entries[entryIndex].pcOffset; michael@0: jsbytecode *pc = script->offsetToPC(pcOffset); michael@0: michael@0: MOZ_ASSERT(script == iter.script()); michael@0: MOZ_ASSERT(pcOffset < script->length()); michael@0: MOZ_ASSERT(script->baselineScript()->debugMode() == expectedDebugMode); michael@0: michael@0: BaselineScript *bl = script->baselineScript(); michael@0: ICEntry::Kind kind = entries[entryIndex].frameKind; michael@0: michael@0: if (kind == ICEntry::Kind_Op) { michael@0: // Case A above. michael@0: // michael@0: // Patching this case needs to patch both the stub frame and michael@0: // the baseline frame. The stub frame is patched below. For michael@0: // the baseline frame here, we resume right after the IC michael@0: // returns. michael@0: // michael@0: // Since we're using the IC-specific k-fixer, we can resume michael@0: // directly to the IC resume address. michael@0: uint8_t *retAddr = bl->returnAddressForIC(bl->icEntryFromPCOffset(pcOffset)); michael@0: SpewPatchBaselineFrame(prev->returnAddress(), retAddr, script, kind, pc); michael@0: prev->setReturnAddress(retAddr); michael@0: entryIndex++; michael@0: break; michael@0: } michael@0: michael@0: bool popFrameReg; michael@0: michael@0: // The RecompileInfo must already be allocated so that this michael@0: // function may be infallible. michael@0: BaselineDebugModeOSRInfo *recompInfo = entries[entryIndex].takeRecompInfo(); michael@0: michael@0: switch (kind) { michael@0: case ICEntry::Kind_CallVM: michael@0: // Case B above. michael@0: // michael@0: // Patching returns from an interrupt handler or the debugger michael@0: // statement handler is similar in that we can resume at the michael@0: // next op. michael@0: pc += GetBytecodeLength(pc); michael@0: recompInfo->resumeAddr = bl->nativeCodeForPC(script, pc, &recompInfo->slotInfo); michael@0: popFrameReg = true; michael@0: break; michael@0: michael@0: case ICEntry::Kind_DebugTrap: michael@0: // Case C above. michael@0: // michael@0: // Debug traps are emitted before each op, so we resume at the michael@0: // same op. Calling debug trap handlers is done via a toggled michael@0: // call to a thunk (DebugTrapHandler) that takes care tearing michael@0: // down its own stub frame so we don't need to worry about michael@0: // popping the frame reg. michael@0: recompInfo->resumeAddr = bl->nativeCodeForPC(script, pc, &recompInfo->slotInfo); michael@0: popFrameReg = false; michael@0: break; michael@0: michael@0: case ICEntry::Kind_DebugPrologue: michael@0: // Case D above. michael@0: // michael@0: // We patch a jump directly to the right place in the prologue michael@0: // after popping the frame reg and checking for forced return. michael@0: recompInfo->resumeAddr = bl->postDebugPrologueAddr(); michael@0: popFrameReg = true; michael@0: break; michael@0: michael@0: default: michael@0: // Case E above. michael@0: // michael@0: // We patch a jump directly to the epilogue after popping the michael@0: // frame reg and checking for forced return. michael@0: MOZ_ASSERT(kind == ICEntry::Kind_DebugEpilogue); michael@0: recompInfo->resumeAddr = bl->epilogueEntryAddr(); michael@0: popFrameReg = true; michael@0: break; michael@0: } michael@0: michael@0: SpewPatchBaselineFrame(prev->returnAddress(), recompInfo->resumeAddr, michael@0: script, kind, recompInfo->pc); michael@0: michael@0: // The recompile handler must already be created so that this michael@0: // function may be infallible. michael@0: JitRuntime *rt = cx->runtime()->jitRuntime(); michael@0: void *handlerAddr = rt->getBaselineDebugModeOSRHandlerAddress(cx, popFrameReg); michael@0: MOZ_ASSERT(handlerAddr); michael@0: michael@0: prev->setReturnAddress(reinterpret_cast(handlerAddr)); michael@0: iter.baselineFrame()->setDebugModeOSRInfo(recompInfo); michael@0: michael@0: entryIndex++; michael@0: break; michael@0: } michael@0: michael@0: case JitFrame_BaselineStub: { michael@0: JSScript *script = entries[entryIndex].script; michael@0: IonBaselineStubFrameLayout *layout = michael@0: reinterpret_cast(iter.fp()); michael@0: MOZ_ASSERT(script->baselineScript()->debugMode() == expectedDebugMode); michael@0: MOZ_ASSERT(layout->maybeStubPtr() == entries[entryIndex].stub); michael@0: michael@0: // Patch baseline stub frames for case A above. michael@0: // michael@0: // We need to patch the stub frame return address to go to the michael@0: // k-fixer that is at the end of fallback stubs of all such michael@0: // can-call ICs. These k-fixers share code with bailout-from-Ion michael@0: // fixers, but in this case we are returning from VM and not michael@0: // Ion. See e.g., JitCompartment::baselineCallReturnFromStubAddr() michael@0: // michael@0: // Subtlety here: the debug trap handler of case C above pushes a michael@0: // stub frame with a null stub pointer. This handler will exist michael@0: // across recompiling the script, so we don't patch anything for michael@0: // such stub frames. We will return to that handler, which takes michael@0: // care of cleaning up the stub frame. michael@0: // michael@0: // Note that for stub pointers that are already on the C stack michael@0: // (i.e. fallback calls), we need to check for recompilation using michael@0: // DebugModeOSRVolatileStub. michael@0: if (layout->maybeStubPtr()) { michael@0: MOZ_ASSERT(layout->maybeStubPtr() == entries[entryIndex].stub); michael@0: uint32_t pcOffset = entries[entryIndex].pcOffset; michael@0: uint8_t *retAddr = GetStubReturnFromStubAddress(cx, script->offsetToPC(pcOffset)); michael@0: michael@0: // Get the fallback stub for the IC in the recompiled michael@0: // script. The fallback stub is guaranteed to exist. michael@0: ICEntry &entry = script->baselineScript()->icEntryFromPCOffset(pcOffset); michael@0: ICStub *newStub = entry.fallbackStub(); michael@0: SpewPatchStubFrame(prev->returnAddress(), retAddr, layout->maybeStubPtr(), newStub); michael@0: prev->setReturnAddress(retAddr); michael@0: layout->setStubPtr(newStub); michael@0: } michael@0: michael@0: break; michael@0: } michael@0: michael@0: case JitFrame_IonJS: michael@0: // Nothing to patch. michael@0: entryIndex++; michael@0: for (InlineFrameIterator inlineIter(cx, &iter); inlineIter.more(); ++inlineIter) michael@0: entryIndex++; michael@0: break; michael@0: michael@0: default:; michael@0: } michael@0: michael@0: prev = iter.current(); michael@0: } michael@0: michael@0: *start = entryIndex; michael@0: } michael@0: michael@0: static bool michael@0: RecompileBaselineScriptForDebugMode(JSContext *cx, JSScript *script) michael@0: { michael@0: BaselineScript *oldBaselineScript = script->baselineScript(); michael@0: michael@0: // If a script is on the stack multiple times, it may have already michael@0: // been recompiled. michael@0: bool expectedDebugMode = cx->compartment()->debugMode(); michael@0: if (oldBaselineScript->debugMode() == expectedDebugMode) michael@0: return true; michael@0: michael@0: IonSpew(IonSpew_BaselineDebugModeOSR, "Recompiling (%s:%d) for debug mode %s", michael@0: script->filename(), script->lineno(), expectedDebugMode ? "ON" : "OFF"); michael@0: michael@0: if (script->hasIonScript()) michael@0: Invalidate(cx, script, /* resetUses = */ false); michael@0: michael@0: script->setBaselineScript(cx, nullptr); michael@0: michael@0: MethodStatus status = BaselineCompile(cx, script); michael@0: if (status != Method_Compiled) { michael@0: // We will only fail to recompile for debug mode due to OOM. Restore michael@0: // the old baseline script in case something doesn't properly michael@0: // propagate OOM. michael@0: MOZ_ASSERT(status == Method_Error); michael@0: script->setBaselineScript(cx, oldBaselineScript); michael@0: return false; michael@0: } michael@0: michael@0: // Don't destroy the old baseline script yet, since if we fail any of the michael@0: // recompiles we need to rollback all the old baseline scripts. michael@0: MOZ_ASSERT(script->baselineScript()->debugMode() == expectedDebugMode); michael@0: return true; michael@0: } michael@0: michael@0: static void michael@0: UndoRecompileBaselineScriptsForDebugMode(JSContext *cx, michael@0: const DebugModeOSREntryVector &entries) michael@0: { michael@0: // In case of failure, roll back the entire set of active scripts so that michael@0: // we don't have to patch return addresses on the stack. michael@0: for (size_t i = 0; i < entries.length(); i++) { michael@0: JSScript *script = entries[i].script; michael@0: BaselineScript *baselineScript = script->baselineScript(); michael@0: if (baselineScript != entries[i].oldBaselineScript) { michael@0: script->setBaselineScript(cx, entries[i].oldBaselineScript); michael@0: BaselineScript::Destroy(cx->runtime()->defaultFreeOp(), baselineScript); michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool michael@0: jit::RecompileOnStackBaselineScriptsForDebugMode(JSContext *cx, JSCompartment *comp) michael@0: { michael@0: AutoCompartment ac(cx, comp); michael@0: michael@0: // First recompile the active scripts on the stack and patch the live michael@0: // frames. michael@0: Vector entries(cx); michael@0: michael@0: for (JitActivationIterator iter(cx->runtime()); !iter.done(); ++iter) { michael@0: if (iter.activation()->compartment() == comp) { michael@0: if (!CollectOnStackScripts(cx, iter, entries)) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: #ifdef JSGC_GENERATIONAL michael@0: // Scripts can entrain nursery things. See note in js::ReleaseAllJITCode. michael@0: if (!entries.empty()) michael@0: MinorGC(cx->runtime(), JS::gcreason::EVICT_NURSERY); michael@0: #endif michael@0: michael@0: // Try to recompile all the scripts. If we encounter an error, we need to michael@0: // roll back as if none of the compilations happened, so that we don't michael@0: // crash. michael@0: for (size_t i = 0; i < entries.length(); i++) { michael@0: JSScript *script = entries[i].script; michael@0: if (!RecompileBaselineScriptForDebugMode(cx, script)) { michael@0: UndoRecompileBaselineScriptsForDebugMode(cx, entries); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: // If all recompiles succeeded, destroy the old baseline scripts and patch michael@0: // the live frames. michael@0: // michael@0: // After this point the function must be infallible. michael@0: michael@0: for (size_t i = 0; i < entries.length(); i++) michael@0: BaselineScript::Destroy(cx->runtime()->defaultFreeOp(), entries[i].oldBaselineScript); michael@0: michael@0: size_t processed = 0; michael@0: for (JitActivationIterator iter(cx->runtime()); !iter.done(); ++iter) { michael@0: if (iter.activation()->compartment() == comp) michael@0: PatchBaselineFramesForDebugMode(cx, iter, entries, &processed); michael@0: } michael@0: MOZ_ASSERT(processed == entries.length()); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: BaselineDebugModeOSRInfo::popValueInto(PCMappingSlotInfo::SlotLocation loc, Value *vp) michael@0: { michael@0: switch (loc) { michael@0: case PCMappingSlotInfo::SlotInR0: michael@0: valueR0 = vp[stackAdjust]; michael@0: break; michael@0: case PCMappingSlotInfo::SlotInR1: michael@0: valueR1 = vp[stackAdjust]; michael@0: break; michael@0: case PCMappingSlotInfo::SlotIgnore: michael@0: break; michael@0: default: michael@0: MOZ_ASSUME_UNREACHABLE("Bad slot location"); michael@0: } michael@0: michael@0: stackAdjust++; michael@0: } michael@0: michael@0: static inline bool michael@0: HasForcedReturn(BaselineDebugModeOSRInfo *info, bool rv) michael@0: { michael@0: ICEntry::Kind kind = info->frameKind; michael@0: michael@0: // The debug epilogue always checks its resumption value, so we don't need michael@0: // to check rv. michael@0: if (kind == ICEntry::Kind_DebugEpilogue) michael@0: return true; michael@0: michael@0: // |rv| is the value in ReturnReg. If true, in the case of the prologue, michael@0: // debug trap, and debugger statement handler, it means a forced return. michael@0: if (kind == ICEntry::Kind_DebugPrologue || michael@0: (kind == ICEntry::Kind_CallVM && JSOp(*info->pc) == JSOP_DEBUGGER)) michael@0: { michael@0: return rv; michael@0: } michael@0: michael@0: // N.B. The debug trap handler handles its own forced return, so no michael@0: // need to deal with it here. michael@0: return false; michael@0: } michael@0: michael@0: static void michael@0: SyncBaselineDebugModeOSRInfo(BaselineFrame *frame, Value *vp, bool rv) michael@0: { michael@0: BaselineDebugModeOSRInfo *info = frame->debugModeOSRInfo(); michael@0: MOZ_ASSERT(info); michael@0: MOZ_ASSERT(frame->script()->baselineScript()->containsCodeAddress(info->resumeAddr)); michael@0: michael@0: if (HasForcedReturn(info, rv)) { michael@0: // Load the frame's rval and overwrite the resume address to go to the michael@0: // epilogue. michael@0: MOZ_ASSERT(R0 == JSReturnOperand); michael@0: info->valueR0 = frame->returnValue(); michael@0: info->resumeAddr = frame->script()->baselineScript()->epilogueEntryAddr(); michael@0: return; michael@0: } michael@0: michael@0: // Read stack values and make sure R0 and R1 have the right values. michael@0: unsigned numUnsynced = info->slotInfo.numUnsynced(); michael@0: MOZ_ASSERT(numUnsynced <= 2); michael@0: if (numUnsynced > 0) michael@0: info->popValueInto(info->slotInfo.topSlotLocation(), vp); michael@0: if (numUnsynced > 1) michael@0: info->popValueInto(info->slotInfo.nextSlotLocation(), vp); michael@0: michael@0: // Scale stackAdjust. michael@0: info->stackAdjust *= sizeof(Value); michael@0: } michael@0: michael@0: static void michael@0: FinishBaselineDebugModeOSR(BaselineFrame *frame) michael@0: { michael@0: frame->deleteDebugModeOSRInfo(); michael@0: } michael@0: michael@0: void michael@0: BaselineFrame::deleteDebugModeOSRInfo() michael@0: { michael@0: js_delete(getDebugModeOSRInfo()); michael@0: flags_ &= ~HAS_DEBUG_MODE_OSR_INFO; michael@0: } michael@0: michael@0: JitCode * michael@0: JitRuntime::getBaselineDebugModeOSRHandler(JSContext *cx) michael@0: { michael@0: if (!baselineDebugModeOSRHandler_) { michael@0: AutoLockForExclusiveAccess lock(cx); michael@0: AutoCompartment ac(cx, cx->runtime()->atomsCompartment()); michael@0: uint32_t offset; michael@0: if (JitCode *code = generateBaselineDebugModeOSRHandler(cx, &offset)) { michael@0: baselineDebugModeOSRHandler_ = code; michael@0: baselineDebugModeOSRHandlerNoFrameRegPopAddr_ = code->raw() + offset; michael@0: } michael@0: } michael@0: michael@0: return baselineDebugModeOSRHandler_; michael@0: } michael@0: michael@0: void * michael@0: JitRuntime::getBaselineDebugModeOSRHandlerAddress(JSContext *cx, bool popFrameReg) michael@0: { michael@0: if (!getBaselineDebugModeOSRHandler(cx)) michael@0: return nullptr; michael@0: return (popFrameReg michael@0: ? baselineDebugModeOSRHandler_->raw() michael@0: : baselineDebugModeOSRHandlerNoFrameRegPopAddr_); michael@0: } michael@0: michael@0: JitCode * michael@0: JitRuntime::generateBaselineDebugModeOSRHandler(JSContext *cx, uint32_t *noFrameRegPopOffsetOut) michael@0: { michael@0: MacroAssembler masm(cx); michael@0: michael@0: GeneralRegisterSet regs(GeneralRegisterSet::All()); michael@0: regs.take(BaselineFrameReg); michael@0: regs.take(ReturnReg); michael@0: Register temp = regs.takeAny(); michael@0: Register syncedStackStart = regs.takeAny(); michael@0: michael@0: // Pop the frame reg. michael@0: masm.pop(BaselineFrameReg); michael@0: michael@0: // Not all patched baseline frames are returning from a situation where michael@0: // the frame reg is already fixed up. michael@0: CodeOffsetLabel noFrameRegPopOffset = masm.currentOffset(); michael@0: michael@0: // Record the stack pointer for syncing. michael@0: masm.movePtr(StackPointer, syncedStackStart); michael@0: masm.push(BaselineFrameReg); michael@0: michael@0: // Call a stub to fully initialize the info. michael@0: masm.setupUnalignedABICall(3, temp); michael@0: masm.loadBaselineFramePtr(BaselineFrameReg, temp); michael@0: masm.passABIArg(temp); michael@0: masm.passABIArg(syncedStackStart); michael@0: masm.passABIArg(ReturnReg); michael@0: masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, SyncBaselineDebugModeOSRInfo)); michael@0: michael@0: // Discard stack values depending on how many were unsynced, as we always michael@0: // have a fully synced stack in the recompile handler. See assert in michael@0: // DebugModeOSREntry constructor. michael@0: masm.pop(BaselineFrameReg); michael@0: masm.loadPtr(Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfScratchValue()), temp); michael@0: masm.addPtr(Address(temp, offsetof(BaselineDebugModeOSRInfo, stackAdjust)), StackPointer); michael@0: michael@0: // Save real return address on the stack temporarily. michael@0: masm.pushValue(Address(temp, offsetof(BaselineDebugModeOSRInfo, valueR0))); michael@0: masm.pushValue(Address(temp, offsetof(BaselineDebugModeOSRInfo, valueR1))); michael@0: masm.push(BaselineFrameReg); michael@0: masm.push(Address(temp, offsetof(BaselineDebugModeOSRInfo, resumeAddr))); michael@0: michael@0: // Call a stub to free the allocated info. michael@0: masm.setupUnalignedABICall(1, temp); michael@0: masm.loadBaselineFramePtr(BaselineFrameReg, temp); michael@0: masm.passABIArg(temp); michael@0: masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, FinishBaselineDebugModeOSR)); michael@0: michael@0: // Restore saved values. michael@0: GeneralRegisterSet jumpRegs(GeneralRegisterSet::All()); michael@0: jumpRegs.take(R0); michael@0: jumpRegs.take(R1); michael@0: jumpRegs.take(BaselineFrameReg); michael@0: Register target = jumpRegs.takeAny(); michael@0: michael@0: masm.pop(target); michael@0: masm.pop(BaselineFrameReg); michael@0: masm.popValue(R1); michael@0: masm.popValue(R0); michael@0: michael@0: masm.jump(target); michael@0: michael@0: Linker linker(masm); michael@0: AutoFlushICache afc("BaselineDebugModeOSRHandler"); michael@0: JitCode *code = linker.newCode(cx, JSC::OTHER_CODE); michael@0: if (!code) michael@0: return nullptr; michael@0: michael@0: noFrameRegPopOffset.fixup(&masm); michael@0: *noFrameRegPopOffsetOut = noFrameRegPopOffset.offset(); michael@0: michael@0: #ifdef JS_ION_PERF michael@0: writePerfSpewerJitCodeProfile(code, "BaselineDebugModeOSRHandler"); michael@0: #endif michael@0: michael@0: return code; michael@0: }