Factor common register allocation code and add integrity checker to RegisterAllocator.h, add baseline StupidAllocator, bug 812945. r=jandem

This commit is contained in:
Brian Hackett 2012-11-26 12:27:05 -05:00
parent d91ef531e3
commit fbc44adf92
15 changed files with 1582 additions and 272 deletions

View File

@ -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 \

View File

@ -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", &regalloc);
if (mir->shouldCancel("Allocate Registers"))
#ifdef DEBUG
integrity.check(false);
#endif
IonSpewPass("Allocate Registers [LSRA]", &regalloc);
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<CodeGenerator>(mir, lir);
if (!codegen || !codegen->generate()) {
js_delete(codegen);

View File

@ -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),

View File

@ -32,7 +32,7 @@ namespace ion {
_(Range) \
/* Information during LICM */ \
_(LICM) \
/* Information during LSRA */ \
/* Information during regalloc */ \
_(RegAlloc) \
/* Information during inlining */ \
_(Inlining) \

View File

@ -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();
}

View File

@ -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)
{

View File

@ -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;
}

View File

@ -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<BitSet*>(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)
{

View File

@ -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<UsePosition>
{
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<UsePosition>
{
LUse *use;
CodePosition pos;
UsePosition(LUse *use, CodePosition pos) :
use(use),
pos(pos)
{ }
};
typedef InlineForwardListIterator<UsePosition> 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<InstructionData>(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<uint32,
LInstruction *,
DefaultHasher<uint32>,
@ -571,7 +411,7 @@ typedef HashMap<uint32,
typedef InlineList<LiveInterval>::iterator IntervalIterator;
typedef InlineList<LiveInterval>::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<LiveInterval *, AnyRegister::Total> 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<LiveInterval> 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();

View File

@ -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<IntegrityItem, 20, SystemAllocPolicy> 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;
}

View File

@ -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<uint32, 2, SystemAllocPolicy> inputs;
Vector<uint32, 1, SystemAllocPolicy> 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<InstructionInfo, 0, SystemAllocPolicy> instructions;
struct BlockInfo {
Vector<InstructionInfo, 5, SystemAllocPolicy> phis;
BlockInfo() {}
BlockInfo(const BlockInfo &o) {
for (size_t i = 0; i < o.phis.length(); i++)
phis.append(o.phis[i]);
}
};
Vector<BlockInfo, 0, SystemAllocPolicy> blocks;
Vector<LDefinition*, 20, SystemAllocPolicy> 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<IntegrityItem, 10, SystemAllocPolicy> worklist;
// Set of all items that have already been processed.
typedef HashSet<IntegrityItem, IntegrityItem, SystemAllocPolicy> 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<InstructionData>(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

View File

@ -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

View File

@ -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));
}
}

View File

@ -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<LDefinition*, 0, SystemAllocPolicy> 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

View File

@ -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",