summaryrefslogtreecommitdiffstats
path: root/js/src/jit/Safepoints.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jit/Safepoints.cpp')
-rw-r--r--js/src/jit/Safepoints.cpp562
1 files changed, 562 insertions, 0 deletions
diff --git a/js/src/jit/Safepoints.cpp b/js/src/jit/Safepoints.cpp
new file mode 100644
index 000000000..b0bb530f9
--- /dev/null
+++ b/js/src/jit/Safepoints.cpp
@@ -0,0 +1,562 @@
+/* -*- 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/Safepoints.h"
+
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/SizePrintfMacros.h"
+
+#include "jit/BitSet.h"
+#include "jit/JitSpewer.h"
+#include "jit/LIR.h"
+
+using namespace js;
+using namespace jit;
+
+using mozilla::FloorLog2;
+
+SafepointWriter::SafepointWriter(uint32_t slotCount, uint32_t argumentCount)
+ : frameSlots_((slotCount / sizeof(intptr_t)) + 1), // Stack slot counts are inclusive.
+ argumentSlots_(argumentCount / sizeof(intptr_t))
+{ }
+
+bool
+SafepointWriter::init(TempAllocator& alloc)
+{
+ return frameSlots_.init(alloc) && argumentSlots_.init(alloc);
+}
+
+uint32_t
+SafepointWriter::startEntry()
+{
+ JitSpew(JitSpew_Safepoints, "Encoding safepoint (position %" PRIuSIZE "):", stream_.length());
+ return uint32_t(stream_.length());
+}
+
+void
+SafepointWriter::writeOsiCallPointOffset(uint32_t osiCallPointOffset)
+{
+ stream_.writeUnsigned(osiCallPointOffset);
+}
+
+static void
+WriteRegisterMask(CompactBufferWriter& stream, uint32_t bits)
+{
+ if (sizeof(PackedRegisterMask) == 1)
+ stream.writeByte(bits);
+ else
+ stream.writeUnsigned(bits);
+}
+
+static int32_t
+ReadRegisterMask(CompactBufferReader& stream)
+{
+ if (sizeof(PackedRegisterMask) == 1)
+ return stream.readByte();
+ return stream.readUnsigned();
+}
+
+static void
+WriteFloatRegisterMask(CompactBufferWriter& stream, uint64_t bits)
+{
+ if (sizeof(FloatRegisters::SetType) == 1) {
+ stream.writeByte(bits);
+ } else if (sizeof(FloatRegisters::SetType) == 4) {
+ stream.writeUnsigned(bits);
+ } else {
+ MOZ_ASSERT(sizeof(FloatRegisters::SetType) == 8);
+ stream.writeUnsigned(bits & 0xffffffff);
+ stream.writeUnsigned(bits >> 32);
+ }
+}
+
+static int64_t
+ReadFloatRegisterMask(CompactBufferReader& stream)
+{
+ if (sizeof(FloatRegisters::SetType) == 1)
+ return stream.readByte();
+ if (sizeof(FloatRegisters::SetType) <= 4)
+ return stream.readUnsigned();
+ MOZ_ASSERT(sizeof(FloatRegisters::SetType) == 8);
+ uint64_t ret = stream.readUnsigned();
+ ret |= uint64_t(stream.readUnsigned()) << 32;
+ return ret;
+}
+
+void
+SafepointWriter::writeGcRegs(LSafepoint* safepoint)
+{
+ LiveGeneralRegisterSet gc(safepoint->gcRegs());
+ LiveGeneralRegisterSet spilledGpr(safepoint->liveRegs().gprs());
+ LiveFloatRegisterSet spilledFloat(safepoint->liveRegs().fpus());
+ LiveGeneralRegisterSet slots(safepoint->slotsOrElementsRegs());
+ LiveGeneralRegisterSet valueRegs;
+
+ WriteRegisterMask(stream_, spilledGpr.bits());
+ if (!spilledGpr.empty()) {
+ WriteRegisterMask(stream_, gc.bits());
+ WriteRegisterMask(stream_, slots.bits());
+
+#ifdef JS_PUNBOX64
+ valueRegs = safepoint->valueRegs();
+ WriteRegisterMask(stream_, valueRegs.bits());
+#endif
+ }
+
+ // GC registers are a subset of the spilled registers.
+ MOZ_ASSERT((valueRegs.bits() & ~spilledGpr.bits()) == 0);
+ MOZ_ASSERT((gc.bits() & ~spilledGpr.bits()) == 0);
+
+ WriteFloatRegisterMask(stream_, spilledFloat.bits());
+
+#ifdef JS_JITSPEW
+ if (JitSpewEnabled(JitSpew_Safepoints)) {
+ for (GeneralRegisterForwardIterator iter(spilledGpr); iter.more(); ++iter) {
+ const char* type = gc.has(*iter)
+ ? "gc"
+ : slots.has(*iter)
+ ? "slots"
+ : valueRegs.has(*iter)
+ ? "value"
+ : "any";
+ JitSpew(JitSpew_Safepoints, " %s reg: %s", type, (*iter).name());
+ }
+ for (FloatRegisterForwardIterator iter(spilledFloat); iter.more(); ++iter)
+ JitSpew(JitSpew_Safepoints, " float reg: %s", (*iter).name());
+ }
+#endif
+}
+
+static void
+WriteBitset(const BitSet& set, CompactBufferWriter& stream)
+{
+ size_t count = set.rawLength();
+ const uint32_t* words = set.raw();
+ for (size_t i = 0; i < count; i++)
+ stream.writeUnsigned(words[i]);
+}
+
+static void
+MapSlotsToBitset(BitSet& stackSet, BitSet& argumentSet,
+ CompactBufferWriter& stream, const LSafepoint::SlotList& slots)
+{
+ stackSet.clear();
+ argumentSet.clear();
+
+ for (uint32_t i = 0; i < slots.length(); i++) {
+ // Slots are represented at a distance from |fp|. We divide by the
+ // pointer size, since we only care about pointer-sized/aligned slots
+ // here.
+ MOZ_ASSERT(slots[i].slot % sizeof(intptr_t) == 0);
+ size_t index = slots[i].slot / sizeof(intptr_t);
+ (slots[i].stack ? stackSet : argumentSet).insert(index);
+ }
+
+ WriteBitset(stackSet, stream);
+ WriteBitset(argumentSet, stream);
+}
+
+void
+SafepointWriter::writeGcSlots(LSafepoint* safepoint)
+{
+ LSafepoint::SlotList& slots = safepoint->gcSlots();
+
+#ifdef JS_JITSPEW
+ for (uint32_t i = 0; i < slots.length(); i++)
+ JitSpew(JitSpew_Safepoints, " gc slot: %u", slots[i].slot);
+#endif
+
+ MapSlotsToBitset(frameSlots_, argumentSlots_, stream_, slots);
+}
+
+void
+SafepointWriter::writeSlotsOrElementsSlots(LSafepoint* safepoint)
+{
+ LSafepoint::SlotList& slots = safepoint->slotsOrElementsSlots();
+
+ stream_.writeUnsigned(slots.length());
+
+ for (uint32_t i = 0; i < slots.length(); i++) {
+ if (!slots[i].stack)
+ MOZ_CRASH();
+#ifdef JS_JITSPEW
+ JitSpew(JitSpew_Safepoints, " slots/elements slot: %d", slots[i].slot);
+#endif
+ stream_.writeUnsigned(slots[i].slot);
+ }
+}
+
+void
+SafepointWriter::writeValueSlots(LSafepoint* safepoint)
+{
+ LSafepoint::SlotList& slots = safepoint->valueSlots();
+
+#ifdef JS_JITSPEW
+ for (uint32_t i = 0; i < slots.length(); i++)
+ JitSpew(JitSpew_Safepoints, " gc value: %u", slots[i].slot);
+#endif
+
+ MapSlotsToBitset(frameSlots_, argumentSlots_, stream_, slots);
+}
+
+#if defined(JS_JITSPEW) && defined(JS_NUNBOX32)
+static void
+DumpNunboxPart(const LAllocation& a)
+{
+ Fprinter& out = JitSpewPrinter();
+ if (a.isStackSlot()) {
+ out.printf("stack %d", a.toStackSlot()->slot());
+ } else if (a.isArgument()) {
+ out.printf("arg %d", a.toArgument()->index());
+ } else {
+ out.printf("reg %s", a.toGeneralReg()->reg().name());
+ }
+}
+#endif // DEBUG
+
+// Nunbox part encoding:
+//
+// Reg = 000
+// Stack = 001
+// Arg = 010
+//
+// [vwu] nentries:
+// uint16_t: tttp ppXX XXXY YYYY
+//
+// If ttt = Reg, type is reg XXXXX
+// If ppp = Reg, payload is reg YYYYY
+//
+// If ttt != Reg, type is:
+// XXXXX if not 11111, otherwise followed by [vwu]
+// If ppp != Reg, payload is:
+// YYYYY if not 11111, otherwise followed by [vwu]
+//
+enum NunboxPartKind {
+ Part_Reg,
+ Part_Stack,
+ Part_Arg
+};
+
+static const uint32_t PART_KIND_BITS = 3;
+static const uint32_t PART_KIND_MASK = (1 << PART_KIND_BITS) - 1;
+static const uint32_t PART_INFO_BITS = 5;
+static const uint32_t PART_INFO_MASK = (1 << PART_INFO_BITS) - 1;
+
+static const uint32_t MAX_INFO_VALUE = (1 << PART_INFO_BITS) - 1;
+static const uint32_t TYPE_KIND_SHIFT = 16 - PART_KIND_BITS;
+static const uint32_t PAYLOAD_KIND_SHIFT = TYPE_KIND_SHIFT - PART_KIND_BITS;
+static const uint32_t TYPE_INFO_SHIFT = PAYLOAD_KIND_SHIFT - PART_INFO_BITS;
+static const uint32_t PAYLOAD_INFO_SHIFT = TYPE_INFO_SHIFT - PART_INFO_BITS;
+
+JS_STATIC_ASSERT(PAYLOAD_INFO_SHIFT == 0);
+
+#ifdef JS_NUNBOX32
+static inline NunboxPartKind
+AllocationToPartKind(const LAllocation& a)
+{
+ if (a.isRegister())
+ return Part_Reg;
+ if (a.isStackSlot())
+ return Part_Stack;
+ MOZ_ASSERT(a.isArgument());
+ return Part_Arg;
+}
+
+// gcc 4.5 doesn't actually inline CanEncodeInfoInHeader when only
+// using the "inline" keyword, and miscompiles the function as well
+// when doing block reordering with branch prediction information.
+// See bug 799295 comment 71.
+static MOZ_ALWAYS_INLINE bool
+CanEncodeInfoInHeader(const LAllocation& a, uint32_t* out)
+{
+ if (a.isGeneralReg()) {
+ *out = a.toGeneralReg()->reg().code();
+ return true;
+ }
+
+ if (a.isStackSlot())
+ *out = a.toStackSlot()->slot();
+ else
+ *out = a.toArgument()->index();
+
+ return *out < MAX_INFO_VALUE;
+}
+
+void
+SafepointWriter::writeNunboxParts(LSafepoint* safepoint)
+{
+ LSafepoint::NunboxList& entries = safepoint->nunboxParts();
+
+# ifdef JS_JITSPEW
+ if (JitSpewEnabled(JitSpew_Safepoints)) {
+ for (uint32_t i = 0; i < entries.length(); i++) {
+ SafepointNunboxEntry& entry = entries[i];
+ if (entry.type.isUse() || entry.payload.isUse())
+ continue;
+ JitSpewHeader(JitSpew_Safepoints);
+ Fprinter& out = JitSpewPrinter();
+ out.printf(" nunbox (type in ");
+ DumpNunboxPart(entry.type);
+ out.printf(", payload in ");
+ DumpNunboxPart(entry.payload);
+ out.printf(")\n");
+ }
+ }
+# endif
+
+ // Safepoints are permitted to have partially filled in entries for nunboxes,
+ // provided that only the type is live and not the payload. Omit these from
+ // the written safepoint.
+
+ size_t pos = stream_.length();
+ stream_.writeUnsigned(entries.length());
+
+ size_t count = 0;
+ for (size_t i = 0; i < entries.length(); i++) {
+ SafepointNunboxEntry& entry = entries[i];
+
+ if (entry.payload.isUse()) {
+ // No allocation associated with the payload.
+ continue;
+ }
+
+ if (entry.type.isUse()) {
+ // No allocation associated with the type. Look for another
+ // safepoint entry with an allocation for the type.
+ entry.type = safepoint->findTypeAllocation(entry.typeVreg);
+ if (entry.type.isUse())
+ continue;
+ }
+
+ count++;
+
+ uint16_t header = 0;
+
+ header |= (AllocationToPartKind(entry.type) << TYPE_KIND_SHIFT);
+ header |= (AllocationToPartKind(entry.payload) << PAYLOAD_KIND_SHIFT);
+
+ uint32_t typeVal;
+ bool typeExtra = !CanEncodeInfoInHeader(entry.type, &typeVal);
+ if (!typeExtra)
+ header |= (typeVal << TYPE_INFO_SHIFT);
+ else
+ header |= (MAX_INFO_VALUE << TYPE_INFO_SHIFT);
+
+ uint32_t payloadVal;
+ bool payloadExtra = !CanEncodeInfoInHeader(entry.payload, &payloadVal);
+ if (!payloadExtra)
+ header |= (payloadVal << PAYLOAD_INFO_SHIFT);
+ else
+ header |= (MAX_INFO_VALUE << PAYLOAD_INFO_SHIFT);
+
+ stream_.writeFixedUint16_t(header);
+ if (typeExtra)
+ stream_.writeUnsigned(typeVal);
+ if (payloadExtra)
+ stream_.writeUnsigned(payloadVal);
+ }
+
+ // Update the stream with the actual number of safepoint entries written.
+ stream_.writeUnsignedAt(pos, count, entries.length());
+}
+#endif
+
+void
+SafepointWriter::encode(LSafepoint* safepoint)
+{
+ uint32_t safepointOffset = startEntry();
+
+ MOZ_ASSERT(safepoint->osiCallPointOffset());
+
+ writeOsiCallPointOffset(safepoint->osiCallPointOffset());
+ writeGcRegs(safepoint);
+ writeGcSlots(safepoint);
+ writeValueSlots(safepoint);
+
+#ifdef JS_NUNBOX32
+ writeNunboxParts(safepoint);
+#endif
+
+ writeSlotsOrElementsSlots(safepoint);
+
+ endEntry();
+ safepoint->setOffset(safepointOffset);
+}
+
+void
+SafepointWriter::endEntry()
+{
+ JitSpew(JitSpew_Safepoints, " -- entry ended at %d", uint32_t(stream_.length()));
+}
+
+SafepointReader::SafepointReader(IonScript* script, const SafepointIndex* si)
+ : stream_(script->safepoints() + si->safepointOffset(),
+ script->safepoints() + script->safepointsSize()),
+ frameSlots_((script->frameSlots() / sizeof(intptr_t)) + 1), // Stack slot counts are inclusive.
+ argumentSlots_(script->argumentSlots() / sizeof(intptr_t))
+{
+ osiCallPointOffset_ = stream_.readUnsigned();
+
+ // gcSpills is a subset of allGprSpills.
+ allGprSpills_ = GeneralRegisterSet(ReadRegisterMask(stream_));
+ if (allGprSpills_.empty()) {
+ gcSpills_ = allGprSpills_;
+ valueSpills_ = allGprSpills_;
+ slotsOrElementsSpills_ = allGprSpills_;
+ } else {
+ gcSpills_ = GeneralRegisterSet(ReadRegisterMask(stream_));
+ slotsOrElementsSpills_ = GeneralRegisterSet(ReadRegisterMask(stream_));
+#ifdef JS_PUNBOX64
+ valueSpills_ = GeneralRegisterSet(ReadRegisterMask(stream_));
+#endif
+ }
+ allFloatSpills_ = FloatRegisterSet(ReadFloatRegisterMask(stream_));
+
+ advanceFromGcRegs();
+}
+
+uint32_t
+SafepointReader::osiReturnPointOffset() const
+{
+ return osiCallPointOffset_ + Assembler::PatchWrite_NearCallSize();
+}
+
+CodeLocationLabel
+SafepointReader::InvalidationPatchPoint(IonScript* script, const SafepointIndex* si)
+{
+ SafepointReader reader(script, si);
+
+ return CodeLocationLabel(script->method(), CodeOffset(reader.osiCallPointOffset()));
+}
+
+void
+SafepointReader::advanceFromGcRegs()
+{
+ currentSlotChunk_ = 0;
+ nextSlotChunkNumber_ = 0;
+ currentSlotsAreStack_ = true;
+}
+
+bool
+SafepointReader::getSlotFromBitmap(SafepointSlotEntry* entry)
+{
+ while (currentSlotChunk_ == 0) {
+ // Are there any more chunks to read?
+ if (currentSlotsAreStack_) {
+ if (nextSlotChunkNumber_ == BitSet::RawLengthForBits(frameSlots_)) {
+ nextSlotChunkNumber_ = 0;
+ currentSlotsAreStack_ = false;
+ continue;
+ }
+ } else if (nextSlotChunkNumber_ == BitSet::RawLengthForBits(argumentSlots_)) {
+ return false;
+ }
+
+ // Yes, read the next chunk.
+ currentSlotChunk_ = stream_.readUnsigned();
+ nextSlotChunkNumber_++;
+ }
+
+ // The current chunk still has bits in it, so get the next bit, then mask
+ // it out of the slot chunk.
+ uint32_t bit = FloorLog2(currentSlotChunk_);
+ currentSlotChunk_ &= ~(1 << bit);
+
+ // Return the slot, and re-scale it by the pointer size, reversing the
+ // transformation in MapSlotsToBitset.
+ entry->stack = currentSlotsAreStack_;
+ entry->slot = (((nextSlotChunkNumber_ - 1) * BitSet::BitsPerWord) + bit) * sizeof(intptr_t);
+ return true;
+}
+
+bool
+SafepointReader::getGcSlot(SafepointSlotEntry* entry)
+{
+ if (getSlotFromBitmap(entry))
+ return true;
+ advanceFromGcSlots();
+ return false;
+}
+
+void
+SafepointReader::advanceFromGcSlots()
+{
+ // No, reset the counter.
+ currentSlotChunk_ = 0;
+ nextSlotChunkNumber_ = 0;
+ currentSlotsAreStack_ = true;
+}
+
+bool
+SafepointReader::getValueSlot(SafepointSlotEntry* entry)
+{
+ if (getSlotFromBitmap(entry))
+ return true;
+ advanceFromValueSlots();
+ return false;
+}
+
+void
+SafepointReader::advanceFromValueSlots()
+{
+#ifdef JS_NUNBOX32
+ nunboxSlotsRemaining_ = stream_.readUnsigned();
+#else
+ nunboxSlotsRemaining_ = 0;
+ advanceFromNunboxSlots();
+#endif
+}
+
+static inline LAllocation
+PartFromStream(CompactBufferReader& stream, NunboxPartKind kind, uint32_t info)
+{
+ if (kind == Part_Reg)
+ return LGeneralReg(Register::FromCode(info));
+
+ if (info == MAX_INFO_VALUE)
+ info = stream.readUnsigned();
+
+ if (kind == Part_Stack)
+ return LStackSlot(info);
+
+ MOZ_ASSERT(kind == Part_Arg);
+ return LArgument(info);
+}
+
+bool
+SafepointReader::getNunboxSlot(LAllocation* type, LAllocation* payload)
+{
+ if (!nunboxSlotsRemaining_--) {
+ advanceFromNunboxSlots();
+ return false;
+ }
+
+ uint16_t header = stream_.readFixedUint16_t();
+ NunboxPartKind typeKind = (NunboxPartKind)((header >> TYPE_KIND_SHIFT) & PART_KIND_MASK);
+ NunboxPartKind payloadKind = (NunboxPartKind)((header >> PAYLOAD_KIND_SHIFT) & PART_KIND_MASK);
+ uint32_t typeInfo = (header >> TYPE_INFO_SHIFT) & PART_INFO_MASK;
+ uint32_t payloadInfo = (header >> PAYLOAD_INFO_SHIFT) & PART_INFO_MASK;
+
+ *type = PartFromStream(stream_, typeKind, typeInfo);
+ *payload = PartFromStream(stream_, payloadKind, payloadInfo);
+ return true;
+}
+
+void
+SafepointReader::advanceFromNunboxSlots()
+{
+ slotsOrElementsSlotsRemaining_ = stream_.readUnsigned();
+}
+
+bool
+SafepointReader::getSlotsOrElementsSlot(SafepointSlotEntry* entry)
+{
+ if (!slotsOrElementsSlotsRemaining_--)
+ return false;
+ entry->stack = true;
+ entry->slot = stream_.readUnsigned();
+ return true;
+}