diff options
Diffstat (limited to 'js/src/jit/Snapshots.cpp')
-rw-r--r-- | js/src/jit/Snapshots.cpp | 731 |
1 files changed, 731 insertions, 0 deletions
diff --git a/js/src/jit/Snapshots.cpp b/js/src/jit/Snapshots.cpp new file mode 100644 index 000000000..9923e41fc --- /dev/null +++ b/js/src/jit/Snapshots.cpp @@ -0,0 +1,731 @@ +/* -*- 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 http://mozilla.org/MPL/2.0/. */ + +#include "jit/Snapshots.h" + +#include "jsscript.h" + +#include "jit/CompileInfo.h" +#include "jit/JitSpewer.h" +#ifdef TRACK_SNAPSHOTS +# include "jit/LIR.h" +#endif +#include "jit/MIR.h" +#include "jit/Recover.h" + +#include "vm/Printer.h" + +using namespace js; +using namespace js::jit; + +// Encodings: +// [ptr] A fixed-size pointer. +// [vwu] A variable-width unsigned integer. +// [vws] A variable-width signed integer. +// [u8] An 8-bit unsigned integer. +// [u8'] An 8-bit unsigned integer which is potentially extended with packed +// data. +// [u8"] Packed data which is stored and packed in the previous [u8']. +// [vwu*] A list of variable-width unsigned integers. +// [pld] Payload of Recover Value Allocation: +// PAYLOAD_NONE: +// There is no payload. +// +// PAYLOAD_INDEX: +// [vwu] Index, such as the constant pool index. +// +// PAYLOAD_STACK_OFFSET: +// [vws] Stack offset based on the base of the Ion frame. +// +// PAYLOAD_GPR: +// [u8] Code of the general register. +// +// PAYLOAD_FPU: +// [u8] Code of the FPU register. +// +// PAYLOAD_PACKED_TAG: +// [u8"] Bits 5-7: JSValueType is encoded on the low bits of the Mode +// of the RValueAllocation. +// +// Snapshot header: +// +// [vwu] bits ((n+1)-31]: recover instruction offset +// bits [0,n): bailout kind (n = SNAPSHOT_BAILOUTKIND_BITS) +// +// Snapshot body, repeated "frame count" times, from oldest frame to newest frame. +// Note that the first frame doesn't have the "parent PC" field. +// +// [ptr] Debug only: JSScript* +// [vwu] pc offset +// [vwu] # of RVA's indexes, including nargs +// [vwu*] List of indexes to R(ecover)ValueAllocation table. Contains +// nargs + nfixed + stackDepth items. +// +// Recover value allocations are encoded at the end of the Snapshot buffer, and +// they are padded on ALLOCATION_TABLE_ALIGNMENT. The encoding of each +// allocation is determined by the RValueAllocation::Layout, which can be +// obtained from the RValueAllocation::Mode with layoutFromMode function. The +// layout structure list the type of payload which are used to serialized / +// deserialized / dumped the content of the allocations. +// +// R(ecover)ValueAllocation items: +// [u8'] Mode, which defines the type of the payload as well as the +// interpretation. +// [pld] first payload (packed tag, index, stack offset, register, ...) +// [pld] second payload (register, stack offset, none) +// +// Modes: +// CONSTANT [INDEX] +// Index into the constant pool. +// +// CST_UNDEFINED [] +// Constant value which correspond to the "undefined" JS value. +// +// CST_NULL [] +// Constant value which correspond to the "null" JS value. +// +// DOUBLE_REG [FPU_REG] +// Double value stored in a FPU register. +// +// ANY_FLOAT_REG [FPU_REG] +// Any Float value (float32, simd) stored in a FPU register. +// +// ANY_FLOAT_STACK [STACK_OFFSET] +// Any Float value (float32, simd) stored on the stack. +// +// UNTYPED_REG [GPR_REG] +// UNTYPED_STACK [STACK_OFFSET] +// UNTYPED_REG_REG [GPR_REG, GPR_REG] +// UNTYPED_REG_STACK [GPR_REG, STACK_OFFSET] +// UNTYPED_STACK_REG [STACK_OFFSET, GPR_REG] +// UNTYPED_STACK_STACK [STACK_OFFSET, STACK_OFFSET] +// Value with dynamically known type. On 32 bits architecture, the +// first register/stack-offset correspond to the holder of the type, +// and the second correspond to the payload of the JS Value. +// +// RECOVER_INSTRUCTION [INDEX] +// Index into the list of recovered instruction results. +// +// RI_WITH_DEFAULT_CST [INDEX] [INDEX] +// The first payload is the index into the list of recovered +// instruction results. The second payload is the index in the +// constant pool. +// +// TYPED_REG [PACKED_TAG, GPR_REG]: +// Value with statically known type, which payload is stored in a +// register. +// +// TYPED_STACK [PACKED_TAG, STACK_OFFSET]: +// Value with statically known type, which payload is stored at an +// offset on the stack. +// + +const RValueAllocation::Layout& +RValueAllocation::layoutFromMode(Mode mode) +{ + switch (mode) { + case CONSTANT: { + static const RValueAllocation::Layout layout = { + PAYLOAD_INDEX, + PAYLOAD_NONE, + "constant" + }; + return layout; + } + + case CST_UNDEFINED: { + static const RValueAllocation::Layout layout = { + PAYLOAD_NONE, + PAYLOAD_NONE, + "undefined" + }; + return layout; + } + + case CST_NULL: { + static const RValueAllocation::Layout layout = { + PAYLOAD_NONE, + PAYLOAD_NONE, + "null" + }; + return layout; + } + + case DOUBLE_REG: { + static const RValueAllocation::Layout layout = { + PAYLOAD_FPU, + PAYLOAD_NONE, + "double" + }; + return layout; + } + case ANY_FLOAT_REG: { + static const RValueAllocation::Layout layout = { + PAYLOAD_FPU, + PAYLOAD_NONE, + "float register content" + }; + return layout; + } + case ANY_FLOAT_STACK: { + static const RValueAllocation::Layout layout = { + PAYLOAD_STACK_OFFSET, + PAYLOAD_NONE, + "float register content" + }; + return layout; + } +#if defined(JS_NUNBOX32) + case UNTYPED_REG_REG: { + static const RValueAllocation::Layout layout = { + PAYLOAD_GPR, + PAYLOAD_GPR, + "value" + }; + return layout; + } + case UNTYPED_REG_STACK: { + static const RValueAllocation::Layout layout = { + PAYLOAD_GPR, + PAYLOAD_STACK_OFFSET, + "value" + }; + return layout; + } + case UNTYPED_STACK_REG: { + static const RValueAllocation::Layout layout = { + PAYLOAD_STACK_OFFSET, + PAYLOAD_GPR, + "value" + }; + return layout; + } + case UNTYPED_STACK_STACK: { + static const RValueAllocation::Layout layout = { + PAYLOAD_STACK_OFFSET, + PAYLOAD_STACK_OFFSET, + "value" + }; + return layout; + } +#elif defined(JS_PUNBOX64) + case UNTYPED_REG: { + static const RValueAllocation::Layout layout = { + PAYLOAD_GPR, + PAYLOAD_NONE, + "value" + }; + return layout; + } + case UNTYPED_STACK: { + static const RValueAllocation::Layout layout = { + PAYLOAD_STACK_OFFSET, + PAYLOAD_NONE, + "value" + }; + return layout; + } +#endif + case RECOVER_INSTRUCTION: { + static const RValueAllocation::Layout layout = { + PAYLOAD_INDEX, + PAYLOAD_NONE, + "instruction" + }; + return layout; + } + case RI_WITH_DEFAULT_CST: { + static const RValueAllocation::Layout layout = { + PAYLOAD_INDEX, + PAYLOAD_INDEX, + "instruction with default" + }; + return layout; + } + + default: { + static const RValueAllocation::Layout regLayout = { + PAYLOAD_PACKED_TAG, + PAYLOAD_GPR, + "typed value" + }; + + static const RValueAllocation::Layout stackLayout = { + PAYLOAD_PACKED_TAG, + PAYLOAD_STACK_OFFSET, + "typed value" + }; + + if (mode >= TYPED_REG_MIN && mode <= TYPED_REG_MAX) + return regLayout; + if (mode >= TYPED_STACK_MIN && mode <= TYPED_STACK_MAX) + return stackLayout; + } + } + + MOZ_CRASH("Wrong mode type?"); +} + +// Pad serialized RValueAllocations by a multiple of X bytes in the allocation +// buffer. By padding serialized value allocations, we are building an +// indexable table of elements of X bytes, and thus we can safely divide any +// offset within the buffer by X to obtain an index. +// +// By padding, we are loosing space within the allocation buffer, but we +// multiple by X the number of indexes that we can store on one byte in each +// snapshots. +// +// Some value allocations are taking more than X bytes to be encoded, in which +// case we will pad to a multiple of X, and we are wasting indexes. The choice +// of X should be balanced between the wasted padding of serialized value +// allocation, and the saving made in snapshot indexes. +static const size_t ALLOCATION_TABLE_ALIGNMENT = 2; /* bytes */ + +void +RValueAllocation::readPayload(CompactBufferReader& reader, PayloadType type, + uint8_t* mode, Payload* p) +{ + switch (type) { + case PAYLOAD_NONE: + break; + case PAYLOAD_INDEX: + p->index = reader.readUnsigned(); + break; + case PAYLOAD_STACK_OFFSET: + p->stackOffset = reader.readSigned(); + break; + case PAYLOAD_GPR: + p->gpr = Register::FromCode(reader.readByte()); + break; + case PAYLOAD_FPU: + p->fpu.data = reader.readByte(); + break; + case PAYLOAD_PACKED_TAG: + p->type = JSValueType(*mode & PACKED_TAG_MASK); + *mode = *mode & ~PACKED_TAG_MASK; + break; + } +} + +RValueAllocation +RValueAllocation::read(CompactBufferReader& reader) +{ + uint8_t mode = reader.readByte(); + const Layout& layout = layoutFromMode(Mode(mode & MODE_BITS_MASK)); + Payload arg1, arg2; + + readPayload(reader, layout.type1, &mode, &arg1); + readPayload(reader, layout.type2, &mode, &arg2); + return RValueAllocation(Mode(mode), arg1, arg2); +} + +void +RValueAllocation::writePayload(CompactBufferWriter& writer, PayloadType type, Payload p) +{ + switch (type) { + case PAYLOAD_NONE: + break; + case PAYLOAD_INDEX: + writer.writeUnsigned(p.index); + break; + case PAYLOAD_STACK_OFFSET: + writer.writeSigned(p.stackOffset); + break; + case PAYLOAD_GPR: + static_assert(Registers::Total <= 0x100, + "Not enough bytes to encode all registers."); + writer.writeByte(p.gpr.code()); + break; + case PAYLOAD_FPU: + static_assert(FloatRegisters::Total <= 0x100, + "Not enough bytes to encode all float registers."); + writer.writeByte(p.fpu.code()); + break; + case PAYLOAD_PACKED_TAG: { + // This code assumes that the PACKED_TAG payload is following the + // writeByte of the mode. + if (!writer.oom()) { + MOZ_ASSERT(writer.length()); + uint8_t* mode = writer.buffer() + (writer.length() - 1); + MOZ_ASSERT((*mode & PACKED_TAG_MASK) == 0 && (p.type & ~PACKED_TAG_MASK) == 0); + *mode = *mode | p.type; + } + break; + } + } +} + +void +RValueAllocation::writePadding(CompactBufferWriter& writer) +{ + // Write 0x7f in all padding bytes. + while (writer.length() % ALLOCATION_TABLE_ALIGNMENT) + writer.writeByte(0x7f); +} + +void +RValueAllocation::write(CompactBufferWriter& writer) const +{ + const Layout& layout = layoutFromMode(mode()); + MOZ_ASSERT(layout.type2 != PAYLOAD_PACKED_TAG); + MOZ_ASSERT(writer.length() % ALLOCATION_TABLE_ALIGNMENT == 0); + + writer.writeByte(mode_); + writePayload(writer, layout.type1, arg1_); + writePayload(writer, layout.type2, arg2_); + writePadding(writer); +} + +HashNumber +RValueAllocation::hash() const { + CompactBufferWriter writer; + write(writer); + + // We should never oom because the compact buffer writer has 32 inlined + // bytes, and in the worse case scenario, only encode 12 bytes + // (12 == mode + signed + signed + pad). + MOZ_ASSERT(!writer.oom()); + MOZ_ASSERT(writer.length() <= 12); + + HashNumber res = 0; + for (size_t i = 0; i < writer.length(); i++) { + res = ((res << 8) | (res >> (sizeof(res) - 1))); + res ^= writer.buffer()[i]; + } + return res; +} + +static const char* +ValTypeToString(JSValueType type) +{ + switch (type) { + case JSVAL_TYPE_INT32: + return "int32_t"; + case JSVAL_TYPE_DOUBLE: + return "double"; + case JSVAL_TYPE_STRING: + return "string"; + case JSVAL_TYPE_SYMBOL: + return "symbol"; + case JSVAL_TYPE_BOOLEAN: + return "boolean"; + case JSVAL_TYPE_OBJECT: + return "object"; + case JSVAL_TYPE_MAGIC: + return "magic"; + default: + MOZ_CRASH("no payload"); + } +} + +void +RValueAllocation::dumpPayload(GenericPrinter& out, PayloadType type, Payload p) +{ + switch (type) { + case PAYLOAD_NONE: + break; + case PAYLOAD_INDEX: + out.printf("index %u", p.index); + break; + case PAYLOAD_STACK_OFFSET: + out.printf("stack %d", p.stackOffset); + break; + case PAYLOAD_GPR: + out.printf("reg %s", p.gpr.name()); + break; + case PAYLOAD_FPU: + out.printf("reg %s", p.fpu.name()); + break; + case PAYLOAD_PACKED_TAG: + out.printf("%s", ValTypeToString(p.type)); + break; + } +} + +void +RValueAllocation::dump(GenericPrinter& out) const +{ + const Layout& layout = layoutFromMode(mode()); + out.printf("%s", layout.name); + + if (layout.type1 != PAYLOAD_NONE) + out.printf(" ("); + dumpPayload(out, layout.type1, arg1_); + if (layout.type2 != PAYLOAD_NONE) + out.printf(", "); + dumpPayload(out, layout.type2, arg2_); + if (layout.type1 != PAYLOAD_NONE) + out.printf(")"); +} + +bool +RValueAllocation::equalPayloads(PayloadType type, Payload lhs, Payload rhs) +{ + switch (type) { + case PAYLOAD_NONE: + return true; + case PAYLOAD_INDEX: + return lhs.index == rhs.index; + case PAYLOAD_STACK_OFFSET: + return lhs.stackOffset == rhs.stackOffset; + case PAYLOAD_GPR: + return lhs.gpr == rhs.gpr; + case PAYLOAD_FPU: + return lhs.fpu == rhs.fpu; + case PAYLOAD_PACKED_TAG: + return lhs.type == rhs.type; + } + + return false; +} + +SnapshotReader::SnapshotReader(const uint8_t* snapshots, uint32_t offset, + uint32_t RVATableSize, uint32_t listSize) + : reader_(snapshots + offset, snapshots + listSize), + allocReader_(snapshots + listSize, snapshots + listSize + RVATableSize), + allocTable_(snapshots + listSize), + allocRead_(0) +{ + if (!snapshots) + return; + JitSpew(JitSpew_IonSnapshots, "Creating snapshot reader"); + readSnapshotHeader(); +} + +#define COMPUTE_SHIFT_AFTER_(name) (name ## _BITS + name ##_SHIFT) +#define COMPUTE_MASK_(name) ((uint32_t(1 << name ## _BITS) - 1) << name ##_SHIFT) + +// Details of snapshot header packing. +static const uint32_t SNAPSHOT_BAILOUTKIND_SHIFT = 0; +static const uint32_t SNAPSHOT_BAILOUTKIND_BITS = 6; +static const uint32_t SNAPSHOT_BAILOUTKIND_MASK = COMPUTE_MASK_(SNAPSHOT_BAILOUTKIND); + +static const uint32_t SNAPSHOT_ROFFSET_SHIFT = COMPUTE_SHIFT_AFTER_(SNAPSHOT_BAILOUTKIND); +static const uint32_t SNAPSHOT_ROFFSET_BITS = 32 - SNAPSHOT_ROFFSET_SHIFT; +static const uint32_t SNAPSHOT_ROFFSET_MASK = COMPUTE_MASK_(SNAPSHOT_ROFFSET); + +// Details of recover header packing. +static const uint32_t RECOVER_RESUMEAFTER_SHIFT = 0; +static const uint32_t RECOVER_RESUMEAFTER_BITS = 1; +static const uint32_t RECOVER_RESUMEAFTER_MASK = COMPUTE_MASK_(RECOVER_RESUMEAFTER); + +static const uint32_t RECOVER_RINSCOUNT_SHIFT = COMPUTE_SHIFT_AFTER_(RECOVER_RESUMEAFTER); +static const uint32_t RECOVER_RINSCOUNT_BITS = 32 - RECOVER_RINSCOUNT_SHIFT; +static const uint32_t RECOVER_RINSCOUNT_MASK = COMPUTE_MASK_(RECOVER_RINSCOUNT); + +#undef COMPUTE_MASK_ +#undef COMPUTE_SHIFT_AFTER_ + +void +SnapshotReader::readSnapshotHeader() +{ + uint32_t bits = reader_.readUnsigned(); + + bailoutKind_ = BailoutKind((bits & SNAPSHOT_BAILOUTKIND_MASK) >> SNAPSHOT_BAILOUTKIND_SHIFT); + recoverOffset_ = (bits & SNAPSHOT_ROFFSET_MASK) >> SNAPSHOT_ROFFSET_SHIFT; + + JitSpew(JitSpew_IonSnapshots, "Read snapshot header with bailout kind %u", + bailoutKind_); + +#ifdef TRACK_SNAPSHOTS + readTrackSnapshot(); +#endif +} + +#ifdef TRACK_SNAPSHOTS +void +SnapshotReader::readTrackSnapshot() +{ + pcOpcode_ = reader_.readUnsigned(); + mirOpcode_ = reader_.readUnsigned(); + mirId_ = reader_.readUnsigned(); + lirOpcode_ = reader_.readUnsigned(); + lirId_ = reader_.readUnsigned(); +} + +void +SnapshotReader::spewBailingFrom() const +{ + if (JitSpewEnabled(JitSpew_IonBailouts)) { + JitSpewHeader(JitSpew_IonBailouts); + Fprinter& out = JitSpewPrinter(); + out.printf(" bailing from bytecode: %s, MIR: ", CodeName[pcOpcode_]); + MDefinition::PrintOpcodeName(out, MDefinition::Opcode(mirOpcode_)); + out.printf(" [%u], LIR: ", mirId_); + LInstruction::printName(out, LInstruction::Opcode(lirOpcode_)); + out.printf(" [%u]", lirId_); + out.printf("\n"); + } +} +#endif + +uint32_t +SnapshotReader::readAllocationIndex() +{ + allocRead_++; + return reader_.readUnsigned(); +} + +RValueAllocation +SnapshotReader::readAllocation() +{ + JitSpew(JitSpew_IonSnapshots, "Reading slot %u", allocRead_); + uint32_t offset = readAllocationIndex() * ALLOCATION_TABLE_ALIGNMENT; + allocReader_.seek(allocTable_, offset); + return RValueAllocation::read(allocReader_); +} + +bool +SnapshotWriter::init() +{ + // Based on the measurements made in Bug 962555 comment 20, this should be + // enough to prevent the reallocation of the hash table for at least half of + // the compilations. + return allocMap_.init(32); +} + +RecoverReader::RecoverReader(SnapshotReader& snapshot, const uint8_t* recovers, uint32_t size) + : reader_(nullptr, nullptr), + numInstructions_(0), + numInstructionsRead_(0) +{ + if (!recovers) + return; + reader_ = CompactBufferReader(recovers + snapshot.recoverOffset(), recovers + size); + readRecoverHeader(); + readInstruction(); +} + +void +RecoverReader::readRecoverHeader() +{ + uint32_t bits = reader_.readUnsigned(); + + numInstructions_ = (bits & RECOVER_RINSCOUNT_MASK) >> RECOVER_RINSCOUNT_SHIFT; + resumeAfter_ = (bits & RECOVER_RESUMEAFTER_MASK) >> RECOVER_RESUMEAFTER_SHIFT; + MOZ_ASSERT(numInstructions_); + + JitSpew(JitSpew_IonSnapshots, "Read recover header with instructionCount %u (ra: %d)", + numInstructions_, resumeAfter_); +} + +void +RecoverReader::readInstruction() +{ + MOZ_ASSERT(moreInstructions()); + RInstruction::readRecoverData(reader_, &rawData_); + numInstructionsRead_++; +} + +SnapshotOffset +SnapshotWriter::startSnapshot(RecoverOffset recoverOffset, BailoutKind kind) +{ + lastStart_ = writer_.length(); + allocWritten_ = 0; + + JitSpew(JitSpew_IonSnapshots, "starting snapshot with recover offset %u, bailout kind %u", + recoverOffset, kind); + + MOZ_ASSERT(uint32_t(kind) < (1 << SNAPSHOT_BAILOUTKIND_BITS)); + MOZ_ASSERT(recoverOffset < (1 << SNAPSHOT_ROFFSET_BITS)); + uint32_t bits = + (uint32_t(kind) << SNAPSHOT_BAILOUTKIND_SHIFT) | + (recoverOffset << SNAPSHOT_ROFFSET_SHIFT); + + writer_.writeUnsigned(bits); + return lastStart_; +} + +#ifdef TRACK_SNAPSHOTS +void +SnapshotWriter::trackSnapshot(uint32_t pcOpcode, uint32_t mirOpcode, uint32_t mirId, + uint32_t lirOpcode, uint32_t lirId) +{ + writer_.writeUnsigned(pcOpcode); + writer_.writeUnsigned(mirOpcode); + writer_.writeUnsigned(mirId); + writer_.writeUnsigned(lirOpcode); + writer_.writeUnsigned(lirId); +} +#endif + +bool +SnapshotWriter::add(const RValueAllocation& alloc) +{ + MOZ_ASSERT(allocMap_.initialized()); + + uint32_t offset; + RValueAllocMap::AddPtr p = allocMap_.lookupForAdd(alloc); + if (!p) { + offset = allocWriter_.length(); + alloc.write(allocWriter_); + if (!allocMap_.add(p, alloc, offset)) { + allocWriter_.setOOM(); + return false; + } + } else { + offset = p->value(); + } + + if (JitSpewEnabled(JitSpew_IonSnapshots)) { + JitSpewHeader(JitSpew_IonSnapshots); + Fprinter& out = JitSpewPrinter(); + out.printf(" slot %u (%d): ", allocWritten_, offset); + alloc.dump(out); + out.printf("\n"); + } + + allocWritten_++; + writer_.writeUnsigned(offset / ALLOCATION_TABLE_ALIGNMENT); + return true; +} + +void +SnapshotWriter::endSnapshot() +{ + // Place a sentinel for asserting on the other end. +#ifdef DEBUG + writer_.writeSigned(-1); +#endif + + JitSpew(JitSpew_IonSnapshots, "ending snapshot total size: %u bytes (start %u)", + uint32_t(writer_.length() - lastStart_), lastStart_); +} + +RecoverOffset +RecoverWriter::startRecover(uint32_t instructionCount, bool resumeAfter) +{ + MOZ_ASSERT(instructionCount); + instructionCount_ = instructionCount; + instructionsWritten_ = 0; + + JitSpew(JitSpew_IonSnapshots, "starting recover with %u instruction(s)", + instructionCount); + + MOZ_ASSERT(!(uint32_t(resumeAfter) &~ RECOVER_RESUMEAFTER_MASK)); + MOZ_ASSERT(instructionCount < uint32_t(1 << RECOVER_RINSCOUNT_BITS)); + uint32_t bits = + (uint32_t(resumeAfter) << RECOVER_RESUMEAFTER_SHIFT) | + (instructionCount << RECOVER_RINSCOUNT_SHIFT); + + RecoverOffset recoverOffset = writer_.length(); + writer_.writeUnsigned(bits); + return recoverOffset; +} + +void +RecoverWriter::writeInstruction(const MNode* rp) +{ + if (!rp->writeRecoverData(writer_)) + writer_.setOOM(); + instructionsWritten_++; +} + +void +RecoverWriter::endRecover() +{ + MOZ_ASSERT(instructionCount_ == instructionsWritten_); +} |