fune/js/src/jit/JSJitFrameIter.cpp
Benjamin Bouvier 9eedb4045d Bug 1437065: Inline monomorphic calls to wasm; r=luke, r=jandem
--HG--
extra : rebase_source : fdf0feee57bbde518368beba2d6056680fe72207
2018-08-24 15:27:20 +02:00

797 lines
23 KiB
C++

/* -*- 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/JSJitFrameIter-inl.h"
#include "jit/BaselineDebugModeOSR.h"
#include "jit/BaselineIC.h"
#include "jit/JitcodeMap.h"
#include "jit/JitFrames.h"
#include "jit/Safepoints.h"
using namespace js;
using namespace js::jit;
JSJitFrameIter::JSJitFrameIter(const JitActivation* activation)
: current_(activation->jsExitFP()),
type_(JitFrame_Exit),
returnAddressToFp_(nullptr),
frameSize_(0),
cachedSafepointIndex_(nullptr),
activation_(activation)
{
if (activation_->bailoutData()) {
current_ = activation_->bailoutData()->fp();
frameSize_ = activation_->bailoutData()->topFrameSize();
type_ = JitFrame_Bailout;
} else {
MOZ_ASSERT(!TlsContext.get()->inUnsafeCallWithABI);
}
}
JSJitFrameIter::JSJitFrameIter(const JitActivation* activation, FrameType frameType, uint8_t* fp)
: current_(fp),
type_(frameType),
returnAddressToFp_(nullptr),
frameSize_(0),
cachedSafepointIndex_(nullptr),
activation_(activation)
{
MOZ_ASSERT(type_ == JitFrame_JSJitToWasm || type_ == JitFrame_Exit);
MOZ_ASSERT(!activation_->bailoutData());
MOZ_ASSERT(!TlsContext.get()->inUnsafeCallWithABI);
}
bool
JSJitFrameIter::checkInvalidation() const
{
IonScript* dummy;
return checkInvalidation(&dummy);
}
bool
JSJitFrameIter::checkInvalidation(IonScript** ionScriptOut) const
{
JSScript* script = this->script();
if (isBailoutJS()) {
*ionScriptOut = activation_->bailoutData()->ionScript();
return !script->hasIonScript() || script->ionScript() != *ionScriptOut;
}
uint8_t* returnAddr = returnAddressToFp();
// N.B. the current IonScript is not the same as the frame's
// IonScript if the frame has since been invalidated.
bool invalidated = !script->hasIonScript() ||
!script->ionScript()->containsReturnAddress(returnAddr);
if (!invalidated)
return false;
int32_t invalidationDataOffset = ((int32_t*) returnAddr)[-1];
uint8_t* ionScriptDataOffset = returnAddr + invalidationDataOffset;
IonScript* ionScript = (IonScript*) Assembler::GetPointer(ionScriptDataOffset);
MOZ_ASSERT(ionScript->containsReturnAddress(returnAddr));
*ionScriptOut = ionScript;
return true;
}
CalleeToken
JSJitFrameIter::calleeToken() const
{
return ((JitFrameLayout*) current_)->calleeToken();
}
JSFunction*
JSJitFrameIter::callee() const
{
MOZ_ASSERT(isScripted());
MOZ_ASSERT(isFunctionFrame());
return CalleeTokenToFunction(calleeToken());
}
JSFunction*
JSJitFrameIter::maybeCallee() const
{
if (isScripted() && isFunctionFrame())
return callee();
return nullptr;
}
bool
JSJitFrameIter::isBareExit() const
{
if (type_ != JitFrame_Exit)
return false;
return exitFrame()->isBareExit();
}
bool
JSJitFrameIter::isFunctionFrame() const
{
return CalleeTokenIsFunction(calleeToken());
}
JSScript*
JSJitFrameIter::script() const
{
MOZ_ASSERT(isScripted());
if (isBaselineJS())
return baselineFrame()->script();
JSScript* script = ScriptFromCalleeToken(calleeToken());
MOZ_ASSERT(script);
return script;
}
void
JSJitFrameIter::baselineScriptAndPc(JSScript** scriptRes, jsbytecode** pcRes) const
{
MOZ_ASSERT(isBaselineJS());
JSScript* script = this->script();
if (scriptRes)
*scriptRes = script;
MOZ_ASSERT(pcRes);
// Use the frame's override pc, if we have one. This should only happen
// when we're in FinishBailoutToBaseline, handling an exception or toggling
// debug mode.
if (jsbytecode* overridePc = baselineFrame()->maybeOverridePc()) {
*pcRes = overridePc;
return;
}
// Else, there must be an ICEntry for the current return address.
uint8_t* retAddr = returnAddressToFp();
ICEntry& icEntry = script->baselineScript()->icEntryFromReturnAddress(retAddr);
*pcRes = icEntry.pc(script);
}
Value*
JSJitFrameIter::actualArgs() const
{
return jsFrame()->argv() + 1;
}
uint8_t*
JSJitFrameIter::prevFp() const
{
return current_ + current()->prevFrameLocalSize() + current()->headerSize();
}
void
JSJitFrameIter::operator++()
{
MOZ_ASSERT(!isEntry());
frameSize_ = prevFrameLocalSize();
cachedSafepointIndex_ = nullptr;
// If the next frame is the entry frame, just exit. Don't update current_,
// since the entry and first frames overlap.
if (isEntry(current()->prevType())) {
type_ = current()->prevType();
return;
}
type_ = current()->prevType();
returnAddressToFp_ = current()->returnAddress();
current_ = prevFp();
}
uintptr_t*
JSJitFrameIter::spillBase() const
{
MOZ_ASSERT(isIonJS());
// Get the base address to where safepoint registers are spilled.
// Out-of-line calls do not unwind the extra padding space used to
// aggregate bailout tables, so we use frameSize instead of frameLocals,
// which would only account for local stack slots.
return reinterpret_cast<uintptr_t*>(fp() - ionScript()->frameSize());
}
MachineState
JSJitFrameIter::machineState() const
{
MOZ_ASSERT(isIonScripted());
// The MachineState is used by GCs for tracing call-sites.
if (MOZ_UNLIKELY(isBailoutJS()))
return *activation_->bailoutData()->machineState();
SafepointReader reader(ionScript(), safepoint());
uintptr_t* spill = spillBase();
MachineState machine;
for (GeneralRegisterBackwardIterator iter(reader.allGprSpills()); iter.more(); ++iter)
machine.setRegisterLocation(*iter, --spill);
uint8_t* spillAlign = alignDoubleSpillWithOffset(reinterpret_cast<uint8_t*>(spill), 0);
char* floatSpill = reinterpret_cast<char*>(spillAlign);
FloatRegisterSet fregs = reader.allFloatSpills().set();
fregs = fregs.reduceSetForPush();
for (FloatRegisterBackwardIterator iter(fregs); iter.more(); ++iter) {
floatSpill -= (*iter).size();
for (uint32_t a = 0; a < (*iter).numAlignedAliased(); a++) {
// Only say that registers that actually start here start here.
// e.g. d0 should not start at s1, only at s0.
FloatRegister ftmp = (*iter).alignedAliased(a);
machine.setRegisterLocation(ftmp, (double*)floatSpill);
}
}
return machine;
}
JitFrameLayout*
JSJitFrameIter::jsFrame() const
{
MOZ_ASSERT(isScripted());
if (isBailoutJS())
return (JitFrameLayout*) activation_->bailoutData()->fp();
return (JitFrameLayout*) fp();
}
IonScript*
JSJitFrameIter::ionScript() const
{
MOZ_ASSERT(isIonScripted());
if (isBailoutJS())
return activation_->bailoutData()->ionScript();
IonScript* ionScript = nullptr;
if (checkInvalidation(&ionScript))
return ionScript;
return ionScriptFromCalleeToken();
}
IonScript*
JSJitFrameIter::ionScriptFromCalleeToken() const
{
MOZ_ASSERT(isIonJS());
MOZ_ASSERT(!checkInvalidation());
return script()->ionScript();
}
const SafepointIndex*
JSJitFrameIter::safepoint() const
{
MOZ_ASSERT(isIonJS());
if (!cachedSafepointIndex_)
cachedSafepointIndex_ = ionScript()->getSafepointIndex(returnAddressToFp());
return cachedSafepointIndex_;
}
SnapshotOffset
JSJitFrameIter::snapshotOffset() const
{
MOZ_ASSERT(isIonScripted());
if (isBailoutJS())
return activation_->bailoutData()->snapshotOffset();
return osiIndex()->snapshotOffset();
}
const OsiIndex*
JSJitFrameIter::osiIndex() const
{
MOZ_ASSERT(isIonJS());
SafepointReader reader(ionScript(), safepoint());
return ionScript()->getOsiIndex(reader.osiReturnPointOffset());
}
bool
JSJitFrameIter::isConstructing() const
{
return CalleeTokenIsConstructing(calleeToken());
}
unsigned
JSJitFrameIter::numActualArgs() const
{
if (isScripted())
return jsFrame()->numActualArgs();
MOZ_ASSERT(isExitFrameLayout<NativeExitFrameLayout>());
return exitFrame()->as<NativeExitFrameLayout>()->argc();
}
void
JSJitFrameIter::dumpBaseline() const
{
MOZ_ASSERT(isBaselineJS());
fprintf(stderr, " JS Baseline frame\n");
if (isFunctionFrame()) {
fprintf(stderr, " callee fun: ");
#if defined(DEBUG) || defined(JS_JITSPEW)
DumpObject(callee());
#else
fprintf(stderr, "?\n");
#endif
} else {
fprintf(stderr, " global frame, no callee\n");
}
fprintf(stderr, " file %s line %u\n",
script()->filename(), script()->lineno());
JSContext* cx = TlsContext.get();
RootedScript script(cx);
jsbytecode* pc;
baselineScriptAndPc(script.address(), &pc);
fprintf(stderr, " script = %p, pc = %p (offset %u)\n", (void*)script, pc, uint32_t(script->pcToOffset(pc)));
fprintf(stderr, " current op: %s\n", CodeName[*pc]);
fprintf(stderr, " actual args: %d\n", numActualArgs());
BaselineFrame* frame = baselineFrame();
for (unsigned i = 0; i < frame->numValueSlots(); i++) {
fprintf(stderr, " slot %u: ", i);
#if defined(DEBUG) || defined(JS_JITSPEW)
Value* v = frame->valueSlot(i);
DumpValue(*v);
#else
fprintf(stderr, "?\n");
#endif
}
}
void
JSJitFrameIter::dump() const
{
switch (type_) {
case JitFrame_CppToJSJit:
fprintf(stderr, " Entry frame\n");
fprintf(stderr, " Frame size: %u\n", unsigned(current()->prevFrameLocalSize()));
break;
case JitFrame_BaselineJS:
dumpBaseline();
break;
case JitFrame_BaselineStub:
fprintf(stderr, " Baseline stub frame\n");
fprintf(stderr, " Frame size: %u\n", unsigned(current()->prevFrameLocalSize()));
break;
case JitFrame_Bailout:
case JitFrame_IonJS:
{
InlineFrameIterator frames(TlsContext.get(), this);
for (;;) {
frames.dump();
if (!frames.more())
break;
++frames;
}
break;
}
case JitFrame_Rectifier:
fprintf(stderr, " Rectifier frame\n");
fprintf(stderr, " Frame size: %u\n", unsigned(current()->prevFrameLocalSize()));
break;
case JitFrame_IonICCall:
fprintf(stderr, " Ion IC call\n");
fprintf(stderr, " Frame size: %u\n", unsigned(current()->prevFrameLocalSize()));
break;
case JitFrame_WasmToJSJit:
fprintf(stderr, " Fast wasm-to-JS entry frame\n");
fprintf(stderr, " Frame size: %u\n", unsigned(current()->prevFrameLocalSize()));
break;
case JitFrame_Exit:
fprintf(stderr, " Exit frame\n");
break;
case JitFrame_JSJitToWasm:
fprintf(stderr, " Wasm exit frame\n");
break;
};
fputc('\n', stderr);
}
#ifdef DEBUG
bool
JSJitFrameIter::verifyReturnAddressUsingNativeToBytecodeMap()
{
MOZ_ASSERT(returnAddressToFp_ != nullptr);
// Only handle Ion frames for now.
if (type_ != JitFrame_IonJS && type_ != JitFrame_BaselineJS)
return true;
JSRuntime* rt = TlsContext.get()->runtime();
// Don't verify while off thread.
if (!CurrentThreadCanAccessRuntime(rt))
return true;
// Don't verify if sampling is being suppressed.
if (!TlsContext.get()->isProfilerSamplingEnabled())
return true;
if (JS::RuntimeHeapIsMinorCollecting())
return true;
JitRuntime* jitrt = rt->jitRuntime();
// Look up and print bytecode info for the native address.
const JitcodeGlobalEntry* entry = jitrt->getJitcodeGlobalTable()->lookup(returnAddressToFp_);
if (!entry)
return true;
JitSpew(JitSpew_Profiling, "Found nativeToBytecode entry for %p: %p - %p",
returnAddressToFp_, entry->nativeStartAddr(), entry->nativeEndAddr());
JitcodeGlobalEntry::BytecodeLocationVector location;
uint32_t depth = UINT32_MAX;
if (!entry->callStackAtAddr(rt, returnAddressToFp_, location, &depth))
return false;
MOZ_ASSERT(depth > 0 && depth != UINT32_MAX);
MOZ_ASSERT(location.length() == depth);
JitSpew(JitSpew_Profiling, "Found bytecode location of depth %d:", depth);
for (size_t i = 0; i < location.length(); i++) {
JitSpew(JitSpew_Profiling, " %s:%u - %zu",
location[i].script->filename(), location[i].script->lineno(),
size_t(location[i].pc - location[i].script->code()));
}
if (type_ == JitFrame_IonJS) {
// Create an InlineFrameIterator here and verify the mapped info against the iterator info.
InlineFrameIterator inlineFrames(TlsContext.get(), this);
for (size_t idx = 0; idx < location.length(); idx++) {
MOZ_ASSERT(idx < location.length());
MOZ_ASSERT_IF(idx < location.length() - 1, inlineFrames.more());
JitSpew(JitSpew_Profiling,
"Match %d: ION %s:%u(%zu) vs N2B %s:%u(%zu)",
(int)idx,
inlineFrames.script()->filename(),
inlineFrames.script()->lineno(),
size_t(inlineFrames.pc() - inlineFrames.script()->code()),
location[idx].script->filename(),
location[idx].script->lineno(),
size_t(location[idx].pc - location[idx].script->code()));
MOZ_ASSERT(inlineFrames.script() == location[idx].script);
if (inlineFrames.more())
++inlineFrames;
}
}
return true;
}
#endif // DEBUG
JSJitProfilingFrameIterator::JSJitProfilingFrameIterator(JSContext* cx, void* pc)
{
// If no profilingActivation is live, initialize directly to
// end-of-iteration state.
if (!cx->profilingActivation()) {
type_ = JitFrame_CppToJSJit;
fp_ = nullptr;
returnAddressToFp_ = nullptr;
return;
}
MOZ_ASSERT(cx->profilingActivation()->isJit());
JitActivation* act = cx->profilingActivation()->asJit();
// If the top JitActivation has a null lastProfilingFrame, assume that
// it's a trivially empty activation, and initialize directly
// to end-of-iteration state.
if (!act->lastProfilingFrame()) {
type_ = JitFrame_CppToJSJit;
fp_ = nullptr;
returnAddressToFp_ = nullptr;
return;
}
// Get the fp from the current profilingActivation
fp_ = (uint8_t*) act->lastProfilingFrame();
// Profiler sampling must NOT be suppressed if we are here.
MOZ_ASSERT(cx->isProfilerSamplingEnabled());
// Try initializing with sampler pc
if (tryInitWithPC(pc))
return;
// Try initializing with sampler pc using native=>bytecode table.
JitcodeGlobalTable* table = cx->runtime()->jitRuntime()->getJitcodeGlobalTable();
if (tryInitWithTable(table, pc, /* forLastCallSite = */ false))
return;
// Try initializing with lastProfilingCallSite pc
void* lastCallSite = act->lastProfilingCallSite();
if (lastCallSite) {
if (tryInitWithPC(lastCallSite))
return;
// Try initializing with lastProfilingCallSite pc using native=>bytecode table.
if (tryInitWithTable(table, lastCallSite, /* forLastCallSite = */ true))
return;
}
MOZ_ASSERT(frameScript()->hasBaselineScript());
// If nothing matches, for now just assume we are at the start of the last frame's
// baseline jit code.
type_ = JitFrame_BaselineJS;
returnAddressToFp_ = frameScript()->baselineScript()->method()->raw();
}
template <typename ReturnType = CommonFrameLayout*>
static inline ReturnType
GetPreviousRawFrame(CommonFrameLayout* frame)
{
size_t prevSize = frame->prevFrameLocalSize() + frame->headerSize();
return ReturnType((uint8_t*)frame + prevSize);
}
JSJitProfilingFrameIterator::JSJitProfilingFrameIterator(CommonFrameLayout* fp)
{
moveToNextFrame(fp);
}
bool
JSJitProfilingFrameIterator::tryInitWithPC(void* pc)
{
JSScript* callee = frameScript();
// Check for Ion first, since it's more likely for hot code.
if (callee->hasIonScript() && callee->ionScript()->method()->containsNativePC(pc)) {
type_ = JitFrame_IonJS;
returnAddressToFp_ = pc;
return true;
}
// Check for containment in Baseline jitcode second.
if (callee->hasBaselineScript() && callee->baselineScript()->method()->containsNativePC(pc)) {
type_ = JitFrame_BaselineJS;
returnAddressToFp_ = pc;
return true;
}
return false;
}
bool
JSJitProfilingFrameIterator::tryInitWithTable(JitcodeGlobalTable* table, void* pc,
bool forLastCallSite)
{
if (!pc)
return false;
const JitcodeGlobalEntry* entry = table->lookup(pc);
if (!entry)
return false;
JSScript* callee = frameScript();
MOZ_ASSERT(entry->isIon() || entry->isBaseline() || entry->isIonCache() || entry->isDummy());
// Treat dummy lookups as an empty frame sequence.
if (entry->isDummy()) {
type_ = JitFrame_CppToJSJit;
fp_ = nullptr;
returnAddressToFp_ = nullptr;
return true;
}
if (entry->isIon()) {
// If looked-up callee doesn't match frame callee, don't accept lastProfilingCallSite
if (entry->ionEntry().getScript(0) != callee)
return false;
type_ = JitFrame_IonJS;
returnAddressToFp_ = pc;
return true;
}
if (entry->isBaseline()) {
// If looked-up callee doesn't match frame callee, don't accept lastProfilingCallSite
if (forLastCallSite && entry->baselineEntry().script() != callee)
return false;
type_ = JitFrame_BaselineJS;
returnAddressToFp_ = pc;
return true;
}
if (entry->isIonCache()) {
void* ptr = entry->ionCacheEntry().rejoinAddr();
const JitcodeGlobalEntry& ionEntry = table->lookupInfallible(ptr);
MOZ_ASSERT(ionEntry.isIon());
if (ionEntry.ionEntry().getScript(0) != callee)
return false;
type_ = JitFrame_IonJS;
returnAddressToFp_ = pc;
return true;
}
return false;
}
void
JSJitProfilingFrameIterator::fixBaselineReturnAddress()
{
MOZ_ASSERT(type_ == JitFrame_BaselineJS);
BaselineFrame* bl = (BaselineFrame*)(fp_ - BaselineFrame::FramePointerOffset -
BaselineFrame::Size());
// Debug mode OSR for Baseline uses a "continuation fixer" and stashes the
// actual return address in an auxiliary structure.
if (BaselineDebugModeOSRInfo* info = bl->getDebugModeOSRInfo()) {
returnAddressToFp_ = info->resumeAddr;
return;
}
// Resuming a generator via .throw() pushes a bogus return address onto
// the stack. We have the actual jsbytecode* stashed on the frame itself;
// translate that into the Baseline code address.
if (jsbytecode* override = bl->maybeOverridePc()) {
JSScript* script = bl->script();
returnAddressToFp_ = script->baselineScript()->nativeCodeForPC(script, override);
return;
}
}
void
JSJitProfilingFrameIterator::operator++()
{
JitFrameLayout* frame = framePtr();
moveToNextFrame(frame);
}
void
JSJitProfilingFrameIterator::moveToWasmFrame(CommonFrameLayout* frame)
{
// No previous js jit frame, this is a transition frame, used to
// pass a wasm iterator the correct value of FP.
returnAddressToFp_ = nullptr;
fp_ = GetPreviousRawFrame<uint8_t*>(frame);
type_ = JitFrame_WasmToJSJit;
MOZ_ASSERT(!done());
}
void
JSJitProfilingFrameIterator::moveToCppEntryFrame()
{
// No previous frame, set to nullptr to indicate that
// JSJitProfilingFrameIterator is done().
returnAddressToFp_ = nullptr;
fp_ = nullptr;
type_ = JitFrame_CppToJSJit;
}
void
JSJitProfilingFrameIterator::moveToNextFrame(CommonFrameLayout* frame)
{
/*
* fp_ points to a Baseline or Ion frame. The possible call-stacks
* patterns occurring between this frame and a previous Ion or Baseline
* frame are as follows:
*
* <Baseline-Or-Ion>
* ^
* |
* ^--- Ion
* |
* ^--- Baseline Stub <---- Baseline
* |
* ^--- WasmToJSJit <---- (other wasm frames, not handled by this iterator)
* |
* ^--- Argument Rectifier
* | ^
* | |
* | ^--- Ion
* | |
* | ^--- Baseline Stub <---- Baseline
* | |
* | ^--- WasmToJSJit <--- (other wasm frames)
* | |
* | ^--- CppToJSJit
* |
* ^--- Entry Frame (From C++)
* Exit Frame (From previous JitActivation)
* ^
* |
* ^--- Ion
* |
* ^--- Baseline
* |
* ^--- Baseline Stub <---- Baseline
*/
FrameType prevType = frame->prevType();
if (prevType == JitFrame_IonJS) {
returnAddressToFp_ = frame->returnAddress();
fp_ = GetPreviousRawFrame<uint8_t*>(frame);
type_ = JitFrame_IonJS;
return;
}
if (prevType == JitFrame_BaselineJS) {
returnAddressToFp_ = frame->returnAddress();
fp_ = GetPreviousRawFrame<uint8_t*>(frame);
type_ = JitFrame_BaselineJS;
fixBaselineReturnAddress();
return;
}
if (prevType == JitFrame_BaselineStub) {
BaselineStubFrameLayout* stubFrame = GetPreviousRawFrame<BaselineStubFrameLayout*>(frame);
MOZ_ASSERT(stubFrame->prevType() == JitFrame_BaselineJS);
returnAddressToFp_ = stubFrame->returnAddress();
fp_ = ((uint8_t*) stubFrame->reverseSavedFramePtr())
+ jit::BaselineFrame::FramePointerOffset;
type_ = JitFrame_BaselineJS;
return;
}
if (prevType == JitFrame_Rectifier) {
RectifierFrameLayout* rectFrame = GetPreviousRawFrame<RectifierFrameLayout*>(frame);
FrameType rectPrevType = rectFrame->prevType();
if (rectPrevType == JitFrame_IonJS) {
returnAddressToFp_ = rectFrame->returnAddress();
fp_ = GetPreviousRawFrame<uint8_t*>(rectFrame);
type_ = JitFrame_IonJS;
return;
}
if (rectPrevType == JitFrame_BaselineStub) {
BaselineStubFrameLayout* stubFrame =
GetPreviousRawFrame<BaselineStubFrameLayout*>(rectFrame);
returnAddressToFp_ = stubFrame->returnAddress();
fp_ = ((uint8_t*) stubFrame->reverseSavedFramePtr())
+ jit::BaselineFrame::FramePointerOffset;
type_ = JitFrame_BaselineJS;
return;
}
if (rectPrevType == JitFrame_WasmToJSJit) {
moveToWasmFrame(rectFrame);
return;
}
if (rectPrevType == JitFrame_CppToJSJit) {
moveToCppEntryFrame();
return;
}
MOZ_CRASH("Bad frame type prior to rectifier frame.");
}
if (prevType == JitFrame_IonICCall) {
IonICCallFrameLayout* callFrame =
GetPreviousRawFrame<IonICCallFrameLayout*>(frame);
MOZ_ASSERT(callFrame->prevType() == JitFrame_IonJS);
returnAddressToFp_ = callFrame->returnAddress();
fp_ = GetPreviousRawFrame<uint8_t*>(callFrame);
type_ = JitFrame_IonJS;
return;
}
if (prevType == JitFrame_WasmToJSJit) {
moveToWasmFrame(frame);
return;
}
if (prevType == JitFrame_CppToJSJit) {
moveToCppEntryFrame();
return;
}
MOZ_CRASH("Bad frame type.");
}