Source code
Revision control
Copy as Markdown
Other Tools
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* 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
#include "jit/JitFrames-inl.h"
#include "mozilla/ScopeExit.h"
#include <algorithm>
#include "builtin/ModuleObject.h"
#include "gc/GC.h"
#include "jit/BaselineFrame.h"
#include "jit/BaselineIC.h"
#include "jit/BaselineJIT.h"
#include "jit/Ion.h"
#include "jit/IonScript.h"
#include "jit/JitRuntime.h"
#include "jit/JitSpewer.h"
#include "jit/LIR.h"
#include "jit/PcScriptCache.h"
#include "jit/Recover.h"
#include "jit/Safepoints.h"
#include "jit/ScriptFromCalleeToken.h"
#include "jit/Snapshots.h"
#include "jit/VMFunctions.h"
#include "js/Exception.h"
#include "js/friend/DumpFunctions.h" // js::DumpObject, js::DumpValue
#include "vm/Interpreter.h"
#include "vm/JSContext.h"
#include "vm/JSFunction.h"
#include "vm/JSObject.h"
#include "vm/JSScript.h"
#include "wasm/WasmBuiltins.h"
#include "wasm/WasmInstance.h"
#include "debugger/DebugAPI-inl.h"
#include "jit/JSJitFrameIter-inl.h"
#include "vm/GeckoProfiler-inl.h"
#include "vm/JSScript-inl.h"
#include "vm/Probes-inl.h"
namespace js {
namespace jit {
// Given a slot index, returns the offset, in bytes, of that slot from an
// JitFrameLayout. Slot distances are uniform across architectures, however,
// the distance does depend on the size of the frame header.
static inline int32_t OffsetOfFrameSlot(int32_t slot) { return -slot; }
static inline uint8_t* AddressOfFrameSlot(JitFrameLayout* fp, int32_t slot) {
return (uint8_t*)fp + OffsetOfFrameSlot(slot);
}
static inline uintptr_t ReadFrameSlot(JitFrameLayout* fp, int32_t slot) {
return *(uintptr_t*)AddressOfFrameSlot(fp, slot);
}
static inline void WriteFrameSlot(JitFrameLayout* fp, int32_t slot,
uintptr_t value) {
*(uintptr_t*)AddressOfFrameSlot(fp, slot) = value;
}
static inline double ReadFrameDoubleSlot(JitFrameLayout* fp, int32_t slot) {
return *(double*)AddressOfFrameSlot(fp, slot);
}
static inline float ReadFrameFloat32Slot(JitFrameLayout* fp, int32_t slot) {
return *(float*)AddressOfFrameSlot(fp, slot);
}
static inline int32_t ReadFrameInt32Slot(JitFrameLayout* fp, int32_t slot) {
return *(int32_t*)AddressOfFrameSlot(fp, slot);
}
static inline bool ReadFrameBooleanSlot(JitFrameLayout* fp, int32_t slot) {
return *(bool*)AddressOfFrameSlot(fp, slot);
}
static uint32_t NumArgAndLocalSlots(const InlineFrameIterator& frame) {
JSScript* script = frame.script();
return CountArgSlots(script, frame.maybeCalleeTemplate()) + script->nfixed();
}
static void CloseLiveIteratorIon(JSContext* cx,
const InlineFrameIterator& frame,
const TryNote* tn) {
MOZ_ASSERT(tn->kind() == TryNoteKind::ForIn ||
tn->kind() == TryNoteKind::Destructuring);
bool isDestructuring = tn->kind() == TryNoteKind::Destructuring;
MOZ_ASSERT_IF(!isDestructuring, tn->stackDepth > 0);
MOZ_ASSERT_IF(isDestructuring, tn->stackDepth > 1);
// Save any pending exception, because some recover operations call into
// AutoUnsafeCallWithABI functions, which don't allow pending exceptions.
JS::AutoSaveExceptionState savedExc(cx);
SnapshotIterator si = frame.snapshotIterator();
// Skip stack slots until we reach the iterator object on the stack. For
// the destructuring case, we also need to get the "done" value.
uint32_t stackSlot = tn->stackDepth;
uint32_t adjust = isDestructuring ? 2 : 1;
uint32_t skipSlots = NumArgAndLocalSlots(frame) + stackSlot - adjust;
for (unsigned i = 0; i < skipSlots; i++) {
si.skip();
}
MaybeReadFallback recover(cx, cx->activation()->asJit(), &frame.frame(),
MaybeReadFallback::Fallback_DoNothing);
Value v = si.maybeRead(recover);
MOZ_RELEASE_ASSERT(v.isObject());
RootedObject iterObject(cx, &v.toObject());
if (isDestructuring) {
RootedValue doneValue(cx, si.read());
MOZ_RELEASE_ASSERT(!doneValue.isMagic());
bool done = ToBoolean(doneValue);
// Do not call IteratorClose if the destructuring iterator is already
// done.
if (done) {
return;
}
}
// Restore any pending exception before the closing the iterator.
savedExc.restore();
if (cx->isExceptionPending()) {
if (tn->kind() == TryNoteKind::ForIn) {
CloseIterator(iterObject);
} else {
IteratorCloseForException(cx, iterObject);
}
} else {
UnwindIteratorForUncatchableException(iterObject);
}
}
class IonTryNoteFilter {
uint32_t depth_;
public:
explicit IonTryNoteFilter(const InlineFrameIterator& frame) {
uint32_t base = NumArgAndLocalSlots(frame);
SnapshotIterator si = frame.snapshotIterator();
MOZ_ASSERT(si.numAllocations() >= base);
depth_ = si.numAllocations() - base;
}
bool operator()(const TryNote* note) { return note->stackDepth <= depth_; }
};
class TryNoteIterIon : public TryNoteIter<IonTryNoteFilter> {
public:
TryNoteIterIon(JSContext* cx, const InlineFrameIterator& frame)
: TryNoteIter(cx, frame.script(), frame.pc(), IonTryNoteFilter(frame)) {}
};
static bool ShouldBailoutForDebugger(JSContext* cx,
const InlineFrameIterator& frame,
bool hitBailoutException) {
if (hitBailoutException) {
MOZ_ASSERT(!cx->isPropagatingForcedReturn());
return false;
}
// Bail out if we're propagating a forced return from an inlined frame,
// even if the realm is no longer a debuggee.
if (cx->isPropagatingForcedReturn() && frame.more()) {
return true;
}
if (!cx->realm()->isDebuggee()) {
return false;
}
// Bail out if there's a catchable exception and we are the debuggee of a
// Debugger with a live onExceptionUnwind hook.
if (cx->isExceptionPending() &&
DebugAPI::hasExceptionUnwindHook(cx->global())) {
return true;
}
// Bail out if a Debugger has observed this frame (e.g., for onPop).
JitActivation* act = cx->activation()->asJit();
RematerializedFrame* rematFrame =
act->lookupRematerializedFrame(frame.frame().fp(), frame.frameNo());
return rematFrame && rematFrame->isDebuggee();
}
static void OnLeaveIonFrame(JSContext* cx, const InlineFrameIterator& frame,
ResumeFromException* rfe) {
bool returnFromThisFrame =
cx->isPropagatingForcedReturn() || cx->isClosingGenerator();
if (!returnFromThisFrame) {
return;
}
JitActivation* act = cx->activation()->asJit();
RematerializedFrame* rematFrame = nullptr;
{
JS::AutoSaveExceptionState savedExc(cx);
// We can run recover instructions without invalidating because we're
// already leaving the frame.
MaybeReadFallback::FallbackConsequence consequence =
MaybeReadFallback::Fallback_DoNothing;
rematFrame = act->getRematerializedFrame(cx, frame.frame(), frame.frameNo(),
consequence);
if (!rematFrame) {
return;
}
}
MOZ_ASSERT(!frame.more());
if (cx->isClosingGenerator()) {
HandleClosingGeneratorReturn(cx, rematFrame, /*frameOk=*/true);
} else {
cx->clearPropagatingForcedReturn();
}
Value& rval = rematFrame->returnValue();
MOZ_RELEASE_ASSERT(!rval.isMagic());
// Set both framePointer and stackPointer to the address of the
// JitFrameLayout.
rfe->kind = ExceptionResumeKind::ForcedReturnIon;
rfe->framePointer = frame.frame().fp();
rfe->stackPointer = frame.frame().fp();
rfe->exception = rval;
act->removeIonFrameRecovery(frame.frame().jsFrame());
act->removeRematerializedFrame(frame.frame().fp());
}
static void HandleExceptionIon(JSContext* cx, const InlineFrameIterator& frame,
ResumeFromException* rfe,
bool* hitBailoutException) {
if (ShouldBailoutForDebugger(cx, frame, *hitBailoutException)) {
// We do the following:
//
// 1. Bailout to baseline to reconstruct a baseline frame.
// 2. Resume immediately into the exception tail afterwards, and
// handle the exception again with the top frame now a baseline
// frame.
//
// An empty exception info denotes that we're propagating an Ion
// exception due to debug mode, which BailoutIonToBaseline needs to
// know. This is because we might not be able to fully reconstruct up
// to the stack depth at the snapshot, as we could've thrown in the
// middle of a call.
ExceptionBailoutInfo propagateInfo(cx);
if (ExceptionHandlerBailout(cx, frame, rfe, propagateInfo)) {
return;
}
*hitBailoutException = true;
}
RootedScript script(cx, frame.script());
for (TryNoteIterIon tni(cx, frame); !tni.done(); ++tni) {
const TryNote* tn = *tni;
switch (tn->kind()) {
case TryNoteKind::ForIn:
case TryNoteKind::Destructuring:
CloseLiveIteratorIon(cx, frame, tn);
break;
case TryNoteKind::Catch:
// If we're closing a generator, we have to skip catch blocks.
if (cx->isClosingGenerator()) {
break;
}
if (cx->isExceptionPending()) {
// Ion can compile try-catch, but bailing out to catch
// exceptions is slow. Reset the warm-up counter so that if we
// catch many exceptions we won't Ion-compile the script.
script->resetWarmUpCounterToDelayIonCompilation();
if (*hitBailoutException) {
break;
}
// Bailout at the start of the catch block.
jsbytecode* catchPC = script->offsetToPC(tn->start + tn->length);
ExceptionBailoutInfo excInfo(cx, frame.frameNo(), catchPC,
tn->stackDepth);
if (ExceptionHandlerBailout(cx, frame, rfe, excInfo)) {
// Record exception locations to allow scope unwinding in
// |FinishBailoutToBaseline|
MOZ_ASSERT(cx->isExceptionPending());
rfe->bailoutInfo->tryPC =
UnwindEnvironmentToTryPc(frame.script(), tn);
rfe->bailoutInfo->faultPC = frame.pc();
return;
}
*hitBailoutException = true;
MOZ_ASSERT(cx->isExceptionPending());
}
break;
case TryNoteKind::Finally: {
if (!cx->isExceptionPending()) {
// We don't catch uncatchable exceptions.
break;
}
script->resetWarmUpCounterToDelayIonCompilation();
if (*hitBailoutException) {
break;
}
// Bailout at the start of the finally block.
jsbytecode* finallyPC = script->offsetToPC(tn->start + tn->length);
ExceptionBailoutInfo excInfo(cx, frame.frameNo(), finallyPC,
tn->stackDepth);
RootedValue exception(cx);
if (!cx->getPendingException(&exception)) {
exception = UndefinedValue();
}
excInfo.setFinallyException(exception.get());
cx->clearPendingException();
if (ExceptionHandlerBailout(cx, frame, rfe, excInfo)) {
// Record exception locations to allow scope unwinding in
// |FinishBailoutToBaseline|
rfe->bailoutInfo->tryPC =
UnwindEnvironmentToTryPc(frame.script(), tn);
rfe->bailoutInfo->faultPC = frame.pc();
return;
}
*hitBailoutException = true;
MOZ_ASSERT(cx->isExceptionPending());
break;
}
case TryNoteKind::ForOf:
case TryNoteKind::Loop:
break;
// TryNoteKind::ForOfIterclose is handled internally by the try note
// iterator.
default:
MOZ_CRASH("Unexpected try note");
}
}
OnLeaveIonFrame(cx, frame, rfe);
}
static void OnLeaveBaselineFrame(JSContext* cx, const JSJitFrameIter& frame,
jsbytecode* pc, ResumeFromException* rfe,
bool frameOk) {
BaselineFrame* baselineFrame = frame.baselineFrame();
bool returnFromThisFrame = jit::DebugEpilogue(cx, baselineFrame, pc, frameOk);
if (returnFromThisFrame) {
rfe->kind = ExceptionResumeKind::ForcedReturnBaseline;
rfe->framePointer = frame.fp();
rfe->stackPointer = reinterpret_cast<uint8_t*>(baselineFrame);
}
}
static inline void BaselineFrameAndStackPointersFromTryNote(
const TryNote* tn, const JSJitFrameIter& frame, uint8_t** framePointer,
uint8_t** stackPointer) {
JSScript* script = frame.baselineFrame()->script();
*framePointer = frame.fp();
*stackPointer = *framePointer - BaselineFrame::Size() -
(script->nfixed() + tn->stackDepth) * sizeof(Value);
}
static void SettleOnTryNote(JSContext* cx, const TryNote* tn,
const JSJitFrameIter& frame, EnvironmentIter& ei,
ResumeFromException* rfe, jsbytecode** pc) {
RootedScript script(cx, frame.baselineFrame()->script());
// Unwind environment chain (pop block objects).
if (cx->isExceptionPending()) {
UnwindEnvironment(cx, ei, UnwindEnvironmentToTryPc(script, tn));
}
// Compute base pointer and stack pointer.
BaselineFrameAndStackPointersFromTryNote(tn, frame, &rfe->framePointer,
&rfe->stackPointer);
// Compute the pc.
*pc = script->offsetToPC(tn->start + tn->length);
}
class BaselineTryNoteFilter {
const JSJitFrameIter& frame_;
public:
explicit BaselineTryNoteFilter(const JSJitFrameIter& frame) : frame_(frame) {}
bool operator()(const TryNote* note) {
BaselineFrame* frame = frame_.baselineFrame();
uint32_t numValueSlots = frame_.baselineFrameNumValueSlots();
MOZ_RELEASE_ASSERT(numValueSlots >= frame->script()->nfixed());
uint32_t currDepth = numValueSlots - frame->script()->nfixed();
return note->stackDepth <= currDepth;
}
};
class TryNoteIterBaseline : public TryNoteIter<BaselineTryNoteFilter> {
public:
TryNoteIterBaseline(JSContext* cx, const JSJitFrameIter& frame,
jsbytecode* pc)
: TryNoteIter(cx, frame.script(), pc, BaselineTryNoteFilter(frame)) {}
};
// Close all live iterators on a BaselineFrame due to exception unwinding. The
// pc parameter is updated to where the envs have been unwound to.
static void CloseLiveIteratorsBaselineForUncatchableException(
JSContext* cx, const JSJitFrameIter& frame, jsbytecode* pc) {
for (TryNoteIterBaseline tni(cx, frame, pc); !tni.done(); ++tni) {
const TryNote* tn = *tni;
switch (tn->kind()) {
case TryNoteKind::ForIn: {
uint8_t* framePointer;
uint8_t* stackPointer;
BaselineFrameAndStackPointersFromTryNote(tn, frame, &framePointer,
&stackPointer);
Value iterValue(*(Value*)stackPointer);
RootedObject iterObject(cx, &iterValue.toObject());
UnwindIteratorForUncatchableException(iterObject);
break;
}
default:
break;
}
}
}
static bool ProcessTryNotesBaseline(JSContext* cx, const JSJitFrameIter& frame,
EnvironmentIter& ei,
ResumeFromException* rfe, jsbytecode** pc) {
MOZ_ASSERT(frame.baselineFrame()->runningInInterpreter(),
"Caller must ensure frame is an interpreter frame");
RootedScript script(cx, frame.baselineFrame()->script());
for (TryNoteIterBaseline tni(cx, frame, *pc); !tni.done(); ++tni) {
const TryNote* tn = *tni;
MOZ_ASSERT(cx->isExceptionPending());
switch (tn->kind()) {
case TryNoteKind::Catch: {
// If we're closing a generator, we have to skip catch blocks.
if (cx->isClosingGenerator()) {
break;
}
SettleOnTryNote(cx, tn, frame, ei, rfe, pc);
// Ion can compile try-catch, but bailing out to catch
// exceptions is slow. Reset the warm-up counter so that if we
// catch many exceptions we won't Ion-compile the script.
script->resetWarmUpCounterToDelayIonCompilation();
// Resume at the start of the catch block.
const BaselineInterpreter& interp =
cx->runtime()->jitRuntime()->baselineInterpreter();
frame.baselineFrame()->setInterpreterFields(*pc);
rfe->kind = ExceptionResumeKind::Catch;
rfe->target = interp.interpretOpAddr().value;
return true;
}
case TryNoteKind::Finally: {
SettleOnTryNote(cx, tn, frame, ei, rfe, pc);
const BaselineInterpreter& interp =
cx->runtime()->jitRuntime()->baselineInterpreter();
frame.baselineFrame()->setInterpreterFields(*pc);
rfe->kind = ExceptionResumeKind::Finally;
rfe->target = interp.interpretOpAddr().value;
// Drop the exception instead of leaking cross compartment data.
if (!cx->getPendingException(
MutableHandleValue::fromMarkedLocation(&rfe->exception))) {
rfe->exception = UndefinedValue();
}
cx->clearPendingException();
return true;
}
case TryNoteKind::ForIn: {
uint8_t* framePointer;
uint8_t* stackPointer;
BaselineFrameAndStackPointersFromTryNote(tn, frame, &framePointer,
&stackPointer);
Value iterValue(*reinterpret_cast<Value*>(stackPointer));
JSObject* iterObject = &iterValue.toObject();
CloseIterator(iterObject);
break;
}
case TryNoteKind::Destructuring: {
uint8_t* framePointer;
uint8_t* stackPointer;
BaselineFrameAndStackPointersFromTryNote(tn, frame, &framePointer,
&stackPointer);
// Note: if this ever changes, also update the
// TryNoteKind::Destructuring code in WarpBuilder.cpp!
RootedValue doneValue(cx, *(reinterpret_cast<Value*>(stackPointer)));
MOZ_RELEASE_ASSERT(!doneValue.isMagic());
bool done = ToBoolean(doneValue);
if (!done) {
Value iterValue(*(reinterpret_cast<Value*>(stackPointer) + 1));
RootedObject iterObject(cx, &iterValue.toObject());
if (!IteratorCloseForException(cx, iterObject)) {
SettleOnTryNote(cx, tn, frame, ei, rfe, pc);
return false;
}
}
break;
}
case TryNoteKind::ForOf:
case TryNoteKind::Loop:
break;
// TryNoteKind::ForOfIterClose is handled internally by the try note
// iterator.
default:
MOZ_CRASH("Invalid try note");
}
}
return true;
}
static void HandleExceptionBaseline(JSContext* cx, JSJitFrameIter& frame,
CommonFrameLayout* prevFrame,
ResumeFromException* rfe) {
MOZ_ASSERT(frame.isBaselineJS());
MOZ_ASSERT(prevFrame);
jsbytecode* pc;
frame.baselineScriptAndPc(nullptr, &pc);
// Ensure the BaselineFrame is an interpreter frame. This is easy to do and
// simplifies the code below and interaction with DebugModeOSR.
//
// Note that we never return to this frame via the previous frame's return
// address. We could set the return address to nullptr to ensure it's never
// used, but the profiler expects a non-null return value for its JitCode map
// lookup so we have to use an address in the interpreter code instead.
if (!frame.baselineFrame()->runningInInterpreter()) {
const BaselineInterpreter& interp =
cx->runtime()->jitRuntime()->baselineInterpreter();
uint8_t* retAddr = interp.codeRaw();
BaselineFrame* baselineFrame = frame.baselineFrame();
// Suppress profiler sampling while we fix up the frame to ensure the
// sampler thread doesn't see an inconsistent state.
AutoSuppressProfilerSampling suppressProfilerSampling(cx);
baselineFrame->switchFromJitToInterpreterForExceptionHandler(cx, pc);
prevFrame->setReturnAddress(retAddr);
// Ensure the current iterator's resumePCInCurrentFrame_ isn't used
// anywhere.
frame.setResumePCInCurrentFrame(nullptr);
}
bool frameOk = false;
RootedScript script(cx, frame.baselineFrame()->script());
if (script->hasScriptCounts()) {
PCCounts* counts = script->getThrowCounts(pc);
// If we failed to allocate, then skip the increment and continue to
// handle the exception.
if (counts) {
counts->numExec()++;
}
}
bool hasTryNotes = !script->trynotes().empty();
again:
if (cx->isExceptionPending()) {
if (!cx->isClosingGenerator()) {
if (!DebugAPI::onExceptionUnwind(cx, frame.baselineFrame())) {
if (!cx->isExceptionPending()) {
goto again;
}
}
// Ensure that the debugger hasn't returned 'true' while clearing the
// exception state.
MOZ_ASSERT(cx->isExceptionPending());
}
if (hasTryNotes) {
EnvironmentIter ei(cx, frame.baselineFrame(), pc);
if (!ProcessTryNotesBaseline(cx, frame, ei, rfe, &pc)) {
goto again;
}
if (rfe->kind != ExceptionResumeKind::EntryFrame) {
// No need to increment the PCCounts number of execution here,
// as the interpreter increments any PCCounts if present.
MOZ_ASSERT_IF(script->hasScriptCounts(), script->maybeGetPCCounts(pc));
return;
}
}
frameOk = HandleClosingGeneratorReturn(cx, frame.baselineFrame(), frameOk);
} else {
if (hasTryNotes) {
CloseLiveIteratorsBaselineForUncatchableException(cx, frame, pc);
}
// We may be propagating a forced return from a debugger hook function.
if (MOZ_UNLIKELY(cx->isPropagatingForcedReturn())) {
cx->clearPropagatingForcedReturn();
frameOk = true;
}
}
OnLeaveBaselineFrame(cx, frame, pc, rfe, frameOk);
}
static JitFrameLayout* GetLastProfilingFrame(ResumeFromException* rfe) {
switch (rfe->kind) {
case ExceptionResumeKind::EntryFrame:
case ExceptionResumeKind::Wasm:
case ExceptionResumeKind::WasmCatch:
return nullptr;
// The following all return into Baseline or Ion frames.
case ExceptionResumeKind::Catch:
case ExceptionResumeKind::Finally:
case ExceptionResumeKind::ForcedReturnBaseline:
case ExceptionResumeKind::ForcedReturnIon:
return reinterpret_cast<JitFrameLayout*>(rfe->framePointer);
// When resuming into a bailed-out ion frame, use the bailout info to
// find the frame we are resuming into.
case ExceptionResumeKind::Bailout:
return reinterpret_cast<JitFrameLayout*>(rfe->bailoutInfo->incomingStack);
}
MOZ_CRASH("Invalid ResumeFromException type!");
return nullptr;
}
void HandleExceptionWasm(JSContext* cx, wasm::WasmFrameIter* iter,
ResumeFromException* rfe) {
MOZ_ASSERT(cx->activation()->asJit()->hasWasmExitFP());
wasm::HandleThrow(cx, *iter, rfe);
MOZ_ASSERT(iter->done());
}
void HandleException(ResumeFromException* rfe) {
JSContext* cx = TlsContext.get();
#ifdef DEBUG
cx->runtime()->jitRuntime()->clearDisallowArbitraryCode();
// Reset the counter when we bailed after MDebugEnterGCUnsafeRegion, but
// before the matching MDebugLeaveGCUnsafeRegion.
//
// NOTE: EnterJit ensures the counter is zero when we enter JIT code.
cx->resetInUnsafeRegion();
#endif
auto resetProfilerFrame = mozilla::MakeScopeExit([=] {
if (!cx->runtime()->jitRuntime()->isProfilerInstrumentationEnabled(
cx->runtime())) {
return;
}
MOZ_ASSERT(cx->jitActivation == cx->profilingActivation());
auto* lastProfilingFrame = GetLastProfilingFrame(rfe);
cx->jitActivation->setLastProfilingFrame(lastProfilingFrame);
});
rfe->kind = ExceptionResumeKind::EntryFrame;
JitSpew(JitSpew_IonInvalidate, "handling exception");
JitActivation* activation = cx->activation()->asJit();
#ifdef CHECK_OSIPOINT_REGISTERS
if (JitOptions.checkOsiPointRegisters) {
activation->setCheckRegs(false);
}
#endif
JitFrameIter iter(cx->activation()->asJit(),
/* mustUnwindActivation = */ true);
CommonFrameLayout* prevJitFrame = nullptr;
while (!iter.done()) {
if (iter.isWasm()) {
prevJitFrame = nullptr;
HandleExceptionWasm(cx, &iter.asWasm(), rfe);
// If a wasm try-catch handler is found, we can immediately jump to it
// and quit iterating through the stack.
if (rfe->kind == ExceptionResumeKind::WasmCatch) {
return;
}
if (!iter.done()) {
++iter;
}
continue;
}
JSJitFrameIter& frame = iter.asJSJit();
// JIT code can enter same-compartment realms, so reset cx->realm to
// this frame's realm.
if (frame.isScripted()) {
cx->setRealmForJitExceptionHandler(iter.realm());
}
if (frame.isIonJS()) {
// Search each inlined frame for live iterator objects, and close
// them.
InlineFrameIterator frames(cx, &frame);
// Invalidation state will be the same for all inlined scripts in the
// frame.
IonScript* ionScript = nullptr;
bool invalidated = frame.checkInvalidation(&ionScript);
// If we hit OOM or overrecursion while bailing out, we don't
// attempt to bail out a second time for this Ion frame. Just unwind
// and continue at the next frame.
bool hitBailoutException = false;
for (;;) {
HandleExceptionIon(cx, frames, rfe, &hitBailoutException);
if (rfe->kind == ExceptionResumeKind::Bailout ||
rfe->kind == ExceptionResumeKind::ForcedReturnIon) {
if (invalidated) {
ionScript->decrementInvalidationCount(cx->gcContext());
}
return;
}
MOZ_ASSERT(rfe->kind == ExceptionResumeKind::EntryFrame);
// When profiling, each frame popped needs a notification that
// the function has exited, so invoke the probe that a function
// is exiting.
JSScript* script = frames.script();
probes::ExitScript(cx, script, script->function(),
/* popProfilerFrame = */ false);
if (!frames.more()) {
break;
}
++frames;
}
// Remove left-over state which might have been needed for bailout.
activation->removeIonFrameRecovery(frame.jsFrame());
activation->removeRematerializedFrame(frame.fp());
// If invalidated, decrement the number of frames remaining on the
// stack for the given IonScript.
if (invalidated) {
ionScript->decrementInvalidationCount(cx->gcContext());
}
} else if (frame.isBaselineJS()) {
HandleExceptionBaseline(cx, frame, prevJitFrame, rfe);
if (rfe->kind != ExceptionResumeKind::EntryFrame &&
rfe->kind != ExceptionResumeKind::ForcedReturnBaseline) {
return;
}
// Unwind profiler pseudo-stack
JSScript* script = frame.script();
probes::ExitScript(cx, script, script->function(),
/* popProfilerFrame = */ false);
if (rfe->kind == ExceptionResumeKind::ForcedReturnBaseline) {
return;
}
}
prevJitFrame = frame.current();
++iter;
}
// Wasm sets its own value of SP in HandleExceptionWasm.
if (iter.isJSJit()) {
MOZ_ASSERT(rfe->kind == ExceptionResumeKind::EntryFrame);
rfe->framePointer = iter.asJSJit().current()->callerFramePtr();
rfe->stackPointer =
iter.asJSJit().fp() + CommonFrameLayout::offsetOfReturnAddress();
}
}
// Turns a JitFrameLayout into an UnwoundJit ExitFrameLayout.
void EnsureUnwoundJitExitFrame(JitActivation* act, JitFrameLayout* frame) {
ExitFrameLayout* exitFrame = reinterpret_cast<ExitFrameLayout*>(frame);
if (act->jsExitFP() == (uint8_t*)frame) {
// If we already called this function for the current frame, do
// nothing.
MOZ_ASSERT(exitFrame->isUnwoundJitExit());
return;
}
#ifdef DEBUG
JSJitFrameIter iter(act);
while (!iter.isScripted()) {
++iter;
}
MOZ_ASSERT(iter.current() == frame, "|frame| must be the top JS frame");
MOZ_ASSERT(!!act->jsExitFP());
MOZ_ASSERT((uint8_t*)exitFrame->footer() >= act->jsExitFP(),
"Must have space for ExitFooterFrame before jsExitFP");
#endif
act->setJSExitFP((uint8_t*)frame);
exitFrame->footer()->setUnwoundJitExitFrame();
MOZ_ASSERT(exitFrame->isUnwoundJitExit());
}
JSScript* MaybeForwardedScriptFromCalleeToken(CalleeToken token) {
switch (GetCalleeTokenTag(token)) {
case CalleeToken_Script:
return MaybeForwarded(CalleeTokenToScript(token));
case CalleeToken_Function:
case CalleeToken_FunctionConstructing: {
JSFunction* fun = MaybeForwarded(CalleeTokenToFunction(token));
return MaybeForwarded(fun)->nonLazyScript();
}
}
MOZ_CRASH("invalid callee token tag");
}
CalleeToken TraceCalleeToken(JSTracer* trc, CalleeToken token) {
switch (CalleeTokenTag tag = GetCalleeTokenTag(token)) {
case CalleeToken_Function:
case CalleeToken_FunctionConstructing: {
JSFunction* fun = CalleeTokenToFunction(token);
TraceRoot(trc, &fun, "jit-callee");
return CalleeToToken(fun, tag == CalleeToken_FunctionConstructing);
}
case CalleeToken_Script: {
JSScript* script = CalleeTokenToScript(token);
TraceRoot(trc, &script, "jit-script");
return CalleeToToken(script);
}
default:
MOZ_CRASH("unknown callee token type");
}
}
uintptr_t* JitFrameLayout::slotRef(SafepointSlotEntry where) {
if (where.stack) {
return (uintptr_t*)((uint8_t*)this - where.slot);
}
return (uintptr_t*)((uint8_t*)thisAndActualArgs() + where.slot);
}
#ifdef JS_NUNBOX32
static inline uintptr_t ReadAllocation(const JSJitFrameIter& frame,
const LAllocation* a) {
if (a->isGeneralReg()) {
Register reg = a->toGeneralReg()->reg();
return frame.machineState().read(reg);
}
return *frame.jsFrame()->slotRef(SafepointSlotEntry(a));
}
#endif
static void TraceThisAndArguments(JSTracer* trc, const JSJitFrameIter& frame,
JitFrameLayout* layout) {
// Trace |this| and any extra actual arguments for an Ion frame. Tracing
// of formal arguments is taken care of by the frame's safepoint/snapshot,
// except when the script might have lazy arguments or rest, in which case
// we trace them as well. We also have to trace formals if we have a
// LazyLink frame or an InterpreterStub frame or a special JSJit to wasm
// frame (since wasm doesn't use snapshots).
if (!CalleeTokenIsFunction(layout->calleeToken())) {
return;
}
size_t nargs = layout->numActualArgs();
size_t nformals = 0;
JSFunction* fun = CalleeTokenToFunction(layout->calleeToken());
if (frame.type() != FrameType::JSJitToWasm &&
!frame.isExitFrameLayout<CalledFromJitExitFrameLayout>() &&
!fun->nonLazyScript()->mayReadFrameArgsDirectly()) {
nformals = fun->nargs();
}
size_t newTargetOffset = std::max(nargs, fun->nargs());
Value* argv = layout->thisAndActualArgs();
// Trace |this|.
TraceRoot(trc, argv, "ion-thisv");
// Trace actual arguments beyond the formals. Note + 1 for thisv.
for (size_t i = nformals + 1; i < nargs + 1; i++) {
TraceRoot(trc, &argv[i], "ion-argv");
}
// Always trace the new.target from the frame. It's not in the snapshots.
// +1 to pass |this|
if (CalleeTokenIsConstructing(layout->calleeToken())) {
TraceRoot(trc, &argv[1 + newTargetOffset], "ion-newTarget");
}
}
#ifdef JS_NUNBOX32
static inline void WriteAllocation(const JSJitFrameIter& frame,
const LAllocation* a, uintptr_t value) {
if (a->isGeneralReg()) {
Register reg = a->toGeneralReg()->reg();
frame.machineState().write(reg, value);
} else {
*frame.jsFrame()->slotRef(SafepointSlotEntry(a)) = value;
}
}
#endif
static void TraceIonJSFrame(JSTracer* trc, const JSJitFrameIter& frame) {
JitFrameLayout* layout = (JitFrameLayout*)frame.fp();
layout->replaceCalleeToken(TraceCalleeToken(trc, layout->calleeToken()));
IonScript* ionScript = nullptr;
if (frame.checkInvalidation(&ionScript)) {
// This frame has been invalidated, meaning that its IonScript is no
// longer reachable through the callee token (JSFunction/JSScript->ion
// is now nullptr or recompiled). Manually trace it here.
ionScript->trace(trc);
} else {
ionScript = frame.ionScriptFromCalleeToken();
}
TraceThisAndArguments(trc, frame, frame.jsFrame());
const SafepointIndex* si =
ionScript->getSafepointIndex(frame.resumePCinCurrentFrame());
SafepointReader safepoint(ionScript, si);
// Scan through slots which contain pointers (or on punboxing systems,
// actual values).
SafepointSlotEntry entry;
while (safepoint.getGcSlot(&entry)) {
uintptr_t* ref = layout->slotRef(entry);
TraceGenericPointerRoot(trc, reinterpret_cast<gc::Cell**>(ref),
"ion-gc-slot");
}
uintptr_t* spill = frame.spillBase();
LiveGeneralRegisterSet gcRegs = safepoint.gcSpills();
LiveGeneralRegisterSet valueRegs = safepoint.valueSpills();
LiveGeneralRegisterSet wasmAnyRefRegs = safepoint.wasmAnyRefSpills();
for (GeneralRegisterBackwardIterator iter(safepoint.allGprSpills());
iter.more(); ++iter) {
--spill;
if (gcRegs.has(*iter)) {
TraceGenericPointerRoot(trc, reinterpret_cast<gc::Cell**>(spill),
"ion-gc-spill");
} else if (valueRegs.has(*iter)) {
TraceRoot(trc, reinterpret_cast<Value*>(spill), "ion-value-spill");
} else if (wasmAnyRefRegs.has(*iter)) {
TraceRoot(trc, reinterpret_cast<wasm::AnyRef*>(spill),
"ion-anyref-spill");
}
}
#ifdef JS_PUNBOX64
while (safepoint.getValueSlot(&entry)) {
Value* v = (Value*)layout->slotRef(entry);
TraceRoot(trc, v, "ion-gc-slot");
}
#else
LAllocation type, payload;
while (safepoint.getNunboxSlot(&type, &payload)) {
JSValueTag tag = JSValueTag(ReadAllocation(frame, &type));
uintptr_t rawPayload = ReadAllocation(frame, &payload);
Value v = Value::fromTagAndPayload(tag, rawPayload);
TraceRoot(trc, &v, "ion-torn-value");
if (v != Value::fromTagAndPayload(tag, rawPayload)) {
// GC moved the value, replace the stored payload.
rawPayload = v.toNunboxPayload();
WriteAllocation(frame, &payload, rawPayload);
}
}
#endif
// Skip over slots/elements to get to wasm anyrefs
while (safepoint.getSlotsOrElementsSlot(&entry)) {
}
while (safepoint.getWasmAnyRefSlot(&entry)) {
wasm::AnyRef* v = (wasm::AnyRef*)layout->slotRef(entry);
TraceRoot(trc, v, "ion-wasm-anyref-slot");
}
}
static void TraceBailoutFrame(JSTracer* trc, const JSJitFrameIter& frame) {
JitFrameLayout* layout = (JitFrameLayout*)frame.fp();
layout->replaceCalleeToken(TraceCalleeToken(trc, layout->calleeToken()));
// We have to trace the list of actual arguments, as only formal arguments
// are represented in the Snapshot.
TraceThisAndArguments(trc, frame, frame.jsFrame());
// Under a bailout, do not have a Safepoint to only iterate over GC-things.
// Thus we use a SnapshotIterator to trace all the locations which would be
// used to reconstruct the Baseline frame.
//
// Note that at the time where this function is called, we have not yet
// started to reconstruct baseline frames.
// The vector of recover instructions is already traced as part of the
// JitActivation.
SnapshotIterator snapIter(frame,
frame.activation()->bailoutData()->machineState());
// For each instruction, we read the allocations without evaluating the
// recover instruction, nor reconstructing the frame. We are only looking at
// tracing readable allocations.
while (true) {
while (snapIter.moreAllocations()) {
snapIter.traceAllocation(trc);
}
if (!snapIter.moreInstructions()) {
break;
}
snapIter.nextInstruction();
}
}
static void UpdateIonJSFrameForMinorGC(JSRuntime* rt,
const JSJitFrameIter& frame) {
// Minor GCs may move slots/elements allocated in the nursery. Update
// any slots/elements pointers stored in this frame.
JitFrameLayout* layout = (JitFrameLayout*)frame.fp();
IonScript* ionScript = nullptr;
if (frame.checkInvalidation(&ionScript)) {
// This frame has been invalidated, meaning that its IonScript is no
// longer reachable through the callee token (JSFunction/JSScript->ion
// is now nullptr or recompiled).
} else {
ionScript = frame.ionScriptFromCalleeToken();
}
Nursery& nursery = rt->gc.nursery();
const SafepointIndex* si =
ionScript->getSafepointIndex(frame.resumePCinCurrentFrame());
SafepointReader safepoint(ionScript, si);
LiveGeneralRegisterSet slotsRegs = safepoint.slotsOrElementsSpills();
uintptr_t* spill = frame.spillBase();
for (GeneralRegisterBackwardIterator iter(safepoint.allGprSpills());
iter.more(); ++iter) {
--spill;
if (slotsRegs.has(*iter)) {
nursery.forwardBufferPointer(spill);
}
}
// Skip to the right place in the safepoint
SafepointSlotEntry entry;
while (safepoint.getGcSlot(&entry)) {
}
#ifdef JS_PUNBOX64
while (safepoint.getValueSlot(&entry)) {
}
#else
LAllocation type, payload;
while (safepoint.getNunboxSlot(&type, &payload)) {
}
#endif
while (safepoint.getSlotsOrElementsSlot(&entry)) {
nursery.forwardBufferPointer(layout->slotRef(entry));
}
}
static void TraceBaselineStubFrame(JSTracer* trc, const JSJitFrameIter& frame) {
// Trace the ICStub pointer stored in the stub frame. This is necessary
// so that we don't destroy the stub code after unlinking the stub.
MOZ_ASSERT(frame.type() == FrameType::BaselineStub);
BaselineStubFrameLayout* layout = (BaselineStubFrameLayout*)frame.fp();
if (ICStub* stub = layout->maybeStubPtr()) {
if (stub->isFallback()) {
// Fallback stubs use runtime-wide trampoline code we don't need to trace.
MOZ_ASSERT(stub->usesTrampolineCode());
} else {
MOZ_ASSERT(stub->toCacheIRStub()->makesGCCalls());
stub->toCacheIRStub()->trace(trc);
for (int i = 0; i < stub->jitCode()->localTracingSlots(); ++i) {
TraceRoot(trc, layout->locallyTracedValuePtr(i),
"baseline-local-tracing-slot");
}
}
}
}
static void TraceWeakBaselineStubFrame(JSTracer* trc,
const JSJitFrameIter& frame) {
MOZ_ASSERT(frame.type() == FrameType::BaselineStub);
BaselineStubFrameLayout* layout = (BaselineStubFrameLayout*)frame.fp();
if (ICStub* stub = layout->maybeStubPtr()) {
if (!stub->isFallback()) {
MOZ_ASSERT(stub->toCacheIRStub()->makesGCCalls());
stub->toCacheIRStub()->traceWeak(trc);
}
}
}
static void TraceIonICCallFrame(JSTracer* trc, const JSJitFrameIter& frame) {
MOZ_ASSERT(frame.type() == FrameType::IonICCall);
IonICCallFrameLayout* layout = (IonICCallFrameLayout*)frame.fp();
TraceRoot(trc, layout->stubCode(), "ion-ic-call-code");
for (int i = 0; i < (*layout->stubCode())->localTracingSlots(); ++i) {
TraceRoot(trc, layout->locallyTracedValuePtr(i),
"ion-ic-local-tracing-slot");
}
}
#if defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_MIPS32)
uint8_t* alignDoubleSpill(uint8_t* pointer) {
uintptr_t address = reinterpret_cast<uintptr_t>(pointer);
address &= ~(uintptr_t(ABIStackAlignment) - 1);
return reinterpret_cast<uint8_t*>(address);
}
#endif
#ifdef JS_CODEGEN_MIPS32
static void TraceJitExitFrameCopiedArguments(JSTracer* trc,
const VMFunctionData* f,
ExitFooterFrame* footer) {
uint8_t* doubleArgs = footer->alignedForABI();
if (f->outParam == Type_Handle) {
doubleArgs -= sizeof(Value);
}
doubleArgs -= f->doubleByRefArgs() * sizeof(double);
for (uint32_t explicitArg = 0; explicitArg < f->explicitArgs; explicitArg++) {
if (f->argProperties(explicitArg) == VMFunctionData::DoubleByRef) {
// Arguments with double size can only have RootValue type.
if (f->argRootType(explicitArg) == VMFunctionData::RootValue) {
TraceRoot(trc, reinterpret_cast<Value*>(doubleArgs), "ion-vm-args");
} else {
MOZ_ASSERT(f->argRootType(explicitArg) == VMFunctionData::RootNone);
}
doubleArgs += sizeof(double);
}
}
}
#else
static void TraceJitExitFrameCopiedArguments(JSTracer* trc,
const VMFunctionData* f,
ExitFooterFrame* footer) {
// This is NO-OP on other platforms.
}
#endif
static void TraceJitExitFrame(JSTracer* trc, const JSJitFrameIter& frame) {
ExitFooterFrame* footer = frame.exitFrame()->footer();
// This corresponds to the case where we have build a fake exit frame which
// handles the case of a native function call. We need to trace the argument
// vector of the function call, and also new.target if it was a constructing
// call.
if (frame.isExitFrameLayout<NativeExitFrameLayout>()) {
NativeExitFrameLayout* native =
frame.exitFrame()->as<NativeExitFrameLayout>();
size_t len = native->argc() + 2;
Value* vp = native->vp();
TraceRootRange(trc, len, vp, "ion-native-args");
if (frame.isExitFrameLayout<ConstructNativeExitFrameLayout>()) {
TraceRoot(trc, vp + len, "ion-native-new-target");
}
return;
}
if (frame.isExitFrameLayout<IonOOLNativeExitFrameLayout>()) {
IonOOLNativeExitFrameLayout* oolnative =
frame.exitFrame()->as<IonOOLNativeExitFrameLayout>();
TraceRoot(trc, oolnative->stubCode(), "ion-ool-native-code");
TraceRoot(trc, oolnative->vp(), "iol-ool-native-vp");
size_t len = oolnative->argc() + 1;
TraceRootRange(trc, len, oolnative->thisp(), "ion-ool-native-thisargs");
return;
}
if (frame.isExitFrameLayout<IonOOLProxyExitFrameLayout>()) {
IonOOLProxyExitFrameLayout* oolproxy =
frame.exitFrame()->as<IonOOLProxyExitFrameLayout>();
TraceRoot(trc, oolproxy->stubCode(), "ion-ool-proxy-code");
TraceRoot(trc, oolproxy->vp(), "ion-ool-proxy-vp");
TraceRoot(trc, oolproxy->id(), "ion-ool-proxy-id");
TraceRoot(trc, oolproxy->proxy(), "ion-ool-proxy-proxy");
return;
}
if (frame.isExitFrameLayout<IonDOMExitFrameLayout>()) {
IonDOMExitFrameLayout* dom = frame.exitFrame()->as<IonDOMExitFrameLayout>();
TraceRoot(trc, dom->thisObjAddress(), "ion-dom-args");
if (dom->isMethodFrame()) {
IonDOMMethodExitFrameLayout* method =
reinterpret_cast<IonDOMMethodExitFrameLayout*>(dom);
size_t len = method->argc() + 2;
Value* vp = method->vp();
TraceRootRange(trc, len, vp, "ion-dom-args");
} else {
TraceRoot(trc, dom->vp(), "ion-dom-args");
}
return;
}
if (frame.isExitFrameLayout<CalledFromJitExitFrameLayout>()) {
auto* layout = frame.exitFrame()->as<CalledFromJitExitFrameLayout>();
JitFrameLayout* jsLayout = layout->jsFrame();
jsLayout->replaceCalleeToken(
TraceCalleeToken(trc, jsLayout->calleeToken()));
TraceThisAndArguments(trc, frame, jsLayout);
return;
}
if (frame.isExitFrameLayout<DirectWasmJitCallFrameLayout>()) {
// Nothing needs to be traced here at the moment -- the arguments to the
// callee are traced by the callee, and the inlined caller does not push
// anything else.
return;
}
if (frame.isBareExit() || frame.isUnwoundJitExit()) {
// Nothing to trace. Fake exit frame pushed for VM functions with
// nothing to trace on the stack or unwound JitFrameLayout.
return;
}
MOZ_ASSERT(frame.exitFrame()->isWrapperExit());
const VMFunctionData* f = footer->function();
MOZ_ASSERT(f);
// Trace arguments of the VM wrapper.
uint8_t* argBase = frame.exitFrame()->argBase();
for (uint32_t explicitArg = 0; explicitArg < f->explicitArgs; explicitArg++) {
switch (f->argRootType(explicitArg)) {
case VMFunctionData::RootNone:
break;
case VMFunctionData::RootObject: {
// Sometimes we can bake in HandleObjects to nullptr.
JSObject** pobj = reinterpret_cast<JSObject**>(argBase);
if (*pobj) {
TraceRoot(trc, pobj, "ion-vm-args");
}
break;
}
case VMFunctionData::RootString:
TraceRoot(trc, reinterpret_cast<JSString**>(argBase), "ion-vm-args");
break;
case VMFunctionData::RootValue:
TraceRoot(trc, reinterpret_cast<Value*>(argBase), "ion-vm-args");
break;
case VMFunctionData::RootId:
TraceRoot(trc, reinterpret_cast<jsid*>(argBase), "ion-vm-args");
break;
case VMFunctionData::RootCell:
TraceGenericPointerRoot(trc, reinterpret_cast<gc::Cell**>(argBase),
"ion-vm-args");
break;
case VMFunctionData::RootBigInt:
TraceRoot(trc, reinterpret_cast<JS::BigInt**>(argBase), "ion-vm-args");
break;
}
switch (f->argProperties(explicitArg)) {
case VMFunctionData::WordByValue:
case VMFunctionData::WordByRef:
argBase += sizeof(void*);
break;
case VMFunctionData::DoubleByValue:
case VMFunctionData::DoubleByRef:
argBase += 2 * sizeof(void*);
break;
}
}
if (f->outParam == Type_Handle) {
switch (f->outParamRootType) {
case VMFunctionData::RootNone:
MOZ_CRASH("Handle outparam must have root type");
case VMFunctionData::RootObject:
TraceRoot(trc, footer->outParam<JSObject*>(), "ion-vm-out");
break;
case VMFunctionData::RootString:
TraceRoot(trc, footer->outParam<JSString*>(), "ion-vm-out");
break;
case VMFunctionData::RootValue:
TraceRoot(trc, footer->outParam<Value>(), "ion-vm-outvp");
break;
case VMFunctionData::RootId:
TraceRoot(trc, footer->outParam<jsid>(), "ion-vm-outvp");
break;
case VMFunctionData::RootCell:
TraceGenericPointerRoot(trc, footer->outParam<gc::Cell*>(),
"ion-vm-out");
break;
case VMFunctionData::RootBigInt:
TraceRoot(trc, footer->outParam<JS::BigInt*>(), "ion-vm-out");
break;
}
}
TraceJitExitFrameCopiedArguments(trc, f, footer);
}
static void TraceBaselineInterpreterEntryFrame(JSTracer* trc,
const JSJitFrameIter& frame) {
// Baseline Interpreter entry code generated under --emit-interpreter-entry.
BaselineInterpreterEntryFrameLayout* layout =
(BaselineInterpreterEntryFrameLayout*)frame.fp();
layout->replaceCalleeToken(TraceCalleeToken(trc, layout->calleeToken()));
TraceThisAndArguments(trc, frame, layout);
}
static void TraceRectifierFrame(JSTracer* trc, const JSJitFrameIter& frame) {
// Trace thisv.
//
// Baseline JIT code generated as part of the ICCall_Fallback stub may use
// it if we're calling a constructor that returns a primitive value.
RectifierFrameLayout* layout = (RectifierFrameLayout*)frame.fp();
TraceRoot(trc, &layout->thisv(), "rectifier-thisv");
}
static void TraceJSJitToWasmFrame(JSTracer* trc, const JSJitFrameIter& frame) {
// This is doing a subset of TraceIonJSFrame, since the callee doesn't
// have a script.
JitFrameLayout* layout = (JitFrameLayout*)frame.fp();
layout->replaceCalleeToken(TraceCalleeToken(trc, layout->calleeToken()));
TraceThisAndArguments(trc, frame, layout);
}
static void TraceJitActivation(JSTracer* trc, JitActivation* activation) {
#ifdef CHECK_OSIPOINT_REGISTERS
if (JitOptions.checkOsiPointRegisters) {
// GC can modify spilled registers, breaking our register checks.
// To handle this, we disable these checks for the current VM call
// when a GC happens.
activation->setCheckRegs(false);
}
#endif
activation->traceRematerializedFrames(trc);
activation->traceIonRecovery(trc);
// This is used for sanity checking continuity of the sequence of wasm stack
// maps as we unwind. It has no functional purpose.
uintptr_t highestByteVisitedInPrevWasmFrame = 0;
for (JitFrameIter frames(activation); !frames.done(); ++frames) {
if (frames.isJSJit()) {
const JSJitFrameIter& jitFrame = frames.asJSJit();
switch (jitFrame.type()) {
case FrameType::Exit:
TraceJitExitFrame(trc, jitFrame);
break;
case FrameType::BaselineJS:
jitFrame.baselineFrame()->trace(trc, jitFrame);
break;
case FrameType::IonJS:
TraceIonJSFrame(trc, jitFrame);
break;
case FrameType::BaselineStub:
TraceBaselineStubFrame(trc, jitFrame);
break;
case FrameType::Bailout:
TraceBailoutFrame(trc, jitFrame);
break;
case FrameType::BaselineInterpreterEntry:
TraceBaselineInterpreterEntryFrame(trc, jitFrame);
break;
case FrameType::Rectifier:
TraceRectifierFrame(trc, jitFrame);
break;
case FrameType::IonICCall:
TraceIonICCallFrame(trc, jitFrame);
break;
case FrameType::WasmToJSJit:
// Ignore: this is a special marker used to let the
// JitFrameIter know the frame above is a wasm frame, handled
// in the next iteration.
break;
case FrameType::JSJitToWasm:
TraceJSJitToWasmFrame(trc, jitFrame);
break;
default:
MOZ_CRASH("unexpected frame type");
}
highestByteVisitedInPrevWasmFrame = 0; /* "unknown" */
} else {
MOZ_ASSERT(frames.isWasm());
uint8_t* nextPC = frames.resumePCinCurrentFrame();
MOZ_ASSERT(nextPC != 0);
wasm::WasmFrameIter& wasmFrameIter = frames.asWasm();
wasm::Instance* instance = wasmFrameIter.instance();
wasm::TraceInstanceEdge(trc, instance, "WasmFrameIter instance");
highestByteVisitedInPrevWasmFrame = instance->traceFrame(
trc, wasmFrameIter, nextPC, highestByteVisitedInPrevWasmFrame);
}
}
}
void TraceJitActivations(JSContext* cx, JSTracer* trc) {
for (JitActivationIterator activations(cx); !activations.done();
++activations) {
TraceJitActivation(trc, activations->asJit());
}
}
void TraceWeakJitActivationsInSweepingZones(JSContext* cx, JSTracer* trc) {
for (JitActivationIterator activation(cx); !activation.done(); ++activation) {
if (activation->compartment()->zone()->isGCSweeping()) {
for (JitFrameIter frame(activation->asJit()); !frame.done(); ++frame) {
if (frame.isJSJit()) {
const JSJitFrameIter& jitFrame = frame.asJSJit();
if (jitFrame.type() == FrameType::BaselineStub) {
TraceWeakBaselineStubFrame(trc, jitFrame);
}
}
}
}
}
}
void UpdateJitActivationsForMinorGC(JSRuntime* rt) {
MOZ_ASSERT(JS::RuntimeHeapIsMinorCollecting());
JSContext* cx = rt->mainContextFromOwnThread();
for (JitActivationIterator activations(cx); !activations.done();
++activations) {
for (OnlyJSJitFrameIter iter(activations); !iter.done(); ++iter) {
if (iter.frame().type() == FrameType::IonJS) {
UpdateIonJSFrameForMinorGC(rt, iter.frame());
}
}
}
}
JSScript* GetTopJitJSScript(JSContext* cx) {
JSJitFrameIter frame(cx->activation()->asJit());
MOZ_ASSERT(frame.type() == FrameType::Exit);
++frame;
if (frame.isBaselineStub()) {
++frame;
MOZ_ASSERT(frame.isBaselineJS());
}
MOZ_ASSERT(frame.isScripted());
return frame.script();
}
void GetPcScript(JSContext* cx, JSScript** scriptRes, jsbytecode** pcRes) {
JitSpew(JitSpew_IonSnapshots, "Recover PC & Script from the last frame.");
// Recover the return address so that we can look it up in the
// PcScriptCache, as script/pc computation is expensive.
JitActivationIterator actIter(cx);
OnlyJSJitFrameIter it(actIter);
uint8_t* retAddr;
if (it.frame().isExitFrame()) {
++it;
// Skip baseline interpreter entry frames.
// Can exist before rectifier frames.
if (it.frame().isBaselineInterpreterEntry()) {
++it;
}
// Skip rectifier frames.
if (it.frame().isRectifier()) {
++it;
MOZ_ASSERT(it.frame().isBaselineStub() || it.frame().isBaselineJS() ||
it.frame().isIonJS());
}
// Skip Baseline/Ion stub and IC call frames.
if (it.frame().isBaselineStub()) {
++it;
MOZ_ASSERT(it.frame().isBaselineJS());
} else if (it.frame().isIonICCall()) {
++it;
MOZ_ASSERT(it.frame().isIonJS());
}
MOZ_ASSERT(it.frame().isBaselineJS() || it.frame().isIonJS());
// Don't use the return address and the cache if the BaselineFrame is
// running in the Baseline Interpreter. In this case the bytecode pc is
// cheap to get, so we won't benefit from the cache, and the return address
// does not map to a single bytecode pc.
if (it.frame().isBaselineJS() &&
it.frame().baselineFrame()->runningInInterpreter()) {
it.frame().baselineScriptAndPc(scriptRes, pcRes);
return;
}
retAddr = it.frame().resumePCinCurrentFrame();
} else {
MOZ_ASSERT(it.frame().isBailoutJS());
retAddr = it.frame().returnAddress();
}
MOZ_ASSERT(retAddr);
uint32_t hash = PcScriptCache::Hash(retAddr);
// Lazily initialize the cache. The allocation may safely fail and will not
// GC.