diff options
Diffstat (limited to 'js/src/jit/Snapshots.h')
-rw-r--r-- | js/src/jit/Snapshots.h | 529 |
1 files changed, 529 insertions, 0 deletions
diff --git a/js/src/jit/Snapshots.h b/js/src/jit/Snapshots.h new file mode 100644 index 0000000000..c0c332d926 --- /dev/null +++ b/js/src/jit/Snapshots.h @@ -0,0 +1,529 @@ +/* -*- 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 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef jit_Snapshot_h +#define jit_Snapshot_h + +#include "mozilla/Attributes.h" + +#include "jit/CompactBuffer.h" +#include "jit/IonTypes.h" +#include "jit/Registers.h" +#include "js/AllocPolicy.h" +#include "js/HashTable.h" +#include "js/TypeDecls.h" + +namespace js { +class JS_PUBLIC_API 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() : index(0) { + static_assert(sizeof(index) == sizeof(Payload), + "All Payload bits are initialized."); + } + }; + + 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); +#ifdef JS_JITSPEW + static void dumpPayload(GenericPrinter& out, PayloadType t, Payload p); +#endif + 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) { + arg2_.index = 0; + } + + explicit RValueAllocation(Mode mode) : mode_(mode) { + arg1_.index = 0; + arg2_.index = 0; + } + + public: + RValueAllocation() : mode_(INVALID) { + arg1_.index = 0; + arg2_.index = 0; + } + + // 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: + bool valid() const { return mode_ != INVALID; } + 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: +#ifdef JS_JITSPEW + void dump(GenericPrinter& out) const; +#endif + + bool operator==(const RValueAllocation& rhs) const { + // Note, this equality compares the verbatim content of the payload, + // which is made possible because we ensure that the payload content is + // fully initialized during the creation. + static_assert(sizeof(int32_t) == sizeof(Payload), + "All Payload bits are compared."); + return mode_ == rhs.mode_ && arg1_.index == rhs.arg1_.index && + arg2_.index == rhs.arg2_.index; + } + + HashNumber hash() const; + + struct Hasher { + using Key = RValueAllocation; + using Lookup = Key; + 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. + using RVA = RValueAllocation; + 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: + SnapshotWriter(); + + 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 + [[nodiscard]] 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); + + 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 MOZ_NON_PARAM RInstructionStorage { + static constexpr size_t Size = 4 * sizeof(uint32_t); + + // This presumes all RInstructionStorage are safely void*-alignable. + // RInstruction::readRecoverData asserts that no RInstruction subclass + // has stricter alignment requirements than RInstructionStorage. + static constexpr size_t Alignment = alignof(void*); + + alignas(Alignment) unsigned char mem[Size]; + + public: + const void* addr() const { return mem; } + void* addr() { return mem; } + + RInstructionStorage() = default; + + // Making a copy of raw bytes holding a RInstruction instance would be a + // strict aliasing violation: see bug 1269319 for an instance of bytewise + // copying having caused crashes. + RInstructionStorage(const RInstructionStorage&) = delete; + RInstructionStorage& operator=(const RInstructionStorage& other) = delete; +}; + +class RInstruction; + +class RecoverReader { + CompactBufferReader reader_; + + // Number of encoded instructions. + uint32_t numInstructions_; + + // Number of instruction read. + uint32_t numInstructionsRead_; + + // 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); + explicit RecoverReader(const RecoverReader& rr); + RecoverReader& operator=(const RecoverReader& rr); + + 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()); + } +}; + +} // namespace jit +} // namespace js + +#endif /* jit_Snapshot_h */ |