/* -*- 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 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 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(rawData_.addr()); } }; } // namespace jit } // namespace js #endif /* jit_Snapshot_h */