From fbc44adf924c3596629e67bc5f245d214624e6c2 Mon Sep 17 00:00:00 2001 From: Brian Hackett Date: Mon, 26 Nov 2012 12:27:05 -0500 Subject: [PATCH] Factor common register allocation code and add integrity checker to RegisterAllocator.h, add baseline StupidAllocator, bug 812945. r=jandem --- js/src/Makefile.in | 2 + js/src/ion/Ion.cpp | 38 ++- js/src/ion/Ion.h | 15 +- js/src/ion/IonSpewer.h | 2 +- js/src/ion/LIR-Common.h | 11 +- js/src/ion/LIR.cpp | 38 +++ js/src/ion/LIR.h | 148 +++++++++- js/src/ion/LinearScan.cpp | 49 +--- js/src/ion/LinearScan.h | 222 ++------------- js/src/ion/RegisterAllocator.cpp | 464 +++++++++++++++++++++++++++++++ js/src/ion/RegisterAllocator.h | 336 ++++++++++++++++++++++ js/src/ion/Safepoints.cpp | 19 +- js/src/ion/StupidAllocator.cpp | 419 ++++++++++++++++++++++++++++ js/src/ion/StupidAllocator.h | 84 ++++++ js/src/shell/js.cpp | 7 +- 15 files changed, 1582 insertions(+), 272 deletions(-) create mode 100644 js/src/ion/RegisterAllocator.cpp create mode 100644 js/src/ion/RegisterAllocator.h create mode 100644 js/src/ion/StupidAllocator.cpp create mode 100644 js/src/ion/StupidAllocator.h diff --git a/js/src/Makefile.in b/js/src/Makefile.in index ae1fa49b6975..0fa1cd658c6b 100644 --- a/js/src/Makefile.in +++ b/js/src/Makefile.in @@ -294,8 +294,10 @@ CPPSRCS += MIR.cpp \ MIRGraph.cpp \ MoveResolver.cpp \ EdgeCaseAnalysis.cpp \ + RegisterAllocator.cpp \ Snapshots.cpp \ Safepoints.cpp \ + StupidAllocator.cpp \ TypeOracle.cpp \ TypePolicy.cpp \ ValueNumbering.cpp \ diff --git a/js/src/ion/Ion.cpp b/js/src/ion/Ion.cpp index 250d5bc126f8..8530025e499c 100644 --- a/js/src/ion/Ion.cpp +++ b/js/src/ion/Ion.cpp @@ -21,6 +21,7 @@ #include "jsworkers.h" #include "IonCompartment.h" #include "CodeGenerator.h" +#include "StupidAllocator.h" #if defined(JS_CPU_X86) # include "x86/Lowering-x86.h" @@ -949,16 +950,47 @@ CompileBackEnd(MIRGenerator *mir) if (mir->shouldCancel("Generate LIR")) return NULL; - if (js_IonOptions.lsra) { + AllocationIntegrityState integrity(*lir); + + switch (js_IonOptions.registerAllocator) { + case RegisterAllocator_LSRA: { +#ifdef DEBUG + integrity.record(); +#endif + LinearScanAllocator regalloc(mir, &lirgen, *lir); if (!regalloc.go()) return NULL; - IonSpewPass("Allocate Registers", ®alloc); - if (mir->shouldCancel("Allocate Registers")) +#ifdef DEBUG + integrity.check(false); +#endif + + IonSpewPass("Allocate Registers [LSRA]", ®alloc); + break; + } + + case RegisterAllocator_Stupid: { + // Use the integrity checker to populate safepoint information, so + // run it in all builds. + integrity.record(); + + StupidAllocator regalloc(mir, &lirgen, *lir); + if (!regalloc.go()) return NULL; + if (!integrity.check(true)) + return NULL; + IonSpewPass("Allocate Registers [Stupid]"); + break; + } + + default: + JS_NOT_REACHED("Bad regalloc"); } + if (mir->shouldCancel("Allocate Registers")) + return NULL; + CodeGenerator *codegen = js_new(mir, lir); if (!codegen || !codegen->generate()) { js_delete(codegen); diff --git a/js/src/ion/Ion.h b/js/src/ion/Ion.h index 21db9dabee8e..d8bcd20effc9 100644 --- a/js/src/ion/Ion.h +++ b/js/src/ion/Ion.h @@ -19,6 +19,12 @@ namespace ion { class TempAllocator; +// Possible register allocators which may be used. +enum IonRegisterAllocator { + RegisterAllocator_LSRA, + RegisterAllocator_Stupid +}; + struct IonOptions { // Toggles whether global value numbering is used. @@ -47,11 +53,10 @@ struct IonOptions // Default: true bool limitScriptSize; - // Toggles whether Linear Scan Register Allocation is used. If LSRA is not - // used, then Greedy Register Allocation is used instead. + // Describes which register allocator to use. // - // Default: true - bool lsra; + // Default: LSRA + IonRegisterAllocator registerAllocator; // Toggles whether inlining is performed. // @@ -162,7 +167,7 @@ struct IonOptions licm(true), osr(true), limitScriptSize(true), - lsra(true), + registerAllocator(RegisterAllocator_LSRA), inlining(true), edgeCaseAnalysis(true), rangeAnalysis(true), diff --git a/js/src/ion/IonSpewer.h b/js/src/ion/IonSpewer.h index 507912a12f53..b21070a5b2b4 100644 --- a/js/src/ion/IonSpewer.h +++ b/js/src/ion/IonSpewer.h @@ -32,7 +32,7 @@ namespace ion { _(Range) \ /* Information during LICM */ \ _(LICM) \ - /* Information during LSRA */ \ + /* Information during regalloc */ \ _(RegAlloc) \ /* Information during inlining */ \ _(Inlining) \ diff --git a/js/src/ion/LIR-Common.h b/js/src/ion/LIR-Common.h index 00b91ddc2b26..62002eb56bde 100644 --- a/js/src/ion/LIR-Common.h +++ b/js/src/ion/LIR-Common.h @@ -105,10 +105,13 @@ class LMoveGroup : public LInstructionHelper<0, 0, 0> LIR_HEADER(MoveGroup); void printOperands(FILE *fp); - bool add(LAllocation *from, LAllocation *to) { - JS_ASSERT(*from != *to); - return moves_.append(LMove(from, to)); - } + + // Add a move which takes place simultaneously with all others in the group. + bool add(LAllocation *from, LAllocation *to); + + // Add a move which takes place after existing moves in the group. + bool addAfter(LAllocation *from, LAllocation *to); + size_t numMoves() const { return moves_.length(); } diff --git a/js/src/ion/LIR.cpp b/js/src/ion/LIR.cpp index ce67eb0c237f..85294396deb6 100644 --- a/js/src/ion/LIR.cpp +++ b/js/src/ion/LIR.cpp @@ -314,6 +314,44 @@ LInstruction::initSafepoint() JS_ASSERT(safepoint_); } +bool +LMoveGroup::add(LAllocation *from, LAllocation *to) +{ +#ifdef DEBUG + JS_ASSERT(*from != *to); + for (size_t i = 0; i < moves_.length(); i++) + JS_ASSERT(*to != *moves_[i].to()); +#endif + return moves_.append(LMove(from, to)); +} + +bool +LMoveGroup::addAfter(LAllocation *from, LAllocation *to) +{ + // Transform the operands to this move so that performing the result + // simultaneously with existing moves in the group will have the same + // effect as if the original move took place after the existing moves. + + for (size_t i = 0; i < moves_.length(); i++) { + if (*moves_[i].to() == *from) { + from = moves_[i].from(); + break; + } + } + + if (*from == *to) + return true; + + for (size_t i = 0; i < moves_.length(); i++) { + if (*to == *moves_[i].to()) { + moves_[i] = LMove(from, to); + return true; + } + } + + return add(from, to); +} + void LMoveGroup::printOperands(FILE *fp) { diff --git a/js/src/ion/LIR.h b/js/src/ion/LIR.h index 05bfa3007ae0..870bc299c978 100644 --- a/js/src/ion/LIR.h +++ b/js/src/ion/LIR.h @@ -194,6 +194,10 @@ class LAllocation : public TempObject return bits_ != other.bits_; } + HashNumber hash() const { + return bits_; + } + static void PrintAllocation(FILE *fp, const LAllocation *a); }; @@ -740,6 +744,9 @@ class LBlock : public TempObject LInstructionReverseIterator rbegin() { return instructions_.rbegin(); } + LInstructionReverseIterator rbegin(LInstruction *at) { + return instructions_.rbegin(at); + } LInstructionReverseIterator rend() { return instructions_.rend(); } @@ -935,6 +942,9 @@ class LSafepoint : public TempObject #ifdef JS_NUNBOX32 // List of registers which contain pieces of values. NunboxList nunboxParts_; + + // Number of nunboxParts which are not completely filled in. + uint32 partialNunboxes_; #elif JS_PUNBOX64 // List of registers which contain values. GeneralRegisterSet valueRegs_; @@ -942,17 +952,20 @@ class LSafepoint : public TempObject public: LSafepoint() - : safepointOffset_(INVALID_SAFEPOINT_OFFSET), - osiCallPointOffset_(0) + : safepointOffset_(INVALID_SAFEPOINT_OFFSET) + , osiCallPointOffset_(0) +#ifdef JS_NUNBOX32 + , partialNunboxes_(0) +#endif { } void addLiveRegister(AnyRegister reg) { - liveRegs_.add(reg); + liveRegs_.addUnchecked(reg); } const RegisterSet &liveRegs() const { return liveRegs_; } void addGcRegister(Register reg) { - gcRegs_.add(reg); + gcRegs_.addUnchecked(reg); } GeneralRegisterSet gcRegs() const { return gcRegs_; @@ -964,6 +977,27 @@ class LSafepoint : public TempObject return gcSlots_; } + void addGcPointer(LAllocation alloc) { + if (alloc.isRegister()) + addGcRegister(alloc.toRegister().gpr()); + else if (alloc.isStackSlot()) + addGcSlot(alloc.toStackSlot()->slot()); + } + + bool hasGcPointer(LAllocation alloc) { + if (alloc.isRegister()) + return gcRegs().has(alloc.toRegister().gpr()); + if (alloc.isStackSlot()) { + for (size_t i = 0; i < gcSlots_.length(); i++) { + if (gcSlots_[i] == alloc.toStackSlot()->slot()) + return true; + } + return false; + } + JS_ASSERT(alloc.isArgument()); + return true; + } + bool addValueSlot(uint32 slot) { return valueSlots_.append(slot); } @@ -971,21 +1005,125 @@ class LSafepoint : public TempObject return valueSlots_; } + bool hasValueSlot(uint32 slot) { + for (size_t i = 0; i < valueSlots_.length(); i++) { + if (valueSlots_[i] == slot) + return true; + } + return false; + } + #ifdef JS_NUNBOX32 + bool addNunboxParts(LAllocation type, LAllocation payload) { return nunboxParts_.append(NunboxEntry(type, payload)); } + + bool addNunboxType(uint32 typeVreg, LAllocation type) { + for (size_t i = 0; i < nunboxParts_.length(); i++) { + if (nunboxParts_[i].type == type) + return true; + if (nunboxParts_[i].type == LUse(LUse::ANY, typeVreg)) { + nunboxParts_[i].type = type; + partialNunboxes_--; + return true; + } + } + partialNunboxes_++; + + // vregs for nunbox pairs are adjacent, with the type coming first. + uint32 payloadVreg = typeVreg + 1; + return nunboxParts_.append(NunboxEntry(type, LUse(payloadVreg, LUse::ANY))); + } + + bool hasNunboxType(LAllocation type) { + if (type.isArgument()) + return true; + if (type.isStackSlot() && hasValueSlot(type.toStackSlot()->slot() + 1)) + return true; + for (size_t i = 0; i < nunboxParts_.length(); i++) { + if (nunboxParts_[i].type == type) + return true; + } + return false; + } + + bool addNunboxPayload(uint32 payloadVreg, LAllocation payload) { + for (size_t i = 0; i < nunboxParts_.length(); i++) { + if (nunboxParts_[i].payload == payload) + return true; + if (nunboxParts_[i].payload == LUse(LUse::ANY, payloadVreg)) { + partialNunboxes_--; + nunboxParts_[i].payload = payload; + return true; + } + } + partialNunboxes_++; + + // vregs for nunbox pairs are adjacent, with the type coming first. + uint32 typeVreg = payloadVreg - 1; + return nunboxParts_.append(NunboxEntry(LUse(typeVreg, LUse::ANY), payload)); + } + + bool hasNunboxPayload(LAllocation payload) { + if (payload.isArgument()) + return true; + if (payload.isStackSlot() && hasValueSlot(payload.toStackSlot()->slot())) + return true; + for (size_t i = 0; i < nunboxParts_.length(); i++) { + if (nunboxParts_[i].payload == payload) + return true; + } + return false; + } + NunboxList &nunboxParts() { return nunboxParts_; } + + uint32 partialNunboxes() { + return partialNunboxes_; + } + #elif JS_PUNBOX64 + void addValueRegister(Register reg) { valueRegs_.add(reg); } GeneralRegisterSet valueRegs() { return valueRegs_; } -#endif + + bool addBoxedValue(LAllocation alloc) { + if (alloc.isRegister()) { + Register reg = alloc.toRegister().gpr(); + if (!valueRegs().has(reg)) + addValueRegister(reg); + return true; + } + if (alloc.isStackSlot()) { + uint32 slot = alloc.toStackSlot()->slot(); + for (size_t i = 0; i < valueSlots().length(); i++) { + if (valueSlots()[i] == slot) + return true; + } + return addValueSlot(slot); + } + JS_ASSERT(alloc.isArgument()); + return true; + } + + bool hasBoxedValue(LAllocation alloc) { + if (alloc.isRegister()) + return valueRegs().has(alloc.toRegister().gpr()); + if (alloc.isStackSlot()) + return hasValueSlot(alloc.toStackSlot()->slot()); + JS_ASSERT(alloc.isArgument()); + return true; + } + +#endif // JS_PUNBOX64 + bool encoded() const { return safepointOffset_ != INVALID_SAFEPOINT_OFFSET; } diff --git a/js/src/ion/LinearScan.cpp b/js/src/ion/LinearScan.cpp index 14b431536716..e31393525ecb 100644 --- a/js/src/ion/LinearScan.cpp +++ b/js/src/ion/LinearScan.cpp @@ -360,9 +360,6 @@ LiveInterval::firstIncompatibleUse(LAllocation alloc) return CodePosition::MAX; } -const CodePosition CodePosition::MAX(UINT_MAX); -const CodePosition CodePosition::MIN(0); - /* * This function pre-allocates and initializes as much global state as possible * to avoid littering the algorithms with memory management cruft. @@ -370,6 +367,9 @@ const CodePosition CodePosition::MIN(0); bool LinearScanAllocator::createDataStructures() { + if (!RegisterAllocator::init()) + return false; + liveIn = lir->mir()->allocate(graph.numBlockIds()); if (!liveIn) return false; @@ -387,9 +387,6 @@ LinearScanAllocator::createDataStructures() if (!vregs.init(lir->mir(), graph.numVirtualRegisters())) return false; - if (!insData.init(lir->mir(), graph.numInstructions())) - return false; - // Build virtual register objects for (size_t i = 0; i < graph.numBlocks(); i++) { if (mir->shouldCancel("LSRA create data structures (main loop)")) @@ -405,7 +402,6 @@ LinearScanAllocator::createDataStructures() return false; } } - insData[*ins].init(*ins, block); for (size_t j = 0; j < ins->numTemps(); j++) { LDefinition *def = ins->getTemp(j); @@ -420,7 +416,6 @@ LinearScanAllocator::createDataStructures() LDefinition *def = phi->getDef(0); if (!vregs[def].init(phi->id(), block, phi, def, /* isTemp */ false)) return false; - insData[phi].init(phi, block); } } @@ -1870,44 +1865,6 @@ LinearScanAllocator::canCoexist(LiveInterval *a, LiveInterval *b) return true; } -LMoveGroup * -LinearScanAllocator::getInputMoveGroup(CodePosition pos) -{ - InstructionData *data = &insData[pos]; - JS_ASSERT(!data->ins()->isPhi()); - JS_ASSERT(!data->ins()->isLabel());; - JS_ASSERT(inputOf(data->ins()) == pos); - - if (data->inputMoves()) - return data->inputMoves(); - - LMoveGroup *moves = new LMoveGroup; - data->setInputMoves(moves); - data->block()->insertBefore(data->ins(), moves); - - return moves; -} - -LMoveGroup * -LinearScanAllocator::getMoveGroupAfter(CodePosition pos) -{ - InstructionData *data = &insData[pos]; - JS_ASSERT(!data->ins()->isPhi()); - JS_ASSERT(outputOf(data->ins()) == pos); - - if (data->movesAfter()) - return data->movesAfter(); - - LMoveGroup *moves = new LMoveGroup; - data->setMovesAfter(moves); - - if (data->ins()->isLabel()) - data->block()->insertAfter(data->block()->getEntryMoveGroup(), moves); - else - data->block()->insertAfter(data->ins(), moves); - return moves; -} - bool LinearScanAllocator::addMove(LMoveGroup *moves, LiveInterval *from, LiveInterval *to) { diff --git a/js/src/ion/LinearScan.h b/js/src/ion/LinearScan.h index bb5ac6922a13..e26b88666ddc 100644 --- a/js/src/ion/LinearScan.h +++ b/js/src/ion/LinearScan.h @@ -5,15 +5,10 @@ * 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 js_ion_registerallocator_h__ -#define js_ion_registerallocator_h__ +#ifndef js_ion_linearscan_h__ +#define js_ion_linearscan_h__ -#include "Ion.h" -#include "MIR.h" -#include "MIRGraph.h" -#include "InlineList.h" -#include "LIR.h" -#include "Lowering.h" +#include "RegisterAllocator.h" #include "BitSet.h" #include "StackSlotAllocator.h" @@ -24,109 +19,6 @@ namespace ion { class VirtualRegister; -/* - * Represents with better-than-instruction precision a position in the - * instruction stream. - * - * An issue comes up when dealing with live intervals as to how to represent - * information such as "this register is only needed for the input of - * this instruction, it can be clobbered in the output". Just having ranges - * of instruction IDs is insufficiently expressive to denote all possibilities. - * This class solves this issue by associating an extra bit with the instruction - * ID which indicates whether the position is the input half or output half of - * an instruction. - */ -class CodePosition -{ - private: - CodePosition(const uint32 &bits) - : bits_(bits) - { } - - static const unsigned int INSTRUCTION_SHIFT = 1; - static const unsigned int SUBPOSITION_MASK = 1; - uint32 bits_; - - public: - static const CodePosition MAX; - static const CodePosition MIN; - - /* - * This is the half of the instruction this code position represents, as - * described in the huge comment above. - */ - enum SubPosition { - INPUT, - OUTPUT - }; - - CodePosition() : bits_(0) - { } - - CodePosition(uint32 instruction, SubPosition where) { - JS_ASSERT(instruction < 0x80000000u); - JS_ASSERT(((uint32)where & SUBPOSITION_MASK) == (uint32)where); - bits_ = (instruction << INSTRUCTION_SHIFT) | (uint32)where; - } - - uint32 ins() const { - return bits_ >> INSTRUCTION_SHIFT; - } - - uint32 pos() const { - return bits_; - } - - SubPosition subpos() const { - return (SubPosition)(bits_ & SUBPOSITION_MASK); - } - - bool operator <(const CodePosition &other) const { - return bits_ < other.bits_; - } - - bool operator <=(const CodePosition &other) const { - return bits_ <= other.bits_; - } - - bool operator !=(const CodePosition &other) const { - return bits_ != other.bits_; - } - - bool operator ==(const CodePosition &other) const { - return bits_ == other.bits_; - } - - bool operator >(const CodePosition &other) const { - return bits_ > other.bits_; - } - - bool operator >=(const CodePosition &other) const { - return bits_ >= other.bits_; - } - - CodePosition previous() const { - JS_ASSERT(*this != MIN); - return CodePosition(bits_ - 1); - } - CodePosition next() const { - JS_ASSERT(*this != MAX); - return CodePosition(bits_ + 1); - } -}; - -struct UsePosition : public TempObject, - public InlineForwardListNode -{ - LUse *use; - CodePosition pos; - - UsePosition(LUse *use, CodePosition pos) : - use(use), - pos(pos) - { } -}; - class Requirement { public: @@ -198,6 +90,18 @@ class Requirement CodePosition position_; }; +struct UsePosition : public TempObject, + public InlineForwardListNode +{ + LUse *use; + CodePosition pos; + + UsePosition(LUse *use, CodePosition pos) : + use(use), + pos(pos) + { } +}; + typedef InlineForwardListIterator UsePositionIterator; /* @@ -494,70 +398,6 @@ class VirtualRegisterMap } }; -class InstructionData -{ - LInstruction *ins_; - LBlock *block_; - LMoveGroup *inputMoves_; - LMoveGroup *movesAfter_; - - public: - void init(LInstruction *ins, LBlock *block) { - JS_ASSERT(!ins_); - JS_ASSERT(!block_); - ins_ = ins; - block_ = block; - } - LInstruction *ins() const { - return ins_; - } - LBlock *block() const { - return block_; - } - void setInputMoves(LMoveGroup *moves) { - inputMoves_ = moves; - } - LMoveGroup *inputMoves() const { - return inputMoves_; - } - void setMovesAfter(LMoveGroup *moves) { - movesAfter_ = moves; - } - LMoveGroup *movesAfter() const { - return movesAfter_; - } -}; - -class InstructionDataMap -{ - InstructionData *insData_; - uint32 numIns_; - - public: - InstructionDataMap() - : insData_(NULL), - numIns_(0) - { } - - bool init(MIRGenerator *gen, uint32 numInstructions) { - insData_ = gen->allocate(numInstructions); - numIns_ = numInstructions; - if (!insData_) - return false; - memset(insData_, 0, sizeof(InstructionData) * numInstructions); - return true; - } - - InstructionData &operator[](const CodePosition &pos) { - JS_ASSERT(pos.ins() < numIns_); - return insData_[pos.ins()]; - } - InstructionData &operator[](LInstruction *ins) { - JS_ASSERT(ins->id() < numIns_); - return insData_[ins->id()]; - } -}; - typedef HashMap, @@ -571,7 +411,7 @@ typedef HashMap::iterator IntervalIterator; typedef InlineList::reverse_iterator IntervalReverseIterator; -class LinearScanAllocator +class LinearScanAllocator : public RegisterAllocator { friend class C1Spewer; friend class JSONSpewer; @@ -590,15 +430,9 @@ class LinearScanAllocator LiveInterval *dequeue(); }; - // Context - MIRGenerator *mir; - LIRGenerator *lir; - LIRGraph &graph; - // Computed inforamtion BitSet **liveIn; VirtualRegisterMap vregs; - InstructionDataMap insData; FixedArityList fixedIntervals; // Union of all ranges in fixedIntervals, used to quickly determine @@ -612,9 +446,6 @@ class LinearScanAllocator SlotList finishedSlots_; SlotList finishedDoubleSlots_; - // Pool of all registers that should be considered allocateable - RegisterSet allRegisters_; - // Run-time state UnhandledQueue unhandled; InlineList active; @@ -643,8 +474,6 @@ class LinearScanAllocator AnyRegister::Code findBestFreeRegister(CodePosition *freeUntil); AnyRegister::Code findBestBlockedRegister(CodePosition *nextUsed); bool canCoexist(LiveInterval *a, LiveInterval *b); - LMoveGroup *getInputMoveGroup(CodePosition pos); - LMoveGroup *getMoveGroupAfter(CodePosition pos); bool addMove(LMoveGroup *moves, LiveInterval *from, LiveInterval *to); bool moveInput(CodePosition pos, LiveInterval *from, LiveInterval *to); bool moveInputAlloc(CodePosition pos, LAllocation *from, LAllocation *to); @@ -669,31 +498,14 @@ class LinearScanAllocator inline void validateVirtualRegisters() { }; #endif - CodePosition outputOf(uint32 pos) { - return CodePosition(pos, CodePosition::OUTPUT); - } - CodePosition outputOf(LInstruction *ins) { - return CodePosition(ins->id(), CodePosition::OUTPUT); - } - CodePosition inputOf(uint32 pos) { - return CodePosition(pos, CodePosition::INPUT); - } - CodePosition inputOf(LInstruction *ins) { - return CodePosition(ins->id(), CodePosition::INPUT); - } #ifdef JS_NUNBOX32 VirtualRegister *otherHalfOfNunbox(VirtualRegister *vreg); #endif public: LinearScanAllocator(MIRGenerator *mir, LIRGenerator *lir, LIRGraph &graph) - : mir(mir), - lir(lir), - graph(graph), - allRegisters_(RegisterSet::All()) + : RegisterAllocator(mir, lir, graph) { - if (FramePointer != InvalidReg && lir->mir()->instrumentedProfiling()) - allRegisters_.take(AnyRegister(FramePointer)); } bool go(); diff --git a/js/src/ion/RegisterAllocator.cpp b/js/src/ion/RegisterAllocator.cpp new file mode 100644 index 000000000000..8be440033c98 --- /dev/null +++ b/js/src/ion/RegisterAllocator.cpp @@ -0,0 +1,464 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=4 sw=4 et 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 "RegisterAllocator.h" + +using namespace js; +using namespace js::ion; + +bool +AllocationIntegrityState::record() +{ + // Ignore repeated record() calls. + if (!instructions.empty()) + return true; + + if (!instructions.reserve(graph.numInstructions())) + return false; + for (size_t i = 0; i < graph.numInstructions(); i++) + instructions.infallibleAppend(InstructionInfo()); + + if (!virtualRegisters.reserve(graph.numVirtualRegisters())) + return false; + for (size_t i = 0; i < graph.numVirtualRegisters(); i++) + virtualRegisters.infallibleAppend(NULL); + + if (!blocks.reserve(graph.numBlocks())) + return false; + for (size_t i = 0; i < graph.numBlocks(); i++) { + blocks.infallibleAppend(BlockInfo()); + LBlock *block = graph.getBlock(i); + JS_ASSERT(block->mir()->id() == i); + + BlockInfo &blockInfo = blocks[i]; + if (!blockInfo.phis.reserve(block->numPhis())) + return false; + + for (size_t j = 0; j < block->numPhis(); j++) { + blockInfo.phis.infallibleAppend(InstructionInfo()); + InstructionInfo &info = blockInfo.phis[j]; + LPhi *phi = block->getPhi(j); + for (size_t k = 0; k < phi->numDefs(); k++) { + uint32 vreg = phi->getDef(k)->virtualRegister(); + virtualRegisters[vreg] = phi->getDef(k); + if (!info.outputs.append(vreg)) + return false; + } + for (size_t k = 0; k < phi->numOperands(); k++) { + if (!info.inputs.append(phi->getOperand(k)->toUse()->virtualRegister())) + return false; + } + } + + for (LInstructionIterator iter = block->begin(); iter != block->end(); iter++) { + LInstruction *ins = *iter; + InstructionInfo &info = instructions[ins->id()]; + + for (size_t k = 0; k < ins->numDefs(); k++) { + uint32 vreg = ins->getDef(k)->virtualRegister(); + virtualRegisters[vreg] = ins->getDef(k); + if (!info.outputs.append(vreg)) + return false; + } + for (LInstruction::InputIterator alloc(*ins); alloc.more(); alloc.next()) { + if (!info.inputs.append(alloc->isUse() ? alloc->toUse()->virtualRegister() : UINT32_MAX)) + return false; + } + } + } + + return seen.init(); +} + +bool +AllocationIntegrityState::check(bool populateSafepoints) +{ + JS_ASSERT(!instructions.empty()); + +#ifdef DEBUG + for (size_t blockIndex = 0; blockIndex < graph.numBlocks(); blockIndex++) { + LBlock *block = graph.getBlock(blockIndex); + + // Check that all instruction inputs and outputs have been assigned an allocation. + for (LInstructionIterator iter = block->begin(); iter != block->end(); iter++) { + LInstruction *ins = *iter; + + for (LInstruction::InputIterator alloc(*ins); alloc.more(); alloc.next()) + JS_ASSERT(!alloc->isUse()); + + for (size_t i = 0; i < ins->numDefs(); i++) { + LDefinition *def = ins->getDef(i); + JS_ASSERT_IF(def->policy() != LDefinition::PASSTHROUGH, !def->output()->isUse()); + + if (def->output()->isRegister()) { + // The live regs for an instruction's safepoint should + // exclude the instruction's definitions. + LSafepoint *safepoint = ins->safepoint(); + JS_ASSERT_IF(safepoint, !safepoint->liveRegs().has(def->output()->toRegister())); + } + } + } + } +#endif + + // Check that the register assignment and move groups preserve the original + // semantics of the virtual registers. Each virtual register has a single + // write (owing to the SSA representation), but the allocation may move the + // written value around between registers and memory locations along + // different paths through the script. + // + // For each use of an allocation, follow the physical value which is read + // backward through the script, along all paths to the value's virtual + // register's definition. + for (size_t blockIndex = 0; blockIndex < graph.numBlocks(); blockIndex++) { + LBlock *block = graph.getBlock(blockIndex); + for (LInstructionIterator iter = block->begin(); iter != block->end(); iter++) { + LInstruction *ins = *iter; + const InstructionInfo &info = instructions[ins->id()]; + + size_t inputIndex = 0; + for (LInstruction::InputIterator alloc(*ins); alloc.more(); alloc.next()) { + uint32 vreg = info.inputs[inputIndex++]; + if (vreg == UINT32_MAX) + continue; + + // Start checking at the previous instruction, in case this + // instruction reuses its input register for an output. + LInstructionReverseIterator riter = block->rbegin(ins); + riter++; + checkIntegrity(block, *riter, vreg, **alloc, populateSafepoints); + + while (!worklist.empty()) { + IntegrityItem item = worklist.back(); + worklist.popBack(); + checkIntegrity(item.block, *item.block->rbegin(), item.vreg, item.alloc, populateSafepoints); + } + } + } + } + + if (IonSpewEnabled(IonSpew_RegAlloc)) + dump(); + + return true; +} + +bool +AllocationIntegrityState::checkIntegrity(LBlock *block, LInstruction *ins, + uint32 vreg, LAllocation alloc, bool populateSafepoints) +{ + for (LInstructionReverseIterator iter(block->rbegin(ins)); iter != block->rend(); iter++) { + ins = *iter; + + // Follow values through assignments in move groups. All assignments in + // a move group are considered to happen simultaneously, so stop after + // the first matching move is found. + if (ins->isMoveGroup()) { + LMoveGroup *group = ins->toMoveGroup(); + for (int i = group->numMoves() - 1; i >= 0; i--) { + if (*group->getMove(i).to() == alloc) { + alloc = *group->getMove(i).from(); + break; + } + } + } + + const InstructionInfo &info = instructions[ins->id()]; + + // Make sure the physical location being tracked is not clobbered by + // another instruction, and that if the originating vreg definition is + // found that it is writing to the tracked location. + + for (size_t i = 0; i < ins->numDefs(); i++) { + LDefinition *def = ins->getDef(i); + if (def->policy() == LDefinition::PASSTHROUGH) + continue; + if (info.outputs[i] == vreg) { + check(*def->output() == alloc, + "Found vreg definition, but tracked value does not match"); + + // Found the original definition, done scanning. + return true; + } else { + check(*def->output() != alloc, + "Tracked value clobbered by intermediate definition"); + } + } + + for (size_t i = 0; i < ins->numTemps(); i++) { + LDefinition *temp = ins->getTemp(i); + if (!temp->isBogusTemp()) { + check(*temp->output() != alloc, + "Tracked value clobbered by intermediate temporary"); + } + } + + LSafepoint *safepoint = ins->safepoint(); + if (!safepoint) + continue; + + if (alloc.isRegister()) { + AnyRegister reg = alloc.toRegister(); + if (populateSafepoints) + safepoint->addLiveRegister(reg); + else + check(safepoint->liveRegs().has(reg), "Register not marked in safepoint"); + } + + LDefinition::Type type = virtualRegisters[vreg] + ? virtualRegisters[vreg]->type() + : LDefinition::GENERAL; + + switch (type) { + case LDefinition::OBJECT: + if (populateSafepoints) + safepoint->addGcPointer(alloc); + else + check(safepoint->hasGcPointer(alloc), "GC register not marked in safepoint"); + break; +#ifdef JS_NUNBOX32 + // If a vreg for a value's components are copied in multiple places + // then the safepoint information may be incomplete and not reflect + // all copies. See SafepointWriter::writeNunboxParts. + case LDefinition::TYPE: + if (populateSafepoints) + safepoint->addNunboxType(vreg, alloc); + break; + case LDefinition::PAYLOAD: + if (populateSafepoints) + safepoint->addNunboxPayload(vreg, alloc); + break; +#else + case LDefinition::BOX: + if (populateSafepoints) + safepoint->addBoxedValue(alloc); + else + check(safepoint->hasBoxedValue(alloc), "Boxed value not marked in safepoint"); + break; +#endif + default: + break; + } + } + + // Phis are effectless, but change the vreg we are tracking. Check if there + // is one which produced this vreg. We need to follow back through the phi + // inputs as it is not guaranteed the register allocator filled in physical + // allocations for the inputs and outputs of the phis. + for (size_t i = 0; i < block->numPhis(); i++) { + InstructionInfo &info = blocks[block->mir()->id()].phis[i]; + LPhi *phi = block->getPhi(i); + if (info.outputs[0] == vreg) { + for (size_t j = 0; j < phi->numOperands(); j++) { + uint32 newvreg = info.inputs[j]; + LBlock *predecessor = graph.getBlock(block->mir()->getPredecessor(j)->id()); + if (!addPredecessor(predecessor, newvreg, alloc)) + return false; + } + return true; + } + } + + // No phi which defined the vreg we are tracking, follow back through all + // predecessors with the existing vreg. + for (size_t i = 0; i < block->mir()->numPredecessors(); i++) { + LBlock *predecessor = graph.getBlock(block->mir()->getPredecessor(i)->id()); + if (!addPredecessor(predecessor, vreg, alloc)) + return false; + } + + return true; +} + +bool +AllocationIntegrityState::addPredecessor(LBlock *block, uint32 vreg, LAllocation alloc) +{ + // There is no need to reanalyze if we have already seen this predecessor. + // We share the seen allocations across analysis of each use, as there will + // likely be common ground between different uses of the same vreg. + IntegrityItem item; + item.block = block; + item.vreg = vreg; + item.alloc = alloc; + item.index = seen.count(); + + IntegrityItemSet::AddPtr p = seen.lookupForAdd(item); + if (p) + return true; + if (!seen.add(p, item)) + return false; + + return worklist.append(item); +} + +void +AllocationIntegrityState::check(bool cond, const char *msg) +{ + if (!cond) { + if (IonSpewEnabled(IonSpew_RegAlloc)) + dump(); + printf("%s\n", msg); + JS_NOT_REACHED("Regalloc integrity failure"); + } +} + +void +AllocationIntegrityState::dump() +{ +#ifdef DEBUG + printf("Register Allocation:\n"); + + for (size_t blockIndex = 0; blockIndex < graph.numBlocks(); blockIndex++) { + LBlock *block = graph.getBlock(blockIndex); + MBasicBlock *mir = block->mir(); + + printf("\nBlock %lu", blockIndex); + for (size_t i = 0; i < mir->numSuccessors(); i++) + printf(" [successor %u]", mir->getSuccessor(i)->id()); + printf("\n"); + + for (size_t i = 0; i < block->numPhis(); i++) { + InstructionInfo &info = blocks[blockIndex].phis[i]; + LPhi *phi = block->getPhi(i); + + printf("Phi v%u <-", info.outputs[0]); + for (size_t j = 0; j < phi->numOperands(); j++) + printf(" v%u", info.inputs[j]); + printf("\n"); + } + + for (LInstructionIterator iter = block->begin(); iter != block->end(); iter++) { + LInstruction *ins = *iter; + InstructionInfo &info = instructions[ins->id()]; + + printf("[%s]", ins->opName()); + + if (ins->isMoveGroup()) { + LMoveGroup *group = ins->toMoveGroup(); + for (int i = group->numMoves() - 1; i >= 0; i--) { + printf(" ["); + LAllocation::PrintAllocation(stdout, group->getMove(i).from()); + printf(" -> "); + LAllocation::PrintAllocation(stdout, group->getMove(i).to()); + printf("]"); + } + printf("\n"); + continue; + } + + for (size_t i = 0; i < ins->numTemps(); i++) { + LDefinition *temp = ins->getTemp(i); + if (!temp->isBogusTemp()) { + printf(" [temp "); + LAllocation::PrintAllocation(stdout, temp->output()); + printf("]"); + } + } + + for (size_t i = 0; i < ins->numDefs(); i++) { + LDefinition *def = ins->getDef(i); + printf(" [def v%u ", info.outputs[i]); + LAllocation::PrintAllocation(stdout, def->output()); + printf("]"); + } + + size_t index = 0; + for (LInstruction::InputIterator alloc(*ins); alloc.more(); alloc.next()) { + uint32 vreg = info.inputs[index++]; + if (vreg == UINT32_MAX) + continue; + printf(" [use v%u ", vreg); + LAllocation::PrintAllocation(stdout, *alloc); + printf("]"); + } + + printf("\n"); + } + } + + printf("\nIntermediate Allocations:\n\n"); + + // Print discovered allocations at the ends of blocks, in the order they + // were discovered. + + Vector seenOrdered; + for (size_t i = 0; i < seen.count(); i++) + seenOrdered.append(IntegrityItem()); + + for (IntegrityItemSet::Enum iter(seen); !iter.empty(); iter.popFront()) { + IntegrityItem item = iter.front(); + seenOrdered[item.index] = item; + } + + for (size_t i = 0; i < seenOrdered.length(); i++) { + IntegrityItem item = seenOrdered[i]; + printf("block %u reg v%u alloc ", item.block->mir()->id(), item.vreg); + LAllocation::PrintAllocation(stdout, &item.alloc); + printf("\n"); + } + + printf("\n"); +#endif +} + +const CodePosition CodePosition::MAX(UINT_MAX); +const CodePosition CodePosition::MIN(0); + +bool +RegisterAllocator::init() +{ + if (!insData.init(lir->mir(), graph.numInstructions())) + return false; + + for (size_t i = 0; i < graph.numBlocks(); i++) { + LBlock *block = graph.getBlock(i); + for (LInstructionIterator ins = block->begin(); ins != block->end(); ins++) + insData[*ins].init(*ins, block); + for (size_t j = 0; j < block->numPhis(); j++) { + LPhi *phi = block->getPhi(j); + insData[phi].init(phi, block); + } + } + + return true; +} + +LMoveGroup * +RegisterAllocator::getInputMoveGroup(uint32 ins) +{ + InstructionData *data = &insData[ins]; + JS_ASSERT(!data->ins()->isPhi()); + JS_ASSERT(!data->ins()->isLabel()); + + if (data->inputMoves()) + return data->inputMoves(); + + LMoveGroup *moves = new LMoveGroup; + data->setInputMoves(moves); + data->block()->insertBefore(data->ins(), moves); + + return moves; +} + +LMoveGroup * +RegisterAllocator::getMoveGroupAfter(uint32 ins) +{ + InstructionData *data = &insData[ins]; + JS_ASSERT(!data->ins()->isPhi()); + + if (data->movesAfter()) + return data->movesAfter(); + + LMoveGroup *moves = new LMoveGroup; + data->setMovesAfter(moves); + + if (data->ins()->isLabel()) + data->block()->insertAfter(data->block()->getEntryMoveGroup(), moves); + else + data->block()->insertAfter(data->ins(), moves); + return moves; +} diff --git a/js/src/ion/RegisterAllocator.h b/js/src/ion/RegisterAllocator.h new file mode 100644 index 000000000000..bf82c0734e4b --- /dev/null +++ b/js/src/ion/RegisterAllocator.h @@ -0,0 +1,336 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=4 sw=4 et 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/. */ + +#ifndef js_ion_registerallocator_h__ +#define js_ion_registerallocator_h__ + +#include "Ion.h" +#include "MIR.h" +#include "MIRGraph.h" +#include "InlineList.h" +#include "LIR.h" +#include "Lowering.h" + +// Generic structures and functions for use by register allocators. + +namespace js { +namespace ion { + +// Structure for running a liveness analysis on a finished register allocation. +// This analysis can be used for two purposes: +// +// - Check the integrity of the allocation, i.e. that the reads and writes of +// physical values preserve the semantics of the original virtual registers. +// +// - Populate safepoints with live registers, GC thing and value data, to +// streamline the process of prototyping new allocators. +struct AllocationIntegrityState +{ + AllocationIntegrityState(LIRGraph &graph) + : graph(graph) + {} + + // Record all virtual registers in the graph. This must be called before + // register allocation, to pick up the original LUses. + bool record(); + + // Perform the liveness analysis on the graph, and assert on an invalid + // allocation. This must be called after register allocation, to pick up + // all assigned physical values. If populateSafepoints is specified, + // safepoints will be filled in with liveness information. + bool check(bool populateSafepoints); + + private: + + LIRGraph &graph; + + // For all instructions and phis in the graph, keep track of the virtual + // registers for all inputs and outputs of the nodes. These are overwritten + // in place during register allocation. This information is kept on the + // side rather than in the instructions and phis themselves to avoid + // debug-builds-only bloat in the size of the involved structures. + + struct InstructionInfo { + Vector inputs; + Vector outputs; + InstructionInfo() {} + InstructionInfo(const InstructionInfo &o) { + for (size_t i = 0; i < o.inputs.length(); i++) + inputs.append(o.inputs[i]); + for (size_t i = 0; i < o.outputs.length(); i++) + outputs.append(o.outputs[i]); + } + }; + Vector instructions; + + struct BlockInfo { + Vector phis; + BlockInfo() {} + BlockInfo(const BlockInfo &o) { + for (size_t i = 0; i < o.phis.length(); i++) + phis.append(o.phis[i]); + } + }; + Vector blocks; + + Vector virtualRegisters; + + // Describes a correspondence that should hold at the end of a block. + // The value which was written to vreg in the original LIR should be + // physically stored in alloc after the register allocation. + struct IntegrityItem + { + LBlock *block; + uint32 vreg; + LAllocation alloc; + + // Order of insertion into seen, for sorting. + uint32 index; + + typedef IntegrityItem Lookup; + static HashNumber hash(const IntegrityItem &item) { + HashNumber hash = item.alloc.hash(); + hash = JS_ROTATE_LEFT32(hash, 4) ^ item.vreg; + hash = JS_ROTATE_LEFT32(hash, 4) ^ HashNumber(item.block->mir()->id()); + return hash; + } + static bool match(const IntegrityItem &one, const IntegrityItem &two) { + return one.block == two.block + && one.vreg == two.vreg + && one.alloc == two.alloc; + } + }; + + // Items still to be processed. + Vector worklist; + + // Set of all items that have already been processed. + typedef HashSet IntegrityItemSet; + IntegrityItemSet seen; + + bool checkIntegrity(LBlock *block, LInstruction *ins, uint32 vreg, LAllocation alloc, + bool populateSafepoints); + bool addPredecessor(LBlock *block, uint32 vreg, LAllocation alloc); + + void check(bool cond, const char *msg); + void dump(); +}; + +// Represents with better-than-instruction precision a position in the +// instruction stream. +// +// An issue comes up when performing register allocation as to how to represent +// information such as "this register is only needed for the input of +// this instruction, it can be clobbered in the output". Just having ranges +// of instruction IDs is insufficiently expressive to denote all possibilities. +// This class solves this issue by associating an extra bit with the instruction +// ID which indicates whether the position is the input half or output half of +// an instruction. +class CodePosition +{ + private: + CodePosition(const uint32 &bits) + : bits_(bits) + { } + + static const unsigned int INSTRUCTION_SHIFT = 1; + static const unsigned int SUBPOSITION_MASK = 1; + uint32 bits_; + + public: + static const CodePosition MAX; + static const CodePosition MIN; + + // This is the half of the instruction this code position represents, as + // described in the huge comment above. + enum SubPosition { + INPUT, + OUTPUT + }; + + CodePosition() : bits_(0) + { } + + CodePosition(uint32 instruction, SubPosition where) { + JS_ASSERT(instruction < 0x80000000u); + JS_ASSERT(((uint32)where & SUBPOSITION_MASK) == (uint32)where); + bits_ = (instruction << INSTRUCTION_SHIFT) | (uint32)where; + } + + uint32 ins() const { + return bits_ >> INSTRUCTION_SHIFT; + } + + uint32 pos() const { + return bits_; + } + + SubPosition subpos() const { + return (SubPosition)(bits_ & SUBPOSITION_MASK); + } + + bool operator <(const CodePosition &other) const { + return bits_ < other.bits_; + } + + bool operator <=(const CodePosition &other) const { + return bits_ <= other.bits_; + } + + bool operator !=(const CodePosition &other) const { + return bits_ != other.bits_; + } + + bool operator ==(const CodePosition &other) const { + return bits_ == other.bits_; + } + + bool operator >(const CodePosition &other) const { + return bits_ > other.bits_; + } + + bool operator >=(const CodePosition &other) const { + return bits_ >= other.bits_; + } + + CodePosition previous() const { + JS_ASSERT(*this != MIN); + return CodePosition(bits_ - 1); + } + CodePosition next() const { + JS_ASSERT(*this != MAX); + return CodePosition(bits_ + 1); + } +}; + +// Structure to track moves inserted before or after an instruction. +class InstructionData +{ + LInstruction *ins_; + LBlock *block_; + LMoveGroup *inputMoves_; + LMoveGroup *movesAfter_; + + public: + void init(LInstruction *ins, LBlock *block) { + JS_ASSERT(!ins_); + JS_ASSERT(!block_); + ins_ = ins; + block_ = block; + } + LInstruction *ins() const { + return ins_; + } + LBlock *block() const { + return block_; + } + void setInputMoves(LMoveGroup *moves) { + inputMoves_ = moves; + } + LMoveGroup *inputMoves() const { + return inputMoves_; + } + void setMovesAfter(LMoveGroup *moves) { + movesAfter_ = moves; + } + LMoveGroup *movesAfter() const { + return movesAfter_; + } +}; + +// Structure to track all moves inserted next to instructions in a graph. +class InstructionDataMap +{ + InstructionData *insData_; + uint32 numIns_; + + public: + InstructionDataMap() + : insData_(NULL), + numIns_(0) + { } + + bool init(MIRGenerator *gen, uint32 numInstructions) { + insData_ = gen->allocate(numInstructions); + numIns_ = numInstructions; + if (!insData_) + return false; + memset(insData_, 0, sizeof(InstructionData) * numInstructions); + return true; + } + + InstructionData &operator[](const CodePosition &pos) { + JS_ASSERT(pos.ins() < numIns_); + return insData_[pos.ins()]; + } + InstructionData &operator[](LInstruction *ins) { + JS_ASSERT(ins->id() < numIns_); + return insData_[ins->id()]; + } + InstructionData &operator[](uint32 ins) { + JS_ASSERT(ins < numIns_); + return insData_[ins]; + } +}; + +// Common superclass for register allocators. +class RegisterAllocator +{ + protected: + // Context + MIRGenerator *mir; + LIRGenerator *lir; + LIRGraph &graph; + + // Pool of all registers that should be considered allocateable + RegisterSet allRegisters_; + + // Computed data + InstructionDataMap insData; + + public: + RegisterAllocator(MIRGenerator *mir, LIRGenerator *lir, LIRGraph &graph) + : mir(mir), + lir(lir), + graph(graph), + allRegisters_(RegisterSet::All()) + { + if (FramePointer != InvalidReg && lir->mir()->instrumentedProfiling()) + allRegisters_.take(AnyRegister(FramePointer)); + } + + protected: + bool init(); + + CodePosition outputOf(uint32 pos) { + return CodePosition(pos, CodePosition::OUTPUT); + } + CodePosition outputOf(LInstruction *ins) { + return CodePosition(ins->id(), CodePosition::OUTPUT); + } + CodePosition inputOf(uint32 pos) { + return CodePosition(pos, CodePosition::INPUT); + } + CodePosition inputOf(LInstruction *ins) { + return CodePosition(ins->id(), CodePosition::INPUT); + } + + LMoveGroup *getInputMoveGroup(uint32 ins); + LMoveGroup *getMoveGroupAfter(uint32 ins); + + LMoveGroup *getInputMoveGroup(CodePosition pos) { + return getInputMoveGroup(pos.ins()); + } + LMoveGroup *getMoveGroupAfter(CodePosition pos) { + return getMoveGroupAfter(pos.ins()); + } +}; + +} // namespace ion +} // namespace js + +#endif diff --git a/js/src/ion/Safepoints.cpp b/js/src/ion/Safepoints.cpp index d148cf2cfa55..0ac250ccefd5 100644 --- a/js/src/ion/Safepoints.cpp +++ b/js/src/ion/Safepoints.cpp @@ -226,11 +226,26 @@ SafepointWriter::writeNunboxParts(LSafepoint *safepoint) } # endif - stream_.writeUnsigned(entries.length()); + // 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. + // + // Note that partial entries typically appear when one part of a nunbox is + // stored in multiple places, in which case we will end up with incomplete + // information about all the places the value is stored. This will need to + // be fixed when the GC is permitted to move structures. + uint32 partials = safepoint->partialNunboxes(); + + stream_.writeUnsigned(entries.length() - partials); for (size_t i = 0; i < entries.length(); i++) { SafepointNunboxEntry &entry = entries[i]; + if (entry.type.isUse() || entry.payload.isUse()) { + partials--; + continue; + } + uint16 header = 0; header |= (AllocationToPartKind(entry.type) << TYPE_KIND_SHIFT); @@ -256,6 +271,8 @@ SafepointWriter::writeNunboxParts(LSafepoint *safepoint) if (payloadExtra) stream_.writeUnsigned(payloadVal); } + + JS_ASSERT(partials == 0); } #endif diff --git a/js/src/ion/StupidAllocator.cpp b/js/src/ion/StupidAllocator.cpp new file mode 100644 index 000000000000..9a83164000cc --- /dev/null +++ b/js/src/ion/StupidAllocator.cpp @@ -0,0 +1,419 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=4 sw=4 et 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 "StupidAllocator.h" + +using namespace js; +using namespace js::ion; + +static inline uint32 +DefaultStackSlot(uint32 vreg) +{ +#if JS_BITS_PER_WORD == 32 + return vreg * 2 + 2; +#else + return vreg + 1; +#endif +} + +LAllocation * +StupidAllocator::stackLocation(uint32 vreg) +{ + LDefinition *def = virtualRegisters[vreg]; + if (def->policy() == LDefinition::PRESET && def->output()->kind() == LAllocation::ARGUMENT) + return def->output(); + + return new LStackSlot(DefaultStackSlot(vreg), def->type() == LDefinition::DOUBLE); +} + +StupidAllocator::RegisterIndex +StupidAllocator::registerIndex(AnyRegister reg) +{ + for (size_t i = 0; i < registerCount; i++) { + if (reg == registers[i].reg) + return i; + } + JS_NOT_REACHED("Bad register"); + return UINT32_MAX; +} + +bool +StupidAllocator::init() +{ + if (!RegisterAllocator::init()) + return false; + + if (!virtualRegisters.reserve(graph.numVirtualRegisters())) + return false; + for (size_t i = 0; i < graph.numVirtualRegisters(); i++) + virtualRegisters.infallibleAppend(NULL); + + for (size_t i = 0; i < graph.numBlocks(); i++) { + LBlock *block = graph.getBlock(i); + for (LInstructionIterator ins = block->begin(); ins != block->end(); ins++) { + for (size_t j = 0; j < ins->numDefs(); j++) { + LDefinition *def = ins->getDef(j); + if (def->policy() != LDefinition::PASSTHROUGH) + virtualRegisters[def->virtualRegister()] = def; + } + + for (size_t j = 0; j < ins->numTemps(); j++) { + LDefinition *def = ins->getTemp(j); + if (def->isBogusTemp()) + continue; + virtualRegisters[def->virtualRegister()] = def; + } + } + for (size_t j = 0; j < block->numPhis(); j++) { + LPhi *phi = block->getPhi(j); + LDefinition *def = phi->getDef(0); + uint32 vreg = def->virtualRegister(); + + virtualRegisters[vreg] = def; + } + } + + // Assign physical registers to the tracked allocation. + { + registerCount = 0; + RegisterSet remainingRegisters(allRegisters_); + while (!remainingRegisters.empty(/* float = */ false)) + registers[registerCount++].reg = AnyRegister(remainingRegisters.takeGeneral()); + while (!remainingRegisters.empty(/* float = */ true)) + registers[registerCount++].reg = AnyRegister(remainingRegisters.takeFloat()); + JS_ASSERT(registerCount <= MAX_REGISTERS); + } + + return true; +} + +static inline bool +AllocationRequiresRegister(const LAllocation *alloc, AnyRegister reg) +{ + if (alloc->isRegister() && alloc->toRegister() == reg) + return true; + if (alloc->isUse()) { + const LUse *use = alloc->toUse(); + if (use->policy() == LUse::FIXED && AnyRegister::FromCode(use->registerCode()) == reg) + return true; + } + return false; +} + +static inline bool +RegisterIsReserved(LInstruction *ins, AnyRegister reg) +{ + // Whether reg is already reserved for an input or output of ins. + for (LInstruction::InputIterator alloc(*ins); alloc.more(); alloc.next()) { + if (AllocationRequiresRegister(*alloc, reg)) + return true; + } + for (size_t i = 0; i < ins->numTemps(); i++) { + if (AllocationRequiresRegister(ins->getTemp(i)->output(), reg)) + return true; + } + for (size_t i = 0; i < ins->numDefs(); i++) { + if (AllocationRequiresRegister(ins->getDef(i)->output(), reg)) + return true; + } + return false; +} + +AnyRegister +StupidAllocator::ensureHasRegister(LInstruction *ins, uint32 vreg) +{ + // Ensure that vreg is held in a register before ins. + + // Check if the virtual register is already held in a physical register. + RegisterIndex existing = findExistingRegister(vreg); + if (existing != UINT32_MAX) { + if (RegisterIsReserved(ins, registers[existing].reg)) { + evictRegister(ins, existing); + } else { + registers[existing].age = ins->id(); + return registers[existing].reg; + } + } + + RegisterIndex best = allocateRegister(ins, vreg); + loadRegister(ins, vreg, best); + + return registers[best].reg; +} + +StupidAllocator::RegisterIndex +StupidAllocator::allocateRegister(LInstruction *ins, uint32 vreg) +{ + // Pick a register for vreg, evicting an existing register if necessary. + // Spill code will be placed before ins, and no existing allocated input + // for ins will be touched. + JS_ASSERT(ins); + + LDefinition *def = virtualRegisters[vreg]; + JS_ASSERT(def); + + RegisterIndex best = UINT32_MAX; + + for (size_t i = 0; i < registerCount; i++) { + AnyRegister reg = registers[i].reg; + + if (reg.isFloat() != (def->type() == LDefinition::DOUBLE)) + continue; + + // Skip the register if it is in use for an allocated input or output. + if (RegisterIsReserved(ins, reg)) + continue; + + if (registers[i].vreg == MISSING_ALLOCATION || + best == UINT32_MAX || + registers[best].age > registers[i].age) + { + best = i; + } + } + + evictRegister(ins, best); + return best; +} + +void +StupidAllocator::syncRegister(LInstruction *ins, RegisterIndex index) +{ + if (registers[index].dirty) { + LMoveGroup *input = getInputMoveGroup(ins->id()); + LAllocation *source = new LAllocation(registers[index].reg); + + uint32 existing = registers[index].vreg; + LAllocation *dest = stackLocation(existing); + input->addAfter(source, dest); + + registers[index].dirty = false; + } +} + +void +StupidAllocator::evictRegister(LInstruction *ins, RegisterIndex index) +{ + syncRegister(ins, index); + registers[index].set(MISSING_ALLOCATION); +} + +void +StupidAllocator::loadRegister(LInstruction *ins, uint32 vreg, RegisterIndex index) +{ + // Load a vreg from its stack location to a register. + LMoveGroup *input = getInputMoveGroup(ins->id()); + LAllocation *source = stackLocation(vreg); + LAllocation *dest = new LAllocation(registers[index].reg); + input->addAfter(source, dest); + registers[index].set(vreg, ins); +} + +StupidAllocator::RegisterIndex +StupidAllocator::findExistingRegister(uint32 vreg) +{ + for (size_t i = 0; i < registerCount; i++) { + if (registers[i].vreg == vreg) + return i; + } + return UINT32_MAX; +} + +bool +StupidAllocator::go() +{ + // This register allocator is intended to be as simple as possible, while + // still being complicated enough to share properties with more complicated + // allocators. Namely, physical registers may be used to carry virtual + // registers across LIR instructions, but not across basic blocks. + // + // This algorithm does not pay any attention to liveness. It is performed + // as a single forward pass through the basic blocks in the program. As + // virtual registers and temporaries are defined they are assigned physical + // registers, evicting existing allocations in an LRU fashion. + + // For virtual registers not carried in a register, a canonical spill + // location is used. Each vreg has a different spill location; since we do + // not track liveness we cannot determine that two vregs have disjoint + // lifetimes. Thus, the maximum stack height is the number of vregs (scaled + // by two on 32 bit platforms to allow storing double values). + graph.setLocalSlotCount(DefaultStackSlot(graph.numVirtualRegisters() - 1) + 1); + + if (!init()) + return false; + + for (size_t blockIndex = 0; blockIndex < graph.numBlocks(); blockIndex++) { + LBlock *block = graph.getBlock(blockIndex); + JS_ASSERT(block->mir()->id() == blockIndex); + + for (size_t i = 0; i < registerCount; i++) + registers[i].set(MISSING_ALLOCATION); + + for (LInstructionIterator iter = block->begin(); iter != block->end(); iter++) { + LInstruction *ins = *iter; + + if (ins == *block->rbegin()) + syncForBlockEnd(block, ins); + + allocateForInstruction(ins); + } + } + + return true; +} + +void +StupidAllocator::syncForBlockEnd(LBlock *block, LInstruction *ins) +{ + // Sync any dirty registers, and update the synced state for phi nodes at + // each successor of a block. We cannot conflate the storage for phis with + // that of their inputs, as we cannot prove the live ranges of the phi and + // its input do not overlap. The values for the two may additionally be + // different, as the phi could be for the value of the input in a previous + // loop iteration. + + for (size_t i = 0; i < registerCount; i++) + syncRegister(ins, i); + + LMoveGroup *group = NULL; + + MBasicBlock *successor = block->mir()->successorWithPhis(); + if (successor) { + uint32 position = block->mir()->positionInPhiSuccessor(); + LBlock *lirsuccessor = graph.getBlock(successor->id()); + for (size_t i = 0; i < lirsuccessor->numPhis(); i++) { + LPhi *phi = lirsuccessor->getPhi(i); + + uint32 sourcevreg = phi->getOperand(position)->toUse()->virtualRegister(); + uint32 destvreg = phi->getDef(0)->virtualRegister(); + + if (sourcevreg == destvreg) + continue; + + LAllocation *source = stackLocation(sourcevreg); + LAllocation *dest = stackLocation(destvreg); + + if (!group) { + // The moves we insert here need to happen simultaneously with + // each other, yet after any existing moves before the instruction. + LMoveGroup *input = getInputMoveGroup(ins->id()); + if (input->numMoves() == 0) { + group = input; + } else { + group = new LMoveGroup; + block->insertAfter(input, group); + } + } + + group->add(source, dest); + } + } +} + +void +StupidAllocator::allocateForInstruction(LInstruction *ins) +{ + // Sync all registers before making a call. + if (ins->isCall()) { + for (size_t i = 0; i < registerCount; i++) + syncRegister(ins, i); + } + + // Allocate for inputs which are required to be in registers. + for (LInstruction::InputIterator alloc(*ins); alloc.more(); alloc.next()) { + if (!alloc->isUse()) + continue; + LUse *use = alloc->toUse(); + uint32 vreg = use->virtualRegister(); + if (use->policy() == LUse::REGISTER) { + AnyRegister reg = ensureHasRegister(ins, vreg); + alloc.replace(LAllocation(reg)); + } else if (use->policy() == LUse::FIXED) { + AnyRegister reg = AnyRegister::FromCode(use->registerCode()); + RegisterIndex index = registerIndex(reg); + if (registers[index].vreg != vreg) { + evictRegister(ins, index); + RegisterIndex existing = findExistingRegister(vreg); + if (existing != UINT32_MAX) + evictRegister(ins, existing); + loadRegister(ins, vreg, index); + } + alloc.replace(LAllocation(reg)); + } else { + // Inputs which are not required to be in a register are not + // allocated until after temps/definitions, as the latter may need + // to evict registers which hold these inputs. + } + } + + // Find registers to hold all temporaries and outputs of the instruction. + for (size_t i = 0; i < ins->numTemps(); i++) { + LDefinition *def = ins->getTemp(i); + if (!def->isBogusTemp()) + allocateForDefinition(ins, def); + } + for (size_t i = 0; i < ins->numDefs(); i++) { + LDefinition *def = ins->getDef(i); + if (def->policy() != LDefinition::PASSTHROUGH) + allocateForDefinition(ins, def); + } + + // Allocate for remaining inputs which do not need to be in registers. + for (LInstruction::InputIterator alloc(*ins); alloc.more(); alloc.next()) { + if (!alloc->isUse()) + continue; + LUse *use = alloc->toUse(); + uint32 vreg = use->virtualRegister(); + JS_ASSERT(use->policy() != LUse::REGISTER && use->policy() != LUse::FIXED); + + RegisterIndex index = findExistingRegister(vreg); + if (index == UINT32_MAX) { + LAllocation *stack = stackLocation(use->virtualRegister()); + alloc.replace(*stack); + } else { + registers[index].age = ins->id(); + alloc.replace(LAllocation(registers[index].reg)); + } + } + + // If this is a call, evict all registers except for those holding outputs. + if (ins->isCall()) { + for (size_t i = 0; i < registerCount; i++) { + if (!registers[i].dirty) + registers[i].set(MISSING_ALLOCATION); + } + } +} + +void +StupidAllocator::allocateForDefinition(LInstruction *ins, LDefinition *def) +{ + uint32 vreg = def->virtualRegister(); + + CodePosition from; + if ((def->output()->isRegister() && def->policy() == LDefinition::PRESET) || + def->policy() == LDefinition::MUST_REUSE_INPUT) + { + // Result will be in a specific register, spill any vreg held in + // that register before the instruction. + RegisterIndex index = + registerIndex(def->policy() == LDefinition::PRESET + ? def->output()->toRegister() + : ins->getOperand(def->getReusedInput())->toRegister()); + evictRegister(ins, index); + registers[index].set(vreg, ins, true); + def->setOutput(LAllocation(registers[index].reg)); + } else if (def->policy() == LDefinition::PRESET) { + // The result must be a stack location. + def->setOutput(*stackLocation(vreg)); + } else { + // Find a register to hold the result of the instruction. + RegisterIndex best = allocateRegister(ins, vreg); + registers[best].set(vreg, ins, true); + def->setOutput(LAllocation(registers[best].reg)); + } +} diff --git a/js/src/ion/StupidAllocator.h b/js/src/ion/StupidAllocator.h new file mode 100644 index 000000000000..d922282fc441 --- /dev/null +++ b/js/src/ion/StupidAllocator.h @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=4 sw=4 et 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/. */ + +#ifndef js_ion_stupidallocator_h__ +#define js_ion_stupidallocator_h__ + +#include "RegisterAllocator.h" + +// Simple register allocator that only carries registers within basic blocks. + +namespace js { +namespace ion { + +class StupidAllocator : public RegisterAllocator +{ + static const uint32 MAX_REGISTERS = Registers::Allocatable + FloatRegisters::Allocatable; + static const uint32 MISSING_ALLOCATION = UINT32_MAX; + + struct AllocatedRegister { + AnyRegister reg; + + // Virtual register this physical reg backs, or MISSING_ALLOCATION. + uint32 vreg; + + // id of the instruction which most recently used this register. + uint32 age; + + // Whether the physical register is not synced with the backing stack slot. + bool dirty; + + void set(uint32 vreg, LInstruction *ins = NULL, bool dirty = false) { + this->vreg = vreg; + this->age = ins ? ins->id() : 0; + this->dirty = dirty; + } + }; + + // Active allocation for the current code position. + AllocatedRegister registers[MAX_REGISTERS]; + uint32 registerCount; + + // Type indicating an index into registers. + typedef uint32 RegisterIndex; + + // Information about each virtual register. + Vector virtualRegisters; + + public: + StupidAllocator(MIRGenerator *mir, LIRGenerator *lir, LIRGraph &graph) + : RegisterAllocator(mir, lir, graph) + { + } + + bool go(); + + private: + bool init(); + + void syncForBlockEnd(LBlock *block, LInstruction *ins); + void allocateForInstruction(LInstruction *ins); + void allocateForDefinition(LInstruction *ins, LDefinition *def); + + LAllocation *stackLocation(uint32 vreg); + + RegisterIndex registerIndex(AnyRegister reg); + + AnyRegister ensureHasRegister(LInstruction *ins, uint32 vreg); + RegisterIndex allocateRegister(LInstruction *ins, uint32 vreg); + + void syncRegister(LInstruction *ins, RegisterIndex index); + void evictRegister(LInstruction *ins, RegisterIndex index); + void loadRegister(LInstruction *ins, uint32 vreg, RegisterIndex index); + + RegisterIndex findExistingRegister(uint32 vreg); +}; + +} // namespace ion +} // namespace js + +#endif diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index e3adcbf1f298..82f8b4473682 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -4850,7 +4850,9 @@ ProcessArgs(JSContext *cx, JSObject *obj_, OptionParser *op) if (const char *str = op->getStringOption("ion-regalloc")) { if (strcmp(str, "lsra") == 0) - ion::js_IonOptions.lsra = true; + ion::js_IonOptions.registerAllocator = ion::RegisterAllocator_LSRA; + else if (strcmp(str, "stupid") == 0) + ion::js_IonOptions.registerAllocator = ion::RegisterAllocator_Stupid; else return OptionFailure("ion-regalloc", str); } @@ -5079,7 +5081,8 @@ main(int argc, char **argv, char **envp) "Don't compile very large scripts (default: on, off to disable)") || !op.addStringOption('\0', "ion-regalloc", "[mode]", "Specify Ion register allocation:\n" - " lsra: Linear Scan register allocation (default)") + " lsra: Linear Scan register allocation (default)\n" + " stupid: Simple greedy register allocation") || !op.addBoolOption('\0', "ion-eager", "Always ion-compile methods") #ifdef JS_THREADSAFE || !op.addStringOption('\0', "ion-parallel-compile", "on/off",