summaryrefslogtreecommitdiffstats
path: root/js/src/jit/Snapshots.h
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jit/Snapshots.h')
-rw-r--r--js/src/jit/Snapshots.h529
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 */