blob: b40656c53cc9ff3c36865b5c14648d4256d79b8a [file] [log] [blame]
/* -*- 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 "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 %d):", 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: %d", slots[i]);
#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: %d", slots[i]);
#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;
}