blob: 4b7c212e5a6d2b3a66ab9614b654c17625ec4481 [file] [log] [blame]
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "jit/BaselineDebugModeOSR.h"
#include "mozilla/DebugOnly.h"
#include "jit/BaselineIC.h"
#include "jit/JitcodeMap.h"
#include "jit/Linker.h"
#include "jit/PerfSpewer.h"
#include "jit/JitFrames-inl.h"
#include "jit/MacroAssembler-inl.h"
#include "vm/Stack-inl.h"
using namespace js;
using namespace js::jit;
using mozilla::DebugOnly;
struct DebugModeOSREntry
{
JSScript* script;
BaselineScript* oldBaselineScript;
ICStub* oldStub;
ICStub* newStub;
BaselineDebugModeOSRInfo* recompInfo;
uint32_t pcOffset;
ICEntry::Kind frameKind;
explicit DebugModeOSREntry(JSScript* script)
: script(script),
oldBaselineScript(script->baselineScript()),
oldStub(nullptr),
newStub(nullptr),
recompInfo(nullptr),
pcOffset(uint32_t(-1)),
frameKind(ICEntry::Kind_Invalid)
{ }
DebugModeOSREntry(JSScript* script, uint32_t pcOffset)
: script(script),
oldBaselineScript(script->baselineScript()),
oldStub(nullptr),
newStub(nullptr),
recompInfo(nullptr),
pcOffset(pcOffset),
frameKind(ICEntry::Kind_Invalid)
{ }
DebugModeOSREntry(JSScript* script, const ICEntry& icEntry)
: script(script),
oldBaselineScript(script->baselineScript()),
oldStub(nullptr),
newStub(nullptr),
recompInfo(nullptr),
pcOffset(icEntry.pcOffset()),
frameKind(icEntry.kind())
{
#ifdef DEBUG
MOZ_ASSERT(pcOffset == icEntry.pcOffset());
MOZ_ASSERT(frameKind == icEntry.kind());
#endif
}
DebugModeOSREntry(JSScript* script, BaselineDebugModeOSRInfo* info)
: script(script),
oldBaselineScript(script->baselineScript()),
oldStub(nullptr),
newStub(nullptr),
recompInfo(nullptr),
pcOffset(script->pcToOffset(info->pc)),
frameKind(info->frameKind)
{
#ifdef DEBUG
MOZ_ASSERT(pcOffset == script->pcToOffset(info->pc));
MOZ_ASSERT(frameKind == info->frameKind);
#endif
}
DebugModeOSREntry(DebugModeOSREntry&& other)
: script(other.script),
oldBaselineScript(other.oldBaselineScript),
oldStub(other.oldStub),
newStub(other.newStub),
recompInfo(other.recompInfo ? other.takeRecompInfo() : nullptr),
pcOffset(other.pcOffset),
frameKind(other.frameKind)
{ }
~DebugModeOSREntry() {
// Note that this is nulled out when the recompInfo is taken by the
// frame. The frame then has the responsibility of freeing the
// recompInfo.
js_delete(recompInfo);
}
bool needsRecompileInfo() const {
return frameKind == ICEntry::Kind_CallVM ||
frameKind == ICEntry::Kind_StackCheck ||
frameKind == ICEntry::Kind_EarlyStackCheck ||
frameKind == ICEntry::Kind_DebugTrap ||
frameKind == ICEntry::Kind_DebugPrologue ||
frameKind == ICEntry::Kind_DebugEpilogue;
}
bool recompiled() const {
return oldBaselineScript != script->baselineScript();
}
BaselineDebugModeOSRInfo* takeRecompInfo() {
MOZ_ASSERT(needsRecompileInfo() && recompInfo);
BaselineDebugModeOSRInfo* tmp = recompInfo;
recompInfo = nullptr;
return tmp;
}
bool allocateRecompileInfo(JSContext* cx) {
MOZ_ASSERT(script);
MOZ_ASSERT(needsRecompileInfo());
// If we are returning to a frame which needs a continuation fixer,
// allocate the recompile info up front so that the patching function
// is infallible.
jsbytecode* pc = script->offsetToPC(pcOffset);
// XXX: Work around compiler error disallowing using bitfields
// with the template magic of new_.
ICEntry::Kind kind = frameKind;
recompInfo = cx->new_<BaselineDebugModeOSRInfo>(pc, kind);
return !!recompInfo;
}
ICFallbackStub* fallbackStub() const {
MOZ_ASSERT(script);
MOZ_ASSERT(oldStub);
return script->baselineScript()->icEntryFromPCOffset(pcOffset).fallbackStub();
}
};
typedef Vector<DebugModeOSREntry> DebugModeOSREntryVector;
class UniqueScriptOSREntryIter
{
const DebugModeOSREntryVector& entries_;
size_t index_;
public:
explicit UniqueScriptOSREntryIter(const DebugModeOSREntryVector& entries)
: entries_(entries),
index_(0)
{ }
bool done() {
return index_ == entries_.length();
}
const DebugModeOSREntry& entry() {
MOZ_ASSERT(!done());
return entries_[index_];
}
UniqueScriptOSREntryIter& operator++() {
MOZ_ASSERT(!done());
while (++index_ < entries_.length()) {
bool unique = true;
for (size_t i = 0; i < index_; i++) {
if (entries_[i].script == entries_[index_].script) {
unique = false;
break;
}
}
if (unique)
break;
}
return *this;
}
};
static bool
CollectJitStackScripts(JSContext* cx, const Debugger::ExecutionObservableSet& obs,
const ActivationIterator& activation, DebugModeOSREntryVector& entries)
{
ICStub* prevFrameStubPtr = nullptr;
bool needsRecompileHandler = false;
for (JitFrameIterator iter(activation); !iter.done(); ++iter) {
switch (iter.type()) {
case JitFrame_BaselineJS: {
JSScript* script = iter.script();
if (!obs.shouldRecompileOrInvalidate(script)) {
prevFrameStubPtr = nullptr;
break;
}
BaselineFrame* frame = iter.baselineFrame();
if (BaselineDebugModeOSRInfo* info = frame->getDebugModeOSRInfo()) {
// If patching a previously patched yet unpopped frame, we can
// use the BaselineDebugModeOSRInfo on the frame directly to
// patch. Indeed, we cannot use iter.returnAddressToFp(), as
// it points into the debug mode OSR handler and cannot be
// used to look up a corresponding ICEntry.
//
// See cases F and G in PatchBaselineFramesForDebugMode.
if (!entries.append(DebugModeOSREntry(script, info)))
return false;
} else if (frame->isHandlingException()) {
// We are in the middle of handling an exception and the frame
// must have an override pc.
uint32_t offset = script->pcToOffset(frame->overridePc());
if (!entries.append(DebugModeOSREntry(script, offset)))
return false;
} else {
// The frame must be settled on a pc with an ICEntry.
uint8_t* retAddr = iter.returnAddressToFp();
ICEntry& icEntry = script->baselineScript()->icEntryFromReturnAddress(retAddr);
if (!entries.append(DebugModeOSREntry(script, icEntry)))
return false;
}
if (entries.back().needsRecompileInfo()) {
if (!entries.back().allocateRecompileInfo(cx))
return false;
needsRecompileHandler |= true;
}
entries.back().oldStub = prevFrameStubPtr;
prevFrameStubPtr = nullptr;
break;
}
case JitFrame_BaselineStub:
prevFrameStubPtr =
reinterpret_cast<BaselineStubFrameLayout*>(iter.fp())->maybeStubPtr();
break;
case JitFrame_IonJS: {
InlineFrameIterator inlineIter(cx, &iter);
while (true) {
if (obs.shouldRecompileOrInvalidate(inlineIter.script())) {
if (!entries.append(DebugModeOSREntry(inlineIter.script())))
return false;
}
if (!inlineIter.more())
break;
++inlineIter;
}
break;
}
default:;
}
}
// Initialize the on-stack recompile handler, which may fail, so that
// patching the stack is infallible.
if (needsRecompileHandler) {
JitRuntime* rt = cx->runtime()->jitRuntime();
if (!rt->getBaselineDebugModeOSRHandlerAddress(cx, true))
return false;
}
return true;
}
static bool
CollectInterpreterStackScripts(JSContext* cx, const Debugger::ExecutionObservableSet& obs,
const ActivationIterator& activation,
DebugModeOSREntryVector& entries)
{
// Collect interpreter frame stacks with IonScript or BaselineScript as
// well. These do not need to be patched, but do need to be invalidated
// and recompiled.
InterpreterActivation* act = activation.activation()->asInterpreter();
for (InterpreterFrameIterator iter(act); !iter.done(); ++iter) {
JSScript* script = iter.frame()->script();
if (obs.shouldRecompileOrInvalidate(script)) {
if (!entries.append(DebugModeOSREntry(iter.frame()->script())))
return false;
}
}
return true;
}
#ifdef JS_JITSPEW
static const char*
ICEntryKindToString(ICEntry::Kind kind)
{
switch (kind) {
case ICEntry::Kind_Op:
return "IC";
case ICEntry::Kind_NonOp:
return "non-op IC";
case ICEntry::Kind_CallVM:
return "callVM";
case ICEntry::Kind_StackCheck:
return "stack check";
case ICEntry::Kind_EarlyStackCheck:
return "early stack check";
case ICEntry::Kind_DebugTrap:
return "debug trap";
case ICEntry::Kind_DebugPrologue:
return "debug prologue";
case ICEntry::Kind_DebugEpilogue:
return "debug epilogue";
default:
MOZ_CRASH("bad ICEntry kind");
}
}
#endif // JS_JITSPEW
static void
SpewPatchBaselineFrame(uint8_t* oldReturnAddress, uint8_t* newReturnAddress,
JSScript* script, ICEntry::Kind frameKind, jsbytecode* pc)
{
JitSpew(JitSpew_BaselineDebugModeOSR,
"Patch return %p -> %p on BaselineJS frame (%s:%d) from %s at %s",
oldReturnAddress, newReturnAddress, script->filename(), script->lineno(),
ICEntryKindToString(frameKind), CodeName[(JSOp)*pc]);
}
static void
SpewPatchBaselineFrameFromExceptionHandler(uint8_t* oldReturnAddress, uint8_t* newReturnAddress,
JSScript* script, jsbytecode* pc)
{
JitSpew(JitSpew_BaselineDebugModeOSR,
"Patch return %p -> %p on BaselineJS frame (%s:%d) from exception handler at %s",
oldReturnAddress, newReturnAddress, script->filename(), script->lineno(),
CodeName[(JSOp)*pc]);
}
static void
SpewPatchStubFrame(ICStub* oldStub, ICStub* newStub)
{
JitSpew(JitSpew_BaselineDebugModeOSR,
"Patch stub %p -> %p on BaselineStub frame (%s)",
oldStub, newStub, newStub ? ICStub::KindString(newStub->kind()) : "exception handler");
}
static void
PatchBaselineFramesForDebugMode(JSContext* cx, const Debugger::ExecutionObservableSet& obs,
const ActivationIterator& activation,
DebugModeOSREntryVector& entries, size_t* start)
{
//
// Recompile Patching Overview
//
// When toggling debug mode with live baseline scripts on the stack, we
// could have entered the VM via the following ways from the baseline
// script.
//
// Off to On:
// A. From a "can call" stub.
// B. From a VM call.
// H. From inside HandleExceptionBaseline.
// I. From inside the interrupt handler via the prologue stack check.
//
// On to Off:
// - All the ways above.
// C. From the debug trap handler.
// D. From the debug prologue.
// E. From the debug epilogue.
//
// Off to On to Off:
// F. Undo case B or I above on previously patched yet unpopped frames.
//
// On to Off to On:
// G. Undo cases B, C, D, E, or I above on previously patched yet unpopped
// frames.
//
// In general, we patch the return address from the VM call to return to a
// "continuation fixer" to fix up machine state (registers and stack
// state). Specifics on what need to be done are documented below.
//
CommonFrameLayout* prev = nullptr;
size_t entryIndex = *start;
for (JitFrameIterator iter(activation); !iter.done(); ++iter) {
switch (iter.type()) {
case JitFrame_BaselineJS: {
// If the script wasn't recompiled or is not observed, there's
// nothing to patch.
if (!obs.shouldRecompileOrInvalidate(iter.script()))
break;
DebugModeOSREntry& entry = entries[entryIndex];
if (!entry.recompiled()) {
entryIndex++;
break;
}
JSScript* script = entry.script;
uint32_t pcOffset = entry.pcOffset;
jsbytecode* pc = script->offsetToPC(pcOffset);
MOZ_ASSERT(script == iter.script());
MOZ_ASSERT(pcOffset < script->length());
BaselineScript* bl = script->baselineScript();
ICEntry::Kind kind = entry.frameKind;
if (kind == ICEntry::Kind_Op) {
// Case A above.
//
// Patching these cases needs to patch both the stub frame and
// the baseline frame. The stub frame is patched below. For
// the baseline frame here, we resume right after the IC
// returns.
//
// Since we're using the same IC stub code, we can resume
// directly to the IC resume address.
uint8_t* retAddr = bl->returnAddressForIC(bl->icEntryFromPCOffset(pcOffset));
SpewPatchBaselineFrame(prev->returnAddress(), retAddr, script, kind, pc);
DebugModeOSRVolatileJitFrameIterator::forwardLiveIterators(
cx, prev->returnAddress(), retAddr);
prev->setReturnAddress(retAddr);
entryIndex++;
break;
}
if (kind == ICEntry::Kind_Invalid) {
// Case H above.
//
// We are recompiling on-stack scripts from inside the
// exception handler, by way of an onExceptionUnwind
// invocation, on a pc without an ICEntry. This means the
// frame must have an override pc.
//
// If profiling is off, patch the resume address to nullptr,
// to ensure the old address is not used anywhere.
//
// If profiling is on, JitProfilingFrameIterator requires a
// valid return address.
MOZ_ASSERT(iter.baselineFrame()->isHandlingException());
MOZ_ASSERT(iter.baselineFrame()->overridePc() == pc);
uint8_t* retAddr;
if (cx->runtime()->spsProfiler.enabled())
retAddr = bl->nativeCodeForPC(script, pc);
else
retAddr = nullptr;
SpewPatchBaselineFrameFromExceptionHandler(prev->returnAddress(), retAddr,
script, pc);
DebugModeOSRVolatileJitFrameIterator::forwardLiveIterators(
cx, prev->returnAddress(), retAddr);
prev->setReturnAddress(retAddr);
entryIndex++;
break;
}
// Cases F and G above.
//
// We undo a previous recompile by handling cases B, C, D, E, or I
// like normal, except that we retrieve the pc information via
// the previous OSR debug info stashed on the frame.
BaselineDebugModeOSRInfo* info = iter.baselineFrame()->getDebugModeOSRInfo();
if (info) {
MOZ_ASSERT(info->pc == pc);
MOZ_ASSERT(info->frameKind == kind);
// Case G, might need to undo B, C, D, E, or I.
MOZ_ASSERT_IF(script->baselineScript()->hasDebugInstrumentation(),
kind == ICEntry::Kind_CallVM ||
kind == ICEntry::Kind_StackCheck ||
kind == ICEntry::Kind_EarlyStackCheck ||
kind == ICEntry::Kind_DebugTrap ||
kind == ICEntry::Kind_DebugPrologue ||
kind == ICEntry::Kind_DebugEpilogue);
// Case F, should only need to undo case B or I.
MOZ_ASSERT_IF(!script->baselineScript()->hasDebugInstrumentation(),
kind == ICEntry::Kind_CallVM ||
kind == ICEntry::Kind_StackCheck ||
kind == ICEntry::Kind_EarlyStackCheck);
// We will have allocated a new recompile info, so delete the
// existing one.
iter.baselineFrame()->deleteDebugModeOSRInfo();
}
// The RecompileInfo must already be allocated so that this
// function may be infallible.
BaselineDebugModeOSRInfo* recompInfo = entry.takeRecompInfo();
bool popFrameReg;
switch (kind) {
case ICEntry::Kind_CallVM: {
// Case B above.
//
// Patching returns from a VM call. After fixing up the the
// continuation for unsynced values (the frame register is
// popped by the callVM trampoline), we resume at the
// return-from-callVM address. The assumption here is that all
// callVMs which can trigger debug mode OSR are the *only*
// callVMs generated for their respective pc locations in the
// baseline JIT code.
ICEntry& callVMEntry = bl->callVMEntryFromPCOffset(pcOffset);
recompInfo->resumeAddr = bl->returnAddressForIC(callVMEntry);
popFrameReg = false;
break;
}
case ICEntry::Kind_StackCheck:
case ICEntry::Kind_EarlyStackCheck: {
// Case I above.
//
// Patching mechanism is identical to a CallVM. This is
// handled especially only because the stack check VM call is
// part of the prologue, and not tied an opcode.
bool earlyCheck = kind == ICEntry::Kind_EarlyStackCheck;
ICEntry& stackCheckEntry = bl->stackCheckICEntry(earlyCheck);
recompInfo->resumeAddr = bl->returnAddressForIC(stackCheckEntry);
popFrameReg = false;
break;
}
case ICEntry::Kind_DebugTrap:
// Case C above.
//
// Debug traps are emitted before each op, so we resume at the
// same op. Calling debug trap handlers is done via a toggled
// call to a thunk (DebugTrapHandler) that takes care tearing
// down its own stub frame so we don't need to worry about
// popping the frame reg.
recompInfo->resumeAddr = bl->nativeCodeForPC(script, pc, &recompInfo->slotInfo);
popFrameReg = false;
break;
case ICEntry::Kind_DebugPrologue:
// Case D above.
//
// We patch a jump directly to the right place in the prologue
// after popping the frame reg and checking for forced return.
recompInfo->resumeAddr = bl->postDebugPrologueAddr();
popFrameReg = true;
break;
default:
// Case E above.
//
// We patch a jump directly to the epilogue after popping the
// frame reg and checking for forced return.
MOZ_ASSERT(kind == ICEntry::Kind_DebugEpilogue);
recompInfo->resumeAddr = bl->epilogueEntryAddr();
popFrameReg = true;
break;
}
SpewPatchBaselineFrame(prev->returnAddress(), recompInfo->resumeAddr,
script, kind, recompInfo->pc);
// The recompile handler must already be created so that this
// function may be infallible.
JitRuntime* rt = cx->runtime()->jitRuntime();
void* handlerAddr = rt->getBaselineDebugModeOSRHandlerAddress(cx, popFrameReg);
MOZ_ASSERT(handlerAddr);
prev->setReturnAddress(reinterpret_cast<uint8_t*>(handlerAddr));
iter.baselineFrame()->setDebugModeOSRInfo(recompInfo);
iter.baselineFrame()->setOverridePc(recompInfo->pc);
entryIndex++;
break;
}
case JitFrame_BaselineStub: {
JitFrameIterator prev(iter);
++prev;
BaselineFrame* prevFrame = prev.baselineFrame();
if (!obs.shouldRecompileOrInvalidate(prevFrame->script()))
break;
DebugModeOSREntry& entry = entries[entryIndex];
// If the script wasn't recompiled, there's nothing to patch.
if (!entry.recompiled())
break;
BaselineStubFrameLayout* layout =
reinterpret_cast<BaselineStubFrameLayout*>(iter.fp());
MOZ_ASSERT(layout->maybeStubPtr() == entry.oldStub);
// Patch baseline stub frames for case A above.
//
// We need to patch the stub frame to point to an ICStub belonging
// to the recompiled baseline script. These stubs are allocated up
// front in CloneOldBaselineStub. They share the same JitCode as
// the old baseline script's stubs, so we don't need to patch the
// exit frame's return address.
//
// Subtlety here: the debug trap handler of case C above pushes a
// stub frame with a null stub pointer. This handler will exist
// across recompiling the script, so we don't patch anything for
// such stub frames. We will return to that handler, which takes
// care of cleaning up the stub frame.
//
// Note that for stub pointers that are already on the C stack
// (i.e. fallback calls), we need to check for recompilation using
// DebugModeOSRVolatileStub.
if (layout->maybeStubPtr()) {
MOZ_ASSERT(entry.newStub || prevFrame->isHandlingException());
SpewPatchStubFrame(entry.oldStub, entry.newStub);
layout->setStubPtr(entry.newStub);
}
break;
}
case JitFrame_IonJS: {
// Nothing to patch.
InlineFrameIterator inlineIter(cx, &iter);
while (true) {
if (obs.shouldRecompileOrInvalidate(inlineIter.script()))
entryIndex++;
if (!inlineIter.more())
break;
++inlineIter;
}
break;
}
default:;
}
prev = iter.current();
}
*start = entryIndex;
}
static void
SkipInterpreterFrameEntries(const Debugger::ExecutionObservableSet& obs,
const ActivationIterator& activation,
DebugModeOSREntryVector& entries, size_t* start)
{
size_t entryIndex = *start;
// Skip interpreter frames, which do not need patching.
InterpreterActivation* act = activation.activation()->asInterpreter();
for (InterpreterFrameIterator iter(act); !iter.done(); ++iter) {
if (obs.shouldRecompileOrInvalidate(iter.frame()->script()))
entryIndex++;
}
*start = entryIndex;
}
static bool
RecompileBaselineScriptForDebugMode(JSContext* cx, JSScript* script,
Debugger::IsObserving observing)
{
BaselineScript* oldBaselineScript = script->baselineScript();
// If a script is on the stack multiple times, it may have already
// been recompiled.
if (oldBaselineScript->hasDebugInstrumentation() == observing)
return true;
JitSpew(JitSpew_BaselineDebugModeOSR, "Recompiling (%s:%d) for %s",
script->filename(), script->lineno(), observing ? "DEBUGGING" : "NORMAL EXECUTION");
script->setBaselineScript(cx, nullptr);
MethodStatus status = BaselineCompile(cx, script, /* forceDebugMode = */ observing);
if (status != Method_Compiled) {
// We will only fail to recompile for debug mode due to OOM. Restore
// the old baseline script in case something doesn't properly
// propagate OOM.
MOZ_ASSERT(status == Method_Error);
script->setBaselineScript(cx, oldBaselineScript);
return false;
}
// Don't destroy the old baseline script yet, since if we fail any of the
// recompiles we need to rollback all the old baseline scripts.
MOZ_ASSERT(script->baselineScript()->hasDebugInstrumentation() == observing);
return true;
}
#define PATCHABLE_ICSTUB_KIND_LIST(_) \
_(Call_Scripted) \
_(Call_AnyScripted) \
_(Call_Native) \
_(Call_ClassHook) \
_(Call_ScriptedApplyArray) \
_(Call_ScriptedApplyArguments) \
_(Call_ScriptedFunCall) \
_(GetElem_NativePrototypeCallNativeName) \
_(GetElem_NativePrototypeCallNativeSymbol) \
_(GetElem_NativePrototypeCallScriptedName) \
_(GetElem_NativePrototypeCallScriptedSymbol) \
_(GetProp_CallScripted) \
_(GetProp_CallNative) \
_(GetProp_CallNativeGlobal) \
_(GetProp_CallDOMProxyNative) \
_(GetProp_CallDOMProxyWithGenerationNative) \
_(GetProp_DOMProxyShadowed) \
_(GetProp_Generic) \
_(SetProp_CallScripted) \
_(SetProp_CallNative)
static bool
CloneOldBaselineStub(JSContext* cx, DebugModeOSREntryVector& entries, size_t entryIndex)
{
DebugModeOSREntry& entry = entries[entryIndex];
if (!entry.oldStub)
return true;
ICStub* oldStub = entry.oldStub;
MOZ_ASSERT(ICStub::CanMakeCalls(oldStub->kind()));
if (entry.frameKind == ICEntry::Kind_Invalid) {
// The exception handler can modify the frame's override pc while
// unwinding scopes. This is fine, but if we have a stub frame, the code
// code below will get confused: the entry's pcOffset doesn't match the
// stub that's still on the stack. To prevent that, we just set the new
// stub to nullptr as we will never return to this stub frame anyway.
entry.newStub = nullptr;
return true;
}
// Get the new fallback stub from the recompiled baseline script.
ICFallbackStub* fallbackStub = entry.fallbackStub();
// We don't need to clone fallback stubs, as they are guaranteed to
// exist. Furthermore, their JitCode is cached and should be the same even
// across the recompile.
if (oldStub->isFallback()) {
MOZ_ASSERT(oldStub->jitCode() == fallbackStub->jitCode());
entry.newStub = fallbackStub;
return true;
}
// Check if we have already cloned the stub on a younger frame. Ignore
// frames that entered the exception handler (entries[i].newStub is nullptr
// in that case, see above).
for (size_t i = 0; i < entryIndex; i++) {
if (oldStub == entries[i].oldStub && entries[i].frameKind != ICEntry::Kind_Invalid) {
MOZ_ASSERT(entries[i].newStub);
entry.newStub = entries[i].newStub;
return true;
}
}
// Some stubs are monitored, get the first stub in the monitor chain from
// the new fallback stub if so.
ICStub* firstMonitorStub;
if (fallbackStub->isMonitoredFallback()) {
ICMonitoredFallbackStub* monitored = fallbackStub->toMonitoredFallbackStub();
firstMonitorStub = monitored->fallbackMonitorStub()->firstMonitorStub();
} else {
firstMonitorStub = nullptr;
}
ICStubSpace* stubSpace = ICStubCompiler::StubSpaceForKind(oldStub->kind(), entry.script);
// Clone the existing stub into the recompiled IC.
//
// Note that since JitCode is a GC thing, cloning an ICStub with the same
// JitCode ensures it won't be collected.
switch (oldStub->kind()) {
#define CASE_KIND(kindName) \
case ICStub::kindName: \
entry.newStub = IC##kindName::Clone(cx, stubSpace, firstMonitorStub, \
*oldStub->to##kindName()); \
break;
PATCHABLE_ICSTUB_KIND_LIST(CASE_KIND)
#undef CASE_KIND
default:
MOZ_CRASH("Bad stub kind");
}
if (!entry.newStub)
return false;
fallbackStub->addNewStub(entry.newStub);
return true;
}
static bool
InvalidateScriptsInZone(JSContext* cx, Zone* zone, const Vector<DebugModeOSREntry>& entries)
{
RecompileInfoVector invalid;
for (UniqueScriptOSREntryIter iter(entries); !iter.done(); ++iter) {
JSScript* script = iter.entry().script;
if (script->compartment()->zone() != zone)
continue;
if (script->hasIonScript()) {
if (!invalid.append(script->ionScript()->recompileInfo())) {
ReportOutOfMemory(cx);
return false;
}
}
// Cancel off-thread Ion compile for anything that has a
// BaselineScript. If we relied on the call to Invalidate below to
// cancel off-thread Ion compiles, only those with existing IonScripts
// would be cancelled.
if (script->hasBaselineScript())
CancelOffThreadIonCompile(script->compartment(), script);
}
// No need to cancel off-thread Ion compiles again, we already did it
// above.
Invalidate(zone->types, cx->runtime()->defaultFreeOp(), invalid,
/* resetUses = */ true, /* cancelOffThread = */ false);
return true;
}
static void
UndoRecompileBaselineScriptsForDebugMode(JSContext* cx,
const DebugModeOSREntryVector& entries)
{
// In case of failure, roll back the entire set of active scripts so that
// we don't have to patch return addresses on the stack.
for (UniqueScriptOSREntryIter iter(entries); !iter.done(); ++iter) {
const DebugModeOSREntry& entry = iter.entry();
JSScript* script = entry.script;
BaselineScript* baselineScript = script->baselineScript();
if (entry.recompiled()) {
script->setBaselineScript(cx, entry.oldBaselineScript);
BaselineScript::Destroy(cx->runtime()->defaultFreeOp(), baselineScript);
}
}
}
bool
jit::RecompileOnStackBaselineScriptsForDebugMode(JSContext* cx,
const Debugger::ExecutionObservableSet& obs,
Debugger::IsObserving observing)
{
// First recompile the active scripts on the stack and patch the live
// frames.
Vector<DebugModeOSREntry> entries(cx);
for (ActivationIterator iter(cx->runtime()); !iter.done(); ++iter) {
if (iter->isJit()) {
if (!CollectJitStackScripts(cx, obs, iter, entries))
return false;
} else if (iter->isInterpreter()) {
if (!CollectInterpreterStackScripts(cx, obs, iter, entries))
return false;
}
}
if (entries.empty())
return true;
// Scripts can entrain nursery things. See note in js::ReleaseAllJITCode.
cx->runtime()->gc.evictNursery();
// When the profiler is enabled, we need to have suppressed sampling,
// since the basline jit scripts are in a state of flux.
MOZ_ASSERT(!cx->runtime()->isProfilerSamplingEnabled());
// Invalidate all scripts we are recompiling.
if (Zone* zone = obs.singleZone()) {
if (!InvalidateScriptsInZone(cx, zone, entries))
return false;
} else {
typedef Debugger::ExecutionObservableSet::ZoneRange ZoneRange;
for (ZoneRange r = obs.zones()->all(); !r.empty(); r.popFront()) {
if (!InvalidateScriptsInZone(cx, r.front(), entries))
return false;
}
}
// Try to recompile all the scripts. If we encounter an error, we need to
// roll back as if none of the compilations happened, so that we don't
// crash.
for (size_t i = 0; i < entries.length(); i++) {
JSScript* script = entries[i].script;
AutoCompartment ac(cx, script->compartment());
if (!RecompileBaselineScriptForDebugMode(cx, script, observing) ||
!CloneOldBaselineStub(cx, entries, i))
{
UndoRecompileBaselineScriptsForDebugMode(cx, entries);
return false;
}
}
// If all recompiles succeeded, destroy the old baseline scripts and patch
// the live frames.
//
// After this point the function must be infallible.
for (UniqueScriptOSREntryIter iter(entries); !iter.done(); ++iter) {
const DebugModeOSREntry& entry = iter.entry();
if (entry.recompiled())
BaselineScript::Destroy(cx->runtime()->defaultFreeOp(), entry.oldBaselineScript);
}
size_t processed = 0;
for (ActivationIterator iter(cx->runtime()); !iter.done(); ++iter) {
if (iter->isJit())
PatchBaselineFramesForDebugMode(cx, obs, iter, entries, &processed);
else if (iter->isInterpreter())
SkipInterpreterFrameEntries(obs, iter, entries, &processed);
}
MOZ_ASSERT(processed == entries.length());
return true;
}
void
BaselineDebugModeOSRInfo::popValueInto(PCMappingSlotInfo::SlotLocation loc, Value* vp)
{
switch (loc) {
case PCMappingSlotInfo::SlotInR0:
valueR0 = vp[stackAdjust];
break;
case PCMappingSlotInfo::SlotInR1:
valueR1 = vp[stackAdjust];
break;
case PCMappingSlotInfo::SlotIgnore:
break;
default:
MOZ_CRASH("Bad slot location");
}
stackAdjust++;
}
static inline bool
HasForcedReturn(BaselineDebugModeOSRInfo* info, bool rv)
{
ICEntry::Kind kind = info->frameKind;
// The debug epilogue always checks its resumption value, so we don't need
// to check rv.
if (kind == ICEntry::Kind_DebugEpilogue)
return true;
// |rv| is the value in ReturnReg. If true, in the case of the prologue,
// it means a forced return.
if (kind == ICEntry::Kind_DebugPrologue)
return rv;
// N.B. The debug trap handler handles its own forced return, so no
// need to deal with it here.
return false;
}
static inline bool
IsReturningFromCallVM(BaselineDebugModeOSRInfo* info)
{
// Keep this in sync with EmitBranchIsReturningFromCallVM.
//
// The stack check entries are returns from a callVM, but have a special
// kind because they do not exist in a 1-1 relationship with a pc offset.
return info->frameKind == ICEntry::Kind_CallVM ||
info->frameKind == ICEntry::Kind_StackCheck ||
info->frameKind == ICEntry::Kind_EarlyStackCheck;
}
static void
EmitBranchICEntryKind(MacroAssembler& masm, Register entry, ICEntry::Kind kind, Label* label)
{
masm.branch32(MacroAssembler::Equal,
Address(entry, offsetof(BaselineDebugModeOSRInfo, frameKind)),
Imm32(kind), label);
}
static void
EmitBranchIsReturningFromCallVM(MacroAssembler& masm, Register entry, Label* label)
{
// Keep this in sync with IsReturningFromCallVM.
EmitBranchICEntryKind(masm, entry, ICEntry::Kind_CallVM, label);
EmitBranchICEntryKind(masm, entry, ICEntry::Kind_StackCheck, label);
EmitBranchICEntryKind(masm, entry, ICEntry::Kind_EarlyStackCheck, label);
}
static void
SyncBaselineDebugModeOSRInfo(BaselineFrame* frame, Value* vp, bool rv)
{
BaselineDebugModeOSRInfo* info = frame->debugModeOSRInfo();
MOZ_ASSERT(info);
MOZ_ASSERT(frame->script()->baselineScript()->containsCodeAddress(info->resumeAddr));
if (HasForcedReturn(info, rv)) {
// Load the frame's rval and overwrite the resume address to go to the
// epilogue.
MOZ_ASSERT(R0 == JSReturnOperand);
info->valueR0 = frame->returnValue();
info->resumeAddr = frame->script()->baselineScript()->epilogueEntryAddr();
return;
}
// Read stack values and make sure R0 and R1 have the right values if we
// aren't returning from a callVM.
//
// In the case of returning from a callVM, we don't need to restore R0 and
// R1 ourself since we'll return into code that does it if needed.
if (!IsReturningFromCallVM(info)) {
unsigned numUnsynced = info->slotInfo.numUnsynced();
MOZ_ASSERT(numUnsynced <= 2);
if (numUnsynced > 0)
info->popValueInto(info->slotInfo.topSlotLocation(), vp);
if (numUnsynced > 1)
info->popValueInto(info->slotInfo.nextSlotLocation(), vp);
}
// Scale stackAdjust.
info->stackAdjust *= sizeof(Value);
}
static void
FinishBaselineDebugModeOSR(BaselineFrame* frame)
{
frame->deleteDebugModeOSRInfo();
// We will return to JIT code now so we have to clear the override pc.
frame->clearOverridePc();
}
void
BaselineFrame::deleteDebugModeOSRInfo()
{
js_delete(getDebugModeOSRInfo());
flags_ &= ~HAS_DEBUG_MODE_OSR_INFO;
}
JitCode*
JitRuntime::getBaselineDebugModeOSRHandler(JSContext* cx)
{
if (!baselineDebugModeOSRHandler_) {
AutoLockForExclusiveAccess lock(cx);
AutoCompartment ac(cx, cx->runtime()->atomsCompartment());
uint32_t offset;
if (JitCode* code = generateBaselineDebugModeOSRHandler(cx, &offset)) {
baselineDebugModeOSRHandler_ = code;
baselineDebugModeOSRHandlerNoFrameRegPopAddr_ = code->raw() + offset;
}
}
return baselineDebugModeOSRHandler_;
}
void*
JitRuntime::getBaselineDebugModeOSRHandlerAddress(JSContext* cx, bool popFrameReg)
{
if (!getBaselineDebugModeOSRHandler(cx))
return nullptr;
return popFrameReg
? baselineDebugModeOSRHandler_->raw()
: baselineDebugModeOSRHandlerNoFrameRegPopAddr_;
}
static void
EmitBaselineDebugModeOSRHandlerTail(MacroAssembler& masm, Register temp, bool returnFromCallVM)
{
// Save real return address on the stack temporarily.
//
// If we're returning from a callVM, we don't need to worry about R0 and
// R1 but do need to propagate the original ReturnReg value. Otherwise we
// need to worry about R0 and R1 but can clobber ReturnReg. Indeed, on
// x86, R1 contains ReturnReg.
if (returnFromCallVM) {
masm.push(ReturnReg);
} else {
masm.pushValue(Address(temp, offsetof(BaselineDebugModeOSRInfo, valueR0)));
masm.pushValue(Address(temp, offsetof(BaselineDebugModeOSRInfo, valueR1)));
}
masm.push(BaselineFrameReg);
masm.push(Address(temp, offsetof(BaselineDebugModeOSRInfo, resumeAddr)));
// Call a stub to free the allocated info.
masm.setupUnalignedABICall(temp);
masm.loadBaselineFramePtr(BaselineFrameReg, temp);
masm.passABIArg(temp);
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, FinishBaselineDebugModeOSR));
// Restore saved values.
AllocatableGeneralRegisterSet jumpRegs(GeneralRegisterSet::All());
if (returnFromCallVM) {
jumpRegs.take(ReturnReg);
} else {
jumpRegs.take(R0);
jumpRegs.take(R1);
}
jumpRegs.take(BaselineFrameReg);
Register target = jumpRegs.takeAny();
masm.pop(target);
masm.pop(BaselineFrameReg);
if (returnFromCallVM) {
masm.pop(ReturnReg);
} else {
masm.popValue(R1);
masm.popValue(R0);
}
masm.jump(target);
}
JitCode*
JitRuntime::generateBaselineDebugModeOSRHandler(JSContext* cx, uint32_t* noFrameRegPopOffsetOut)
{
MacroAssembler masm(cx);
AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All());
regs.take(BaselineFrameReg);
regs.take(ReturnReg);
Register temp = regs.takeAny();
Register syncedStackStart = regs.takeAny();
// Pop the frame reg.
masm.pop(BaselineFrameReg);
// Not all patched baseline frames are returning from a situation where
// the frame reg is already fixed up.
CodeOffset noFrameRegPopOffset(masm.currentOffset());
// Record the stack pointer for syncing.
masm.moveStackPtrTo(syncedStackStart);
masm.push(ReturnReg);
masm.push(BaselineFrameReg);
// Call a stub to fully initialize the info.
masm.setupUnalignedABICall(temp);
masm.loadBaselineFramePtr(BaselineFrameReg, temp);
masm.passABIArg(temp);
masm.passABIArg(syncedStackStart);
masm.passABIArg(ReturnReg);
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, SyncBaselineDebugModeOSRInfo));
// Discard stack values depending on how many were unsynced, as we always
// have a fully synced stack in the recompile handler. We arrive here via
// a callVM, and prepareCallVM in BaselineCompiler always fully syncs the
// stack.
masm.pop(BaselineFrameReg);
masm.pop(ReturnReg);
masm.loadPtr(Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfScratchValue()), temp);
masm.addToStackPtr(Address(temp, offsetof(BaselineDebugModeOSRInfo, stackAdjust)));
// Emit two tails for the case of returning from a callVM and all other
// cases, as the state we need to restore differs depending on the case.
Label returnFromCallVM, end;
EmitBranchIsReturningFromCallVM(masm, temp, &returnFromCallVM);
EmitBaselineDebugModeOSRHandlerTail(masm, temp, /* returnFromCallVM = */ false);
masm.jump(&end);
masm.bind(&returnFromCallVM);
EmitBaselineDebugModeOSRHandlerTail(masm, temp, /* returnFromCallVM = */ true);
masm.bind(&end);
Linker linker(masm);
AutoFlushICache afc("BaselineDebugModeOSRHandler");
JitCode* code = linker.newCode<NoGC>(cx, OTHER_CODE);
if (!code)
return nullptr;
*noFrameRegPopOffsetOut = noFrameRegPopOffset.offset();
#ifdef JS_ION_PERF
writePerfSpewerJitCodeProfile(code, "BaselineDebugModeOSRHandler");
#endif
return code;
}
/* static */ void
DebugModeOSRVolatileJitFrameIterator::forwardLiveIterators(JSContext* cx,
uint8_t* oldAddr, uint8_t* newAddr)
{
DebugModeOSRVolatileJitFrameIterator* iter;
for (iter = cx->liveVolatileJitFrameIterators_; iter; iter = iter->prev) {
if (iter->returnAddressToFp_ == oldAddr)
iter->returnAddressToFp_ = newAddr;
}
}