blob: a65d9349dd011d97b2807a874b256cbb3ff8b2d0 [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 */
#ifndef jit_JitFrameIterator_h
#define jit_JitFrameIterator_h
#include "jsfun.h"
#include "jsscript.h"
#include "jstypes.h"
#include "jit/IonCode.h"
#include "jit/Snapshots.h"
#include "js/ProfilingFrameIterator.h"
namespace js {
class ActivationIterator;
} // namespace js
namespace js {
namespace jit {
typedef void * CalleeToken;
enum FrameType
// A JS frame is analagous to a js::InterpreterFrame, representing one scripted
// functon activation. IonJS frames are used by the optimizing compiler.
// JS frame used by the baseline JIT.
// Frame pushed for JIT stubs that make non-tail calls, so that the
// return address -> ICEntry mapping works.
// The entry frame is the initial prologue block transitioning from the VM
// into the Ion world.
// A rectifier frame sits in between two JS frames, adapting argc != nargs
// mismatches in calls.
// Ion IC calling a scripted getter/setter.
// An unwound JS frame is a JS frame signalling that its callee frame has been
// turned into an exit frame (see EnsureExitFrame). Used by Ion bailouts and
// Baseline exception unwinding.
// An exit frame is necessary for transitioning from a JS frame into C++.
// From within C++, an exit frame is always the last frame in any
// JitActivation.
// A bailout frame is a special IonJS jit frame after a bailout, and before
// the reconstruction of the BaselineJS frame. From within C++, a bailout
// frame is always the last frame in a JitActivation iff the bailout frame
// information is recorded on the JitActivation.
// A lazy link frame is a special exit frame where a IonJS frame is reused
// for linking the newly compiled code. A special frame is needed to
// work-around the fact that we can make stack patterns which are similar to
// unwound frames. As opposed to unwound frames, we still have to mark all
// the arguments of the original IonJS frame.
enum ReadFrameArgsBehavior {
// Only read formals (i.e. [0 ... callee()->nargs]
// Only read overflown args (i.e. [callee()->nargs ... numActuals()]
// Read all args (i.e. [0 ... numActuals()])
class CommonFrameLayout;
class JitFrameLayout;
class ExitFrameLayout;
class BaselineFrame;
class JitActivation;
// Iterate over the JIT stack to assert that all invariants are respected.
// - Check that all entry frames are aligned on JitStackAlignment.
// - Check that all rectifier frames keep the JitStackAlignment.
void AssertJitStackInvariants(JSContext* cx);
class JitFrameIterator
uint8_t* current_;
FrameType type_;
uint8_t* returnAddressToFp_;
size_t frameSize_;
mutable const SafepointIndex* cachedSafepointIndex_;
const JitActivation* activation_;
void dumpBaseline() const;
explicit JitFrameIterator();
explicit JitFrameIterator(JSContext* cx);
explicit JitFrameIterator(const ActivationIterator& activations);
// Current frame information.
FrameType type() const {
return type_;
uint8_t* fp() const {
return current_;
const JitActivation* activation() const {
return activation_;
CommonFrameLayout* current() const {
return (CommonFrameLayout*)current_;
inline uint8_t* returnAddress() const;
// Return the pointer of the JitFrame, the iterator is assumed to be settled
// on a scripted frame.
JitFrameLayout* jsFrame() const;
// Returns true iff this exit frame was created using EnsureExitFrame.
inline bool isFakeExitFrame() const;
inline ExitFrameLayout* exitFrame() const;
// Returns whether the JS frame has been invalidated and, if so,
// places the invalidated Ion script in |ionScript|.
bool checkInvalidation(IonScript** ionScript) const;
bool checkInvalidation() const;
bool isExitFrame() const {
return type_ == JitFrame_Exit || type_ == JitFrame_LazyLink;
bool isScripted() const {
return type_ == JitFrame_BaselineJS || type_ == JitFrame_IonJS || type_ == JitFrame_Bailout;
bool isBaselineJS() const {
return type_ == JitFrame_BaselineJS;
bool isIonScripted() const {
return type_ == JitFrame_IonJS || type_ == JitFrame_Bailout;
bool isIonJS() const {
return type_ == JitFrame_IonJS;
bool isIonStub() const {
return type_ == JitFrame_IonStub;
bool isIonStubMaybeUnwound() const {
return type_ == JitFrame_IonStub || type_ == JitFrame_Unwound_IonStub;
bool isIonAccessorICMaybeUnwound() const {
return type_ == JitFrame_IonAccessorIC || type_ == JitFrame_Unwound_IonAccessorIC;
bool isBailoutJS() const {
return type_ == JitFrame_Bailout;
bool isBaselineStub() const {
return type_ == JitFrame_BaselineStub;
bool isBaselineStubMaybeUnwound() const {
return type_ == JitFrame_BaselineStub || type_ == JitFrame_Unwound_BaselineStub;
bool isRectifierMaybeUnwound() const {
return type_ == JitFrame_Rectifier || type_ == JitFrame_Unwound_Rectifier;
bool isBareExit() const;
template <typename T> bool isExitFrameLayout() const;
bool isEntry() const {
return type_ == JitFrame_Entry;
bool isFunctionFrame() const;
bool isConstructing() const;
void* calleeToken() const;
JSFunction* callee() const;
JSFunction* maybeCallee() const;
unsigned numActualArgs() const;
JSScript* script() const;
void baselineScriptAndPc(JSScript** scriptRes, jsbytecode** pcRes) const;
Value* actualArgs() const;
// Returns the return address of the frame above this one (that is, the
// return address that returns back to the current frame).
uint8_t* returnAddressToFp() const {
return returnAddressToFp_;
// Previous frame information extracted from the current frame.
inline size_t prevFrameLocalSize() const;
inline FrameType prevType() const;
uint8_t* prevFp() const;
// Returns the stack space used by the current frame, in bytes. This does
// not include the size of its fixed header.
size_t frameSize() const {
return frameSize_;
// Functions used to iterate on frames. When prevType is JitFrame_Entry,
// the current frame is the last frame.
inline bool done() const {
return type_ == JitFrame_Entry;
JitFrameIterator& operator++();
// Returns the IonScript associated with this JS frame.
IonScript* ionScript() const;
// Returns the IonScript associated with this JS frame; the frame must
// not be invalidated.
IonScript* ionScriptFromCalleeToken() const;
// Returns the Safepoint associated with this JS frame. Incurs a lookup
// overhead.
const SafepointIndex* safepoint() const;
// Returns the OSI index associated with this JS frame. Incurs a lookup
// overhead.
const OsiIndex* osiIndex() const;
// Returns the Snapshot offset associated with this JS frame. Incurs a
// lookup overhead.
SnapshotOffset snapshotOffset() const;
uintptr_t* spillBase() const;
MachineState machineState() const;
template <class Op>
void unaliasedForEachActual(Op op, ReadFrameArgsBehavior behavior) const {
unsigned nactual = numActualArgs();
unsigned start, end;
switch (behavior) {
case ReadFrame_Formals:
start = 0;
end = callee()->nargs();
case ReadFrame_Overflown:
start = callee()->nargs();
end = nactual;
case ReadFrame_Actuals:
start = 0;
end = nactual;
Value* argv = actualArgs();
for (unsigned i = start; i < end; i++)
void dump() const;
inline BaselineFrame* baselineFrame() const;
#ifdef DEBUG
bool verifyReturnAddressUsingNativeToBytecodeMap();
inline bool verifyReturnAddressUsingNativeToBytecodeMap() { return true; }
class JitcodeGlobalTable;
class JitProfilingFrameIterator
uint8_t* fp_;
FrameType type_;
void* returnAddressToFp_;
inline JitFrameLayout* framePtr();
inline JSScript* frameScript();
bool tryInitWithPC(void* pc);
bool tryInitWithTable(JitcodeGlobalTable* table, void* pc, JSRuntime* rt,
bool forLastCallSite);
void fixBaselineDebugModeOSRReturnAddress();
JitProfilingFrameIterator(JSRuntime* rt,
const JS::ProfilingFrameIterator::RegisterState& state);
explicit JitProfilingFrameIterator(void* exitFrame);
void operator++();
bool done() const { return fp_ == nullptr; }
void* fp() const { MOZ_ASSERT(!done()); return fp_; }
void* stackAddress() const { return fp(); }
FrameType frameType() const { MOZ_ASSERT(!done()); return type_; }
void* returnAddressToFp() const { MOZ_ASSERT(!done()); return returnAddressToFp_; }
class RInstructionResults
// Vector of results of recover instructions.
typedef mozilla::Vector<RelocatableValue, 1, SystemAllocPolicy> Values;
mozilla::UniquePtr<Values, JS::DeletePolicy<Values> > results_;
// The frame pointer is used as a key to check if the current frame already
// bailed out.
JitFrameLayout* fp_;
// Record if we tried and succeed at allocating and filling the vector of
// recover instruction results, if needed. This flag is needed in order to
// avoid evaluating the recover instruction twice.
bool initialized_;
explicit RInstructionResults(JitFrameLayout* fp);
RInstructionResults(RInstructionResults&& src);
RInstructionResults& operator=(RInstructionResults&& rhs);
bool init(JSContext* cx, uint32_t numResults);
bool isInitialized() const;
#ifdef DEBUG
size_t length() const;
JitFrameLayout* frame() const;
RelocatableValue& operator[](size_t index);
void trace(JSTracer* trc);
struct MaybeReadFallback
enum NoGCValue {
enum FallbackConsequence {
JSContext* maybeCx;
JitActivation* activation;
const JitFrameIterator* frame;
const NoGCValue unreadablePlaceholder_;
const FallbackConsequence consequence;
explicit MaybeReadFallback(const Value& placeholder = UndefinedValue())
: maybeCx(nullptr),
MaybeReadFallback(JSContext* cx, JitActivation* activation, const JitFrameIterator* frame,
FallbackConsequence consequence = Fallback_Invalidate)
: maybeCx(cx),
bool canRecoverResults() { return maybeCx; }
Value unreadablePlaceholder() const {
if (unreadablePlaceholder_ == NoGC_MagicOptimizedOut)
return MagicValue(JS_OPTIMIZED_OUT);
return UndefinedValue();
NoGCValue noGCPlaceholder(Value v) const {
if (v.isMagic(JS_OPTIMIZED_OUT))
return NoGC_MagicOptimizedOut;
return NoGC_UndefinedValue;
class RResumePoint;
class RSimdBox;
// Reads frame information in snapshot-encoding order (that is, outermost frame
// to innermost frame).
class SnapshotIterator
SnapshotReader snapshot_;
RecoverReader recover_;
JitFrameLayout* fp_;
const MachineState* machine_;
IonScript* ionScript_;
RInstructionResults* instructionResults_;
enum ReadMethod {
// Read the normal value.
RM_Normal = 1 << 0,
// Read the default value, or the normal value if there is no default.
RM_AlwaysDefault = 1 << 1,
// Try to read the normal value if it is readable, otherwise default to
// the Default value.
RM_NormalOrDefault = RM_Normal | RM_AlwaysDefault,
// Read a spilled register from the machine state.
bool hasRegister(Register reg) const {
return machine_->has(reg);
uintptr_t fromRegister(Register reg) const {
return machine_->read(reg);
bool hasRegister(FloatRegister reg) const {
return machine_->has(reg);
double fromRegister(FloatRegister reg) const {
return machine_->read(reg);
// Read an uintptr_t from the stack.
bool hasStack(int32_t offset) const {
return true;
uintptr_t fromStack(int32_t offset) const;
bool hasInstructionResult(uint32_t index) const {
return instructionResults_;
bool hasInstructionResults() const {
return instructionResults_;
Value fromInstructionResult(uint32_t index) const;
Value allocationValue(const RValueAllocation& a, ReadMethod rm = RM_Normal);
bool allocationReadable(const RValueAllocation& a, ReadMethod rm = RM_Normal);
void writeAllocationValuePayload(const RValueAllocation& a, Value v);
void warnUnreadableAllocation();
friend class RSimdBox;
const FloatRegisters::RegisterContent* floatAllocationPointer(const RValueAllocation& a) const;
// Handle iterating over RValueAllocations of the snapshots.
inline RValueAllocation readAllocation() {
return snapshot_.readAllocation();
Value skip() {
return UndefinedValue();
const RResumePoint* resumePoint() const;
const RInstruction* instruction() const {
return recover_.instruction();
uint32_t numAllocations() const;
inline bool moreAllocations() const {
return snapshot_.numAllocationsRead() < numAllocations();
int32_t readOuterNumActualArgs() const;
// Used by recover instruction to store the value back into the instruction
// results array.
void storeInstructionResult(Value v);
// Exhibits frame properties contained in the snapshot.
uint32_t pcOffset() const;
inline bool resumeAfter() const {
// Inline frames are inlined on calls, which are considered as being
// resumed on the Call as baseline will push the pc once we return from
// the call.
if (moreFrames())
return false;
return recover_.resumeAfter();
inline BailoutKind bailoutKind() const {
return snapshot_.bailoutKind();
// Read the next instruction available and get ready to either skip it or
// evaluate it.
inline void nextInstruction() {
MOZ_ASSERT(snapshot_.numAllocationsRead() == numAllocations());
// Skip an Instruction by walking to the next instruction and by skipping
// all the allocations corresponding to this instruction.
void skipInstruction();
inline bool moreInstructions() const {
return recover_.moreInstructions();
// Register a vector used for storing the results of the evaluation of
// recover instructions. This vector should be registered before the
// beginning of the iteration. This function is in charge of allocating
// enough space for all instructions results, and return false iff it fails.
bool initInstructionResults(MaybeReadFallback& fallback);
// This function is used internally for computing the result of the recover
// instructions.
bool computeInstructionResults(JSContext* cx, RInstructionResults* results) const;
// Handle iterating over frames of the snapshots.
void nextFrame();
void settleOnFrame();
inline bool moreFrames() const {
// The last instruction is recovering the innermost frame, so as long as
// there is more instruction there is necesseray more frames.
return moreInstructions();
// Connect all informations about the current script in order to recover the
// content of baseline frames.
SnapshotIterator(const JitFrameIterator& iter, const MachineState* machineState);
Value read() {
return allocationValue(readAllocation());
// Read the |Normal| value unless it is not available and that the snapshot
// provides a |Default| value. This is useful to avoid invalidations of the
// frame while we are only interested in a few properties which are provided
// by the |Default| value.
Value readWithDefault(RValueAllocation* alloc) {
*alloc = RValueAllocation();
RValueAllocation a = readAllocation();
if (allocationReadable(a))
return allocationValue(a);
*alloc = a;
return allocationValue(a, RM_AlwaysDefault);
Value maybeRead(const RValueAllocation& a, MaybeReadFallback& fallback);
Value maybeRead(MaybeReadFallback& fallback) {
RValueAllocation a = readAllocation();
return maybeRead(a, fallback);
void traceAllocation(JSTracer* trc);
template <class Op>
void readFunctionFrameArgs(Op& op, ArgumentsObject** argsObj, Value* thisv,
unsigned start, unsigned end, JSScript* script,
MaybeReadFallback& fallback)
// Assumes that the common frame arguments have already been read.
if (script->argumentsHasVarBinding()) {
if (argsObj) {
Value v = read();
if (v.isObject())
*argsObj = &v.toObject().as<ArgumentsObject>();
} else {
if (thisv)
*thisv = maybeRead(fallback);
unsigned i = 0;
if (end < start)
i = start;
for (; i < start; i++)
for (; i < end; i++) {
// We are not always able to read values from the snapshots, some values
// such as non-gc things may still be live in registers and cause an
// error while reading the machine state.
Value v = maybeRead(fallback);
// Iterate over all the allocations and return only the value of the
// allocation located at one index.
Value maybeReadAllocByIndex(size_t index);
void spewBailingFrom() const {
// Reads frame information in callstack order (that is, innermost frame to
// outermost frame).
class InlineFrameIterator
const JitFrameIterator* frame_;
SnapshotIterator start_;
SnapshotIterator si_;
uint32_t framesRead_;
// When the inline-frame-iterator is created, this variable is defined to
// UINT32_MAX. Then the first iteration of findNextFrame, which settle on
// the innermost frame, is used to update this counter to the number of
// frames contained in the recover buffer.
uint32_t frameCount_;
// The |calleeTemplate_| fields contains either the JSFunction or the
// template from which it is supposed to be cloned. The |calleeRVA_| is an
// Invalid value allocation, if the |calleeTemplate_| field is the effective
// JSFunction, and not its template. On the other hand, any other value
// allocation implies that the |calleeTemplate_| is the template JSFunction
// from which the effective one would be derived and cached by the Recover
// instruction result.
RootedFunction calleeTemplate_;
RValueAllocation calleeRVA_;
RootedScript script_;
jsbytecode* pc_;
uint32_t numActualArgs_;
// Register state, used by all snapshot iterators.
MachineState machine_;
struct Nop {
void operator()(const Value& v) { }
void findNextFrame();
JSObject* computeScopeChain(Value scopeChainValue, MaybeReadFallback& fallback,
bool* hasCallObj = nullptr) const;
InlineFrameIterator(JSContext* cx, const JitFrameIterator* iter);
InlineFrameIterator(JSRuntime* rt, const JitFrameIterator* iter);
InlineFrameIterator(JSContext* cx, const InlineFrameIterator* iter);
bool more() const {
return frame_ && framesRead_ < frameCount_;
// Due to optimizations, we are not always capable of reading the callee of
// inlined frames without invalidating the IonCode. This function might
// return either the effective callee of the JSFunction which might be used
// to create it.
// As such, the |calleeTemplate()| can be used to read most of the metadata
// which are conserved across clones.
JSFunction* calleeTemplate() const {
return calleeTemplate_;
JSFunction* maybeCalleeTemplate() const {
return calleeTemplate_;
JSFunction* callee(MaybeReadFallback& fallback) const;
unsigned numActualArgs() const {
// The number of actual arguments of inline frames is recovered by the
// iteration process. It is recovered from the bytecode because this
// property still hold since the for inlined frames. This property does not
// hold for the parent frame because it can have optimize a call to
// js_fun_call or js_fun_apply.
if (more())
return numActualArgs_;
return frame_->numActualArgs();
template <class ArgOp, class LocalOp>
void readFrameArgsAndLocals(JSContext* cx, ArgOp& argOp, LocalOp& localOp,
JSObject** scopeChain, bool* hasCallObj,
Value* rval, ArgumentsObject** argsObj, Value* thisv,
ReadFrameArgsBehavior behavior,
MaybeReadFallback& fallback) const
SnapshotIterator s(si_);
// Read the scope chain.
if (scopeChain) {
Value scopeChainValue = s.maybeRead(fallback);
*scopeChain = computeScopeChain(scopeChainValue, fallback, hasCallObj);
} else {
// Read return value.
if (rval)
*rval =;
// Read arguments, which only function frames have.
if (isFunctionFrame()) {
unsigned nactual = numActualArgs();
unsigned nformal = calleeTemplate()->nargs();
// Get the non overflown arguments, which are taken from the inlined
// frame, because it will have the updated value when JSOP_SETARG is
// done.
if (behavior != ReadFrame_Overflown)
s.readFunctionFrameArgs(argOp, argsObj, thisv, 0, nformal, script(), fallback);
if (behavior != ReadFrame_Formals) {
if (more()) {
// There is still a parent frame of this inlined frame. All
// arguments (also the overflown) are the last pushed values
// in the parent frame. To get the overflown arguments, we
// need to take them from there.
// The overflown arguments are not available in current frame.
// They are the last pushed arguments in the parent frame of
// this inlined frame.
InlineFrameIterator it(cx, this);
unsigned argsObjAdj = it.script()->argumentsHasVarBinding() ? 1 : 0;
bool hasNewTarget = isConstructing();
SnapshotIterator parent_s(it.snapshotIterator());
// Skip over all slots until we get to the last slots
// (= arguments slots of callee) the +3 is for [this], [returnvalue],
// [scopechain], and maybe +1 for [argsObj]
MOZ_ASSERT(parent_s.numAllocations() >= nactual + 3 + argsObjAdj + hasNewTarget);
unsigned skip = parent_s.numAllocations() - nactual - 3 - argsObjAdj - hasNewTarget;
for (unsigned j = 0; j < skip; j++)
// Get the overflown arguments
MaybeReadFallback unusedFallback;
parent_s.skip(); // scope chain
parent_s.skip(); // return value
parent_s.readFunctionFrameArgs(argOp, nullptr, nullptr,
nformal, nactual + isConstructing(), it.script(),
} else {
// There is no parent frame to this inlined frame, we can read
// from the frame's Value vector directly.
Value* argv = frame_->actualArgs();
for (unsigned i = nformal; i < nactual + isConstructing(); i++)
// At this point we've read all the formals in s, and can read the
// locals.
for (unsigned i = 0; i < script()->nfixed(); i++)
template <class Op>
void unaliasedForEachActual(JSContext* cx, Op op,
ReadFrameArgsBehavior behavior,
MaybeReadFallback& fallback) const
Nop nop;
readFrameArgsAndLocals(cx, op, nop, nullptr, nullptr, nullptr,
nullptr, nullptr, behavior, fallback);
JSScript* script() const {
return script_;
jsbytecode* pc() const {
return pc_;
SnapshotIterator snapshotIterator() const {
return si_;
bool isFunctionFrame() const;
bool isConstructing() const;
JSObject* scopeChain(MaybeReadFallback& fallback) const {
SnapshotIterator s(si_);
// scopeChain
Value v = s.maybeRead(fallback);
return computeScopeChain(v, fallback);
Value thisArgument(MaybeReadFallback& fallback) const {
SnapshotIterator s(si_);
// scopeChain
// return value
// Arguments object.
if (script()->argumentsHasVarBinding())
return s.maybeRead(fallback);
InlineFrameIterator& operator++() {
return *this;
void dump() const;
void resetOn(const JitFrameIterator* iter);
const JitFrameIterator& frame() const {
return *frame_;
// Inline frame number, 0 for the outermost (non-inlined) frame.
size_t frameNo() const {
return frameCount() - framesRead_;
size_t frameCount() const {
MOZ_ASSERT(frameCount_ != UINT32_MAX);
return frameCount_;
InlineFrameIterator() = delete;
InlineFrameIterator(const InlineFrameIterator& iter) = delete;
} // namespace jit
} // namespace js
#endif /* jit_JitFrameIterator_h */