diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /js/src/jit/Snapshots.cpp | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/jit/Snapshots.cpp')
-rw-r--r-- | js/src/jit/Snapshots.cpp | 605 |
1 files changed, 605 insertions, 0 deletions
diff --git a/js/src/jit/Snapshots.cpp b/js/src/jit/Snapshots.cpp new file mode 100644 index 0000000000..2b6c2a945b --- /dev/null +++ b/js/src/jit/Snapshots.cpp @@ -0,0 +1,605 @@ +/* -*- 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/. */ + +#include "jit/Snapshots.h" + +#include "jit/JitSpewer.h" +#ifdef TRACK_SNAPSHOTS +# include "jit/LIR.h" +#endif +#include "jit/MIR.h" +#include "jit/Recover.h" +#include "js/Printer.h" + +using namespace js; +using namespace js::jit; + +// [SMDOC] IonMonkey Snapshot encoding +// +// 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_UNSAFE_PRINTF("Unexpected mode: 0x%x", uint32_t(mode)); +} + +// 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 { + HashNumber res = 0; + res = HashNumber(mode_); + res = arg1_.index + (res << 6) + (res << 16) - res; + res = arg2_.index + (res << 6) + (res << 16) - res; + return res; +} + +#ifdef JS_JITSPEW +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(")"); + } +} +#endif // JS_JITSPEW + +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_assert((1 << SNAPSHOT_BAILOUTKIND_BITS) - 1 >= + uint8_t(BailoutKind::Limit), + "Not enough bits for BailoutKinds"); + +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); + +#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", + uint32_t(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 { +# ifdef JS_JITSPEW + if (JitSpewEnabled(JitSpew_IonBailouts)) { + JitSpewHeader(JitSpew_IonBailouts); + Fprinter& out = JitSpewPrinter(); + out.printf(" bailing from bytecode: %s, MIR: ", CodeName(JSOp(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 +} +#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_); +} + +SnapshotWriter::SnapshotWriter() + // Based on the measurements made in Bug 962555 comment 20, this length + // should be enough to prevent the reallocation of the hash table for at + // least half of the compilations. + : allocMap_(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(); +} + +RecoverReader::RecoverReader(const RecoverReader& rr) + : reader_(rr.reader_), + numInstructions_(rr.numInstructions_), + numInstructionsRead_(rr.numInstructionsRead_) { + if (reader_.currentPosition()) { + rr.instruction()->cloneInto(&rawData_); + } +} + +RecoverReader& RecoverReader::operator=(const RecoverReader& rr) { + reader_ = rr.reader_; + numInstructions_ = rr.numInstructions_; + numInstructionsRead_ = rr.numInstructionsRead_; + if (reader_.currentPosition()) { + rr.instruction()->cloneInto(&rawData_); + } + return *this; +} + +void RecoverReader::readRecoverHeader() { + numInstructions_ = reader_.readUnsigned(); + MOZ_ASSERT(numInstructions_); + + JitSpew(JitSpew_IonSnapshots, "Read recover header with instructionCount %u", + numInstructions_); +} + +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, uint32_t(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) { + 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(); + } + +#ifdef JS_JITSPEW + if (JitSpewEnabled(JitSpew_IonSnapshots)) { + JitSpewHeader(JitSpew_IonSnapshots); + Fprinter& out = JitSpewPrinter(); + out.printf(" slot %u (%u): ", allocWritten_, offset); + alloc.dump(out); + out.printf("\n"); + } +#endif + + 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) { + MOZ_ASSERT(instructionCount); + instructionCount_ = instructionCount; + instructionsWritten_ = 0; + + JitSpew(JitSpew_IonSnapshots, "starting recover with %u instruction(s)", + instructionCount); + + RecoverOffset recoverOffset = writer_.length(); + writer_.writeUnsigned(instructionCount); + return recoverOffset; +} + +void RecoverWriter::writeInstruction(const MNode* rp) { + if (!rp->writeRecoverData(writer_)) { + writer_.setOOM(); + } + instructionsWritten_++; +} + +void RecoverWriter::endRecover() { + MOZ_ASSERT(instructionCount_ == instructionsWritten_); +} |