579 lines
16 KiB
C++
579 lines
16 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
* 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/. */
|
|
|
|
#ifndef jit_Snapshot_h
|
|
#define jit_Snapshot_h
|
|
|
|
#include "mozilla/Alignment.h"
|
|
|
|
#include "jsalloc.h"
|
|
#include "jsbytecode.h"
|
|
|
|
#include "jit/CompactBuffer.h"
|
|
#include "jit/IonTypes.h"
|
|
#include "jit/Registers.h"
|
|
|
|
#include "js/HashTable.h"
|
|
|
|
namespace js {
|
|
class GenericPrinter;
|
|
|
|
namespace jit {
|
|
|
|
class RValueAllocation;
|
|
|
|
// A Recover Value Allocation mirror what is known at compiled time as being the
|
|
// MIRType and the LAllocation. This is read out of the snapshot to recover the
|
|
// value which would be there if this frame was an interpreter frame instead of
|
|
// an Ion frame.
|
|
//
|
|
// It is used with the SnapshotIterator to recover a Value from the stack,
|
|
// spilled registers or the list of constant of the compiled script.
|
|
//
|
|
// Unit tests are located in jsapi-tests/testJitRValueAlloc.cpp.
|
|
class RValueAllocation
|
|
{
|
|
public:
|
|
|
|
// See RValueAllocation encoding in Snapshots.cpp
|
|
enum Mode
|
|
{
|
|
CONSTANT = 0x00,
|
|
CST_UNDEFINED = 0x01,
|
|
CST_NULL = 0x02,
|
|
DOUBLE_REG = 0x03,
|
|
ANY_FLOAT_REG = 0x04,
|
|
ANY_FLOAT_STACK = 0x05,
|
|
#if defined(JS_NUNBOX32)
|
|
UNTYPED_REG_REG = 0x06,
|
|
UNTYPED_REG_STACK = 0x07,
|
|
UNTYPED_STACK_REG = 0x08,
|
|
UNTYPED_STACK_STACK = 0x09,
|
|
#elif defined(JS_PUNBOX64)
|
|
UNTYPED_REG = 0x06,
|
|
UNTYPED_STACK = 0x07,
|
|
#endif
|
|
|
|
// Recover instructions.
|
|
RECOVER_INSTRUCTION = 0x0a,
|
|
RI_WITH_DEFAULT_CST = 0x0b,
|
|
|
|
// The JSValueType is packed in the Mode.
|
|
TYPED_REG_MIN = 0x10,
|
|
TYPED_REG_MAX = 0x1f,
|
|
TYPED_REG = TYPED_REG_MIN,
|
|
|
|
// The JSValueType is packed in the Mode.
|
|
TYPED_STACK_MIN = 0x20,
|
|
TYPED_STACK_MAX = 0x2f,
|
|
TYPED_STACK = TYPED_STACK_MIN,
|
|
|
|
// This mask can be used with any other valid mode. When this flag is
|
|
// set on the mode, this inform the snapshot iterator that even if the
|
|
// allocation is readable, the content of if might be incomplete unless
|
|
// all side-effects are executed.
|
|
RECOVER_SIDE_EFFECT_MASK = 0x80,
|
|
|
|
// This mask represents the set of bits which can be used to encode a
|
|
// value in a snapshot. The mode is used to determine how to interpret
|
|
// the union of values and how to pack the value in memory.
|
|
MODE_BITS_MASK = 0x17f,
|
|
|
|
INVALID = 0x100,
|
|
};
|
|
|
|
enum { PACKED_TAG_MASK = 0x0f };
|
|
|
|
// See Payload encoding in Snapshots.cpp
|
|
enum PayloadType {
|
|
PAYLOAD_NONE,
|
|
PAYLOAD_INDEX,
|
|
PAYLOAD_STACK_OFFSET,
|
|
PAYLOAD_GPR,
|
|
PAYLOAD_FPU,
|
|
PAYLOAD_PACKED_TAG
|
|
};
|
|
|
|
struct Layout {
|
|
PayloadType type1;
|
|
PayloadType type2;
|
|
const char* name;
|
|
};
|
|
|
|
private:
|
|
Mode mode_;
|
|
|
|
// Additional information to recover the content of the allocation.
|
|
struct FloatRegisterBits {
|
|
uint32_t data;
|
|
bool operator == (const FloatRegisterBits& other) const {
|
|
return data == other.data;
|
|
}
|
|
uint32_t code() const {
|
|
return data;
|
|
}
|
|
const char* name() const {
|
|
FloatRegister tmp = FloatRegister::FromCode(data);
|
|
return tmp.name();
|
|
}
|
|
};
|
|
|
|
union Payload {
|
|
uint32_t index;
|
|
int32_t stackOffset;
|
|
Register gpr;
|
|
FloatRegisterBits fpu;
|
|
JSValueType type;
|
|
};
|
|
|
|
Payload arg1_;
|
|
Payload arg2_;
|
|
|
|
static Payload payloadOfIndex(uint32_t index) {
|
|
Payload p;
|
|
p.index = index;
|
|
return p;
|
|
}
|
|
static Payload payloadOfStackOffset(int32_t offset) {
|
|
Payload p;
|
|
p.stackOffset = offset;
|
|
return p;
|
|
}
|
|
static Payload payloadOfRegister(Register reg) {
|
|
Payload p;
|
|
p.gpr = reg;
|
|
return p;
|
|
}
|
|
static Payload payloadOfFloatRegister(FloatRegister reg) {
|
|
Payload p;
|
|
FloatRegisterBits b;
|
|
b.data = reg.code();
|
|
p.fpu = b;
|
|
return p;
|
|
}
|
|
static Payload payloadOfValueType(JSValueType type) {
|
|
Payload p;
|
|
p.type = type;
|
|
return p;
|
|
}
|
|
|
|
static const Layout& layoutFromMode(Mode mode);
|
|
|
|
static void readPayload(CompactBufferReader& reader, PayloadType t,
|
|
uint8_t* mode, Payload* p);
|
|
static void writePayload(CompactBufferWriter& writer, PayloadType t,
|
|
Payload p);
|
|
static void writePadding(CompactBufferWriter& writer);
|
|
static void dumpPayload(GenericPrinter& out, PayloadType t, Payload p);
|
|
static bool equalPayloads(PayloadType t, Payload lhs, Payload rhs);
|
|
|
|
RValueAllocation(Mode mode, Payload a1, Payload a2)
|
|
: mode_(mode),
|
|
arg1_(a1),
|
|
arg2_(a2)
|
|
{
|
|
}
|
|
|
|
RValueAllocation(Mode mode, Payload a1)
|
|
: mode_(mode),
|
|
arg1_(a1)
|
|
{
|
|
}
|
|
|
|
explicit RValueAllocation(Mode mode)
|
|
: mode_(mode)
|
|
{
|
|
}
|
|
|
|
public:
|
|
RValueAllocation()
|
|
: mode_(INVALID)
|
|
{ }
|
|
|
|
// DOUBLE_REG
|
|
static RValueAllocation Double(FloatRegister reg) {
|
|
return RValueAllocation(DOUBLE_REG, payloadOfFloatRegister(reg));
|
|
}
|
|
|
|
// ANY_FLOAT_REG or ANY_FLOAT_STACK
|
|
static RValueAllocation AnyFloat(FloatRegister reg) {
|
|
return RValueAllocation(ANY_FLOAT_REG, payloadOfFloatRegister(reg));
|
|
}
|
|
static RValueAllocation AnyFloat(int32_t offset) {
|
|
return RValueAllocation(ANY_FLOAT_STACK, payloadOfStackOffset(offset));
|
|
}
|
|
|
|
// TYPED_REG or TYPED_STACK
|
|
static RValueAllocation Typed(JSValueType type, Register reg) {
|
|
MOZ_ASSERT(type != JSVAL_TYPE_DOUBLE &&
|
|
type != JSVAL_TYPE_MAGIC &&
|
|
type != JSVAL_TYPE_NULL &&
|
|
type != JSVAL_TYPE_UNDEFINED);
|
|
return RValueAllocation(TYPED_REG, payloadOfValueType(type),
|
|
payloadOfRegister(reg));
|
|
}
|
|
static RValueAllocation Typed(JSValueType type, int32_t offset) {
|
|
MOZ_ASSERT(type != JSVAL_TYPE_MAGIC &&
|
|
type != JSVAL_TYPE_NULL &&
|
|
type != JSVAL_TYPE_UNDEFINED);
|
|
return RValueAllocation(TYPED_STACK, payloadOfValueType(type),
|
|
payloadOfStackOffset(offset));
|
|
}
|
|
|
|
// UNTYPED
|
|
#if defined(JS_NUNBOX32)
|
|
static RValueAllocation Untyped(Register type, Register payload) {
|
|
return RValueAllocation(UNTYPED_REG_REG,
|
|
payloadOfRegister(type),
|
|
payloadOfRegister(payload));
|
|
}
|
|
|
|
static RValueAllocation Untyped(Register type, int32_t payloadStackOffset) {
|
|
return RValueAllocation(UNTYPED_REG_STACK,
|
|
payloadOfRegister(type),
|
|
payloadOfStackOffset(payloadStackOffset));
|
|
}
|
|
|
|
static RValueAllocation Untyped(int32_t typeStackOffset, Register payload) {
|
|
return RValueAllocation(UNTYPED_STACK_REG,
|
|
payloadOfStackOffset(typeStackOffset),
|
|
payloadOfRegister(payload));
|
|
}
|
|
|
|
static RValueAllocation Untyped(int32_t typeStackOffset, int32_t payloadStackOffset) {
|
|
return RValueAllocation(UNTYPED_STACK_STACK,
|
|
payloadOfStackOffset(typeStackOffset),
|
|
payloadOfStackOffset(payloadStackOffset));
|
|
}
|
|
|
|
#elif defined(JS_PUNBOX64)
|
|
static RValueAllocation Untyped(Register reg) {
|
|
return RValueAllocation(UNTYPED_REG, payloadOfRegister(reg));
|
|
}
|
|
|
|
static RValueAllocation Untyped(int32_t stackOffset) {
|
|
return RValueAllocation(UNTYPED_STACK, payloadOfStackOffset(stackOffset));
|
|
}
|
|
#endif
|
|
|
|
// common constants.
|
|
static RValueAllocation Undefined() {
|
|
return RValueAllocation(CST_UNDEFINED);
|
|
}
|
|
static RValueAllocation Null() {
|
|
return RValueAllocation(CST_NULL);
|
|
}
|
|
|
|
// CONSTANT's index
|
|
static RValueAllocation ConstantPool(uint32_t index) {
|
|
return RValueAllocation(CONSTANT, payloadOfIndex(index));
|
|
}
|
|
|
|
// Recover instruction's index
|
|
static RValueAllocation RecoverInstruction(uint32_t index) {
|
|
return RValueAllocation(RECOVER_INSTRUCTION, payloadOfIndex(index));
|
|
}
|
|
static RValueAllocation RecoverInstruction(uint32_t riIndex, uint32_t cstIndex) {
|
|
return RValueAllocation(RI_WITH_DEFAULT_CST,
|
|
payloadOfIndex(riIndex),
|
|
payloadOfIndex(cstIndex));
|
|
}
|
|
|
|
void setNeedSideEffect() {
|
|
MOZ_ASSERT(!needSideEffect() && mode_ != INVALID);
|
|
mode_ = Mode(mode_ | RECOVER_SIDE_EFFECT_MASK);
|
|
}
|
|
|
|
void writeHeader(CompactBufferWriter& writer, JSValueType type, uint32_t regCode) const;
|
|
public:
|
|
static RValueAllocation read(CompactBufferReader& reader);
|
|
void write(CompactBufferWriter& writer) const;
|
|
|
|
public:
|
|
Mode mode() const {
|
|
return Mode(mode_ & MODE_BITS_MASK);
|
|
}
|
|
bool needSideEffect() const {
|
|
return mode_ & RECOVER_SIDE_EFFECT_MASK;
|
|
}
|
|
|
|
uint32_t index() const {
|
|
MOZ_ASSERT(layoutFromMode(mode()).type1 == PAYLOAD_INDEX);
|
|
return arg1_.index;
|
|
}
|
|
int32_t stackOffset() const {
|
|
MOZ_ASSERT(layoutFromMode(mode()).type1 == PAYLOAD_STACK_OFFSET);
|
|
return arg1_.stackOffset;
|
|
}
|
|
Register reg() const {
|
|
MOZ_ASSERT(layoutFromMode(mode()).type1 == PAYLOAD_GPR);
|
|
return arg1_.gpr;
|
|
}
|
|
FloatRegister fpuReg() const {
|
|
MOZ_ASSERT(layoutFromMode(mode()).type1 == PAYLOAD_FPU);
|
|
FloatRegisterBits b = arg1_.fpu;
|
|
return FloatRegister::FromCode(b.data);
|
|
}
|
|
JSValueType knownType() const {
|
|
MOZ_ASSERT(layoutFromMode(mode()).type1 == PAYLOAD_PACKED_TAG);
|
|
return arg1_.type;
|
|
}
|
|
|
|
uint32_t index2() const {
|
|
MOZ_ASSERT(layoutFromMode(mode()).type2 == PAYLOAD_INDEX);
|
|
return arg2_.index;
|
|
}
|
|
int32_t stackOffset2() const {
|
|
MOZ_ASSERT(layoutFromMode(mode()).type2 == PAYLOAD_STACK_OFFSET);
|
|
return arg2_.stackOffset;
|
|
}
|
|
Register reg2() const {
|
|
MOZ_ASSERT(layoutFromMode(mode()).type2 == PAYLOAD_GPR);
|
|
return arg2_.gpr;
|
|
}
|
|
|
|
public:
|
|
void dump(GenericPrinter& out) const;
|
|
|
|
public:
|
|
bool operator==(const RValueAllocation& rhs) const {
|
|
if (mode_ != rhs.mode_)
|
|
return false;
|
|
|
|
const Layout& layout = layoutFromMode(mode());
|
|
return equalPayloads(layout.type1, arg1_, rhs.arg1_) &&
|
|
equalPayloads(layout.type2, arg2_, rhs.arg2_);
|
|
}
|
|
|
|
HashNumber hash() const;
|
|
|
|
struct Hasher
|
|
{
|
|
typedef RValueAllocation Key;
|
|
typedef Key Lookup;
|
|
static HashNumber hash(const Lookup& v) {
|
|
return v.hash();
|
|
}
|
|
static bool match(const Key& k, const Lookup& l) {
|
|
return k == l;
|
|
}
|
|
};
|
|
};
|
|
|
|
class RecoverWriter;
|
|
|
|
// Collects snapshots in a contiguous buffer, which is copied into IonScript
|
|
// memory after code generation.
|
|
class SnapshotWriter
|
|
{
|
|
CompactBufferWriter writer_;
|
|
CompactBufferWriter allocWriter_;
|
|
|
|
// Map RValueAllocations to an offset in the allocWriter_ buffer. This is
|
|
// useful as value allocations are repeated frequently.
|
|
typedef RValueAllocation RVA;
|
|
typedef HashMap<RVA, uint32_t, RVA::Hasher, SystemAllocPolicy> RValueAllocMap;
|
|
RValueAllocMap allocMap_;
|
|
|
|
// This is only used to assert sanity.
|
|
uint32_t allocWritten_;
|
|
|
|
// Used to report size of the snapshot in the spew messages.
|
|
SnapshotOffset lastStart_;
|
|
|
|
public:
|
|
MOZ_MUST_USE bool init();
|
|
|
|
SnapshotOffset startSnapshot(RecoverOffset recoverOffset, BailoutKind kind);
|
|
#ifdef TRACK_SNAPSHOTS
|
|
void trackSnapshot(uint32_t pcOpcode, uint32_t mirOpcode, uint32_t mirId,
|
|
uint32_t lirOpcode, uint32_t lirId);
|
|
#endif
|
|
MOZ_MUST_USE bool add(const RValueAllocation& slot);
|
|
|
|
uint32_t allocWritten() const {
|
|
return allocWritten_;
|
|
}
|
|
void endSnapshot();
|
|
|
|
bool oom() const {
|
|
return writer_.oom() || writer_.length() >= MAX_BUFFER_SIZE ||
|
|
allocWriter_.oom() || allocWriter_.length() >= MAX_BUFFER_SIZE;
|
|
}
|
|
|
|
size_t listSize() const {
|
|
return writer_.length();
|
|
}
|
|
const uint8_t* listBuffer() const {
|
|
return writer_.buffer();
|
|
}
|
|
|
|
size_t RVATableSize() const {
|
|
return allocWriter_.length();
|
|
}
|
|
const uint8_t* RVATableBuffer() const {
|
|
return allocWriter_.buffer();
|
|
}
|
|
};
|
|
|
|
class MNode;
|
|
|
|
class RecoverWriter
|
|
{
|
|
CompactBufferWriter writer_;
|
|
|
|
uint32_t instructionCount_;
|
|
uint32_t instructionsWritten_;
|
|
|
|
public:
|
|
SnapshotOffset startRecover(uint32_t instructionCount, bool resumeAfter);
|
|
|
|
void writeInstruction(const MNode* rp);
|
|
|
|
void endRecover();
|
|
|
|
size_t size() const {
|
|
return writer_.length();
|
|
}
|
|
const uint8_t* buffer() const {
|
|
return writer_.buffer();
|
|
}
|
|
|
|
bool oom() const {
|
|
return writer_.oom() || writer_.length() >= MAX_BUFFER_SIZE;
|
|
}
|
|
};
|
|
|
|
class RecoverReader;
|
|
|
|
// A snapshot reader reads the entries out of the compressed snapshot buffer in
|
|
// a script. These entries describe the equivalent interpreter frames at a given
|
|
// position in JIT code. Each entry is an Ion's value allocations, used to
|
|
// recover the corresponding Value from an Ion frame.
|
|
class SnapshotReader
|
|
{
|
|
CompactBufferReader reader_;
|
|
CompactBufferReader allocReader_;
|
|
const uint8_t* allocTable_;
|
|
|
|
BailoutKind bailoutKind_;
|
|
uint32_t allocRead_; // Number of slots that have been read.
|
|
RecoverOffset recoverOffset_; // Offset of the recover instructions.
|
|
|
|
#ifdef TRACK_SNAPSHOTS
|
|
private:
|
|
uint32_t pcOpcode_;
|
|
uint32_t mirOpcode_;
|
|
uint32_t mirId_;
|
|
uint32_t lirOpcode_;
|
|
uint32_t lirId_;
|
|
|
|
public:
|
|
void readTrackSnapshot();
|
|
void spewBailingFrom() const;
|
|
#endif
|
|
|
|
private:
|
|
void readSnapshotHeader();
|
|
uint32_t readAllocationIndex();
|
|
|
|
public:
|
|
SnapshotReader(const uint8_t* snapshots, uint32_t offset,
|
|
uint32_t RVATableSize, uint32_t listSize);
|
|
|
|
RValueAllocation readAllocation();
|
|
void skipAllocation() {
|
|
readAllocationIndex();
|
|
}
|
|
|
|
BailoutKind bailoutKind() const {
|
|
return bailoutKind_;
|
|
}
|
|
RecoverOffset recoverOffset() const {
|
|
return recoverOffset_;
|
|
}
|
|
|
|
uint32_t numAllocationsRead() const {
|
|
return allocRead_;
|
|
}
|
|
void resetNumAllocationsRead() {
|
|
allocRead_ = 0;
|
|
}
|
|
};
|
|
|
|
class RInstructionStorage
|
|
{
|
|
static const size_t Size = 4 * sizeof(uint32_t);
|
|
mozilla::AlignedStorage<Size> mem;
|
|
|
|
public:
|
|
const void* addr() const { return mem.addr(); }
|
|
void* addr() { return mem.addr(); }
|
|
|
|
RInstructionStorage() = default;
|
|
|
|
RInstructionStorage(const RInstructionStorage& other) {
|
|
memcpy(addr(), other.addr(), Size);
|
|
}
|
|
void operator=(const RInstructionStorage& other) {
|
|
memcpy(addr(), other.addr(), Size);
|
|
}
|
|
};
|
|
|
|
class RInstruction;
|
|
|
|
class RecoverReader
|
|
{
|
|
CompactBufferReader reader_;
|
|
|
|
// Number of encoded instructions.
|
|
uint32_t numInstructions_;
|
|
|
|
// Number of instruction read.
|
|
uint32_t numInstructionsRead_;
|
|
|
|
// True if we need to resume after the Resume Point instruction of the
|
|
// innermost frame.
|
|
bool resumeAfter_;
|
|
|
|
// Space is reserved as part of the RecoverReader to avoid allocations of
|
|
// data which is needed to decode the current instruction.
|
|
RInstructionStorage rawData_;
|
|
|
|
private:
|
|
void readRecoverHeader();
|
|
void readInstruction();
|
|
|
|
public:
|
|
RecoverReader(SnapshotReader& snapshot, const uint8_t* recovers, uint32_t size);
|
|
|
|
uint32_t numInstructions() const {
|
|
return numInstructions_;
|
|
}
|
|
uint32_t numInstructionsRead() const {
|
|
return numInstructionsRead_;
|
|
}
|
|
|
|
bool moreInstructions() const {
|
|
return numInstructionsRead_ < numInstructions_;
|
|
}
|
|
void nextInstruction() {
|
|
readInstruction();
|
|
}
|
|
|
|
const RInstruction* instruction() const {
|
|
return reinterpret_cast<const RInstruction*>(rawData_.addr());
|
|
}
|
|
|
|
bool resumeAfter() const {
|
|
return resumeAfter_;
|
|
}
|
|
};
|
|
|
|
} // namespace jit
|
|
} // namespace js
|
|
|
|
#endif /* jit_Snapshot_h */
|