Arquivos
hhvm/hphp/runtime/vm/translator/regalloc.h
T
aalexandre 26178124a4 Eliminate int32, uint32, int16, uint16, int8, uint8.
This concludes the inttypes replacement.
These replacement have been mostly mechanical
with the use of cxx_replace.
2013-03-09 10:25:16 -08:00

471 linhas
14 KiB
C++

/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010- Facebook, Inc. (http://www.facebook.com) |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
*/
#ifndef incl_REG_ALLOC_H_
#define incl_REG_ALLOC_H_
#include <boost/noncopyable.hpp>
#include "util/trace.h"
#include "util/asm-x64.h"
#include "runtime/vm/translator/translator.h"
#include "runtime/vm/translator/physreg.h"
namespace HPHP { namespace VM { namespace Transl {
// Assumption: the set interfaces are limited to the first 64 registers.
static const int kMaxRegs = 64;
/*
* We take a "virtual memory" approach to register allocation. The virtual
* registers are PHP Location's, and the physical registers are
* an opaque collection of integers that presumably map to machine regs.
* We assume that the machine has enough physical registers to get through
* a single normalized instruction with all inputs and outputs in
* registers.
*
* By analogy with page replacement, we do LRU to replace physical
* registers. While this is an imperfect analogy, it provides constant
* overheads per register, and a guarantee that any runnable code will
* actually be able to allocate registers.
*/
struct RegContent {
enum Kind {
Invalid,
Loc,
Int
} m_kind;
int64_t m_int;
Location m_loc;
RegContent(const Location &loc, int64_t intval = 0)
: m_kind(Loc), m_int(intval), m_loc(loc) { }
RegContent(int64_t _m_int) : m_kind(Int), m_int(_m_int), m_loc(Location()) { }
RegContent() : m_kind(Invalid), m_int(0), m_loc(Location()) { }
bool isInt() const {
return m_kind == Int;
}
bool isLoc() const {
return m_kind == Loc;
}
bool isValid() const {
return m_kind == Int || m_kind == Loc;
}
bool isInvalid() const {
return !isValid();
}
bool isUnreachableStack(int firstUnreachable) const {
return isLoc() && m_loc.isStack() && m_loc.offset >= firstUnreachable;
}
int cmp(const RegContent &other) const {
if (m_kind != other.m_kind) {
return m_kind - other.m_kind;
}
if (m_kind == Int) {
if (m_int == other.m_int) {
return 0;
}
return m_int > other.m_int ? 1 : -1;
}
assert(m_kind == Loc);
return m_loc.cmp(other.m_loc);
}
bool operator==(const RegContent &other) const {
return cmp(other) == 0;
}
bool operator!=(const RegContent &other) const {
return cmp(other) != 0;
}
const char *kindStr() const {
switch (m_kind) {
case Int : return "Int";
case Loc : return "Loc";
default : return "Invalid";
}
}
std::string pretty() const;
// Hash function
size_t operator()(const RegContent& cont) const {
return HPHP::hash_int64_pair(
cont.m_kind, cont.isInt() ? cont.m_int : cont.m_loc(cont.m_loc));
}
};
/*
* In the virtual memory analogy, a RegInfo is the PTE: it contains
* the v2p mapping and dirty bit.
*/
struct RegInfo {
#define REGSTATES \
REGSTATE(INVALID) \
REGSTATE(FREE) REGSTATE(CLEAN) REGSTATE(SCRATCH) REGSTATE(DIRTY)
enum State {
#define REGSTATE(x) x,
// Order is meaningful here; we use <, > to test "strength" of state
REGSTATES
#undef REGSTATE
};
RegContent m_cont;
uint64_t m_epoch;
PhysReg m_pReg;
State m_state;
DataType m_type;
std::string pretty() const {
const char* names[] = {
#define REGSTATE(x) #x,
REGSTATES
#undef REGSTATE
};
char buf[1024];
sprintf(buf, "Reg:%02d:%s:%lld:Type:%d",
int(m_pReg), names[m_state], static_cast<long long>(m_epoch),
m_type);
return Trace::prettyNode(buf, m_cont);
}
RegInfo() : m_cont(), m_state(FREE) { }
#undef REGSTATES
};
/**
* SpillFill -- machine-specific details about how to spill and fill
* registers in given virtual location. Should probably be a template
* parameter, but I'd like to not waste my short remaining years of health
* recompiling.
*/
class SpillFill {
public:
virtual ~SpillFill() { }
virtual void spill(const Location& loc, DataType t, PhysReg reg,
bool writeType) = 0;
virtual void fill(const Location& loc, PhysReg reg) = 0;
virtual void fillByMov(PhysReg src, PhysReg dst) = 0;
virtual void loadImm(int64_t immVal, PhysReg reg) = 0;
virtual void poison(PhysReg reg) = 0;
};
class LazyScratchReg;
class RegAlloc {
friend class LazyScratchReg;
// RegInfo: indexed by PhysReg.
RegInfo m_info[kMaxRegs];
// Secondary indices on m_info.
RegSet m_callerSaved; // Good short-lived regs
RegSet m_calleeSaved; // Good long-lived regs
int m_numRegs; // Number of real registers, <= kMaxRegs
RegSet m_allRegs;
PhysReg m_lru[kMaxRegs]; // lru order over registers
typedef hphp_hash_map<RegContent, PhysReg, RegContent> ContToRegMap;
ContToRegMap m_contToRegMap; // Content -> PhysReg
SpillFill* m_spf;
mutable int m_freezeCount; // support immutability
uint64_t m_epoch;
bool m_branchSynced;
RegInfo* alloc(const Location& loc, DataType t, RegInfo::State state,
bool needsFill, int64_t immVal = 0, PhysReg target = InvalidReg);
RegInfo* findFreeReg(const Location& loc);
void assignRegInfo(RegInfo *regInfo, const RegContent &cont,
RegInfo::State state, DataType type);
void stateTransition(RegInfo* r, RegInfo::State to);
static bool isValidReg(PhysReg pr) {
return int(pr) >= 0 && int(pr) < kMaxRegs;
}
// lru operations are O(numRegs), but numRegs is small.
void lruFront(RegInfo *r);
void lruBack(RegInfo* r);
RegInfo *physRegToInfo(PhysReg pr) const;
void freeRegInfo(RegInfo* ri);
void spill(RegInfo *toSpill);
void trace();
void verify();
void smashRegImpl(RegInfo *r);
void reconcileOne(RegInfo* r, RegAlloc* branchRA, PhysReg branchPR);
bool checkNoScratch();
PhysReg allocScratchReg(PhysReg pr = InvalidReg);
void freeScratchReg(PhysReg r);
public:
RegAlloc(RegSet callerSaved, RegSet calleeSaved, SpillFill* spf);
RegAlloc(const RegAlloc& rhs) {
*this = rhs; // operator= invocation
}
// allocReg: allocate a single operand
PhysReg allocReg(const Location& loc, DataType t, RegInfo::State state) {
RegInfo* ri = alloc(loc, t, state, state == RegInfo::CLEAN);
return ri->m_pReg;
}
void setBranchSynced() {
assert(!m_branchSynced);
m_branchSynced = true;
}
bool branchSynced() {
return m_branchSynced;
}
void assertNoScratch() { assert(checkNoScratch()); }
const RegInfo* getInfo(PhysReg pr) const {
return physRegToInfo(pr);
}
bool regIsDirty(PhysReg pr) const {
return getInfo(pr)->m_state == RegInfo::DIRTY;
}
bool regIsClean(PhysReg pr) const {
return getInfo(pr)->m_state == RegInfo::CLEAN;
}
bool regIsFree(PhysReg pr) const {
return getInfo(pr)->m_state == RegInfo::FREE;
}
void assertRegIsFree(PhysReg pr) const {
if (debug && !regIsFree(pr)) {
std::cerr << getInfo(pr)->pretty() << std::endl;
always_assert(false && "Expected register to be free");
}
}
DataType regType(PhysReg pr) const {
return getInfo(pr)->m_type;
}
void setRegType(PhysReg pr, DataType type) const {
physRegToInfo(pr)->m_type = type;
}
Location regLoc(PhysReg pr) const {
const RegInfo* info = getInfo(pr);
return info->m_cont.isLoc() ? info->m_cont.m_loc : Location();
}
// allocInputRegs: given an instruction, find/fill its inputs.
void allocInputReg(const DynLocation& dl, PhysReg target = InvalidReg);
void allocInputReg(const NormalizedInstruction& ni, int index,
PhysReg target = InvalidReg);
void allocInputRegs(const NormalizedInstruction& ni);
// allocOutputRegs: destructively mark output registers. Should only
// be done when we know that the code we're emitting will drive valid
// values into these outputs.
void allocOutputRegs(const NormalizedInstruction& ni);
void bind(PhysReg reg, const Location& loc, DataType t,
RegInfo::State state);
void bindScratch(LazyScratchReg& reg, const Location& loc, DataType t,
RegInfo::State state);
void markAsClean(const Location& loc);
/*
* Invalidating a location means to drop any register mapped to that
* location down to FREE state, regardless of the current state.
*
* (This differs from smashing a location in that it doesn't require
* that the register is not DIRTY.)
*/
void invalidate(const Location& loc);
void invalidateLocals(int first, int last);
bool hasReg(const Location &loc) const;
RegSet getRegsLike(RegInfo::State state) const;
bool hasDirtyRegs(int firstUnreachableStk) const;
PhysReg getReg(const Location &loc);
/*
* Returns a PhysReg containing the given immediate value (immVal),
* or InvalidReg if this could not be accomplished.
*
* If a physical register already contains `immVal', this function
* returns it. Otherwise, if `allowAllocate' is true and this
* register allocator is not frozen, a free register, if available,
* will be allocated and set to this value.
*
* Otherwise InvalidReg is returned.
*/
PhysReg getImmReg(int64_t immVal, bool allowAllocate = true);
/*
* Reset the register mapping to an empty state, epoch zero.
*
* Post: pristine() == true
*/
void reset();
/*
* Indicates whether the register map is in its initial, empty
* state. That is, empty() == true and bumpEpoch has not been
* called since the last time reset() was called.
*/
bool pristine() const;
/*
* Returns true if this RegMap has no non-FREE registers in it.
*/
bool empty() const;
/*
* Clean any dirty registers from various sets.
*
* For these functions, only registers in the DIRTY state are
* cleaned and transitioned to the CLEAN state. Scratch registers
* remain in scratch state.
*/
void cleanAll();
void cleanRegs(RegSet regsToPurge);
void cleanLoc(const Location& loc);
void cleanLocals();
void cleanReg(PhysReg reg);
/*
* Forget the mapping for all registers in the set. The regs must
* not be DIRTY (if you want to forget a dirty register without
* spilling, scrub it first).
*/
void smashRegs(RegSet smashedRegs);
void smashReg(PhysReg pr);
void smashLoc(const Location& loc);
/*
* Forget a mapping for a register, after cleaning it if it is DIRTY.
*
* This is equivalent to calling clean followed by smash.
*/
void cleanSmashRegs(RegSet set);
void cleanSmashReg(PhysReg pr);
void cleanSmashLoc(const Location& loc);
/*
* Scrubbing a register means to change it to the CLEAN state,
* regardless of whether we've actually spilled its contents to
* memory. These functions may not be called for a scratch
* register---it must be a program location---but it is legal to
* scrub an already-free register.
*
* This is often going to be followed by smashing the register.
* Special functions help for the case of dealing with discarding
* dead execution stack locations, since that's usually what this is
* about.
*/
void scrubStackEntries(int firstUnreachable);
void scrubStackRange(int firstToDiscard, int lastToDiscard);
void scrubReg(PhysReg pr);
void scrubRegs(RegSet regs);
void scrubLoc(const Location&);
void killImms(RegSet imms);
void swapRegisters(PhysReg r1, PhysReg r2);
/*
* Emit spills and fills in order to bring the `branch' state into
* the state of *this.
*
* This reconcile method is not symmetric: it will only emit
* spills/fills to the branch. In particular, this means that
* whatever is happening on the other side of branch.m_spf-> needs
* to be aware of the branch register state. Specifically: this
* means tx64->m_regMap must be the *branch* register map during
* reconciliation, because spill() may try to use/load immediate
* registers.
*/
void reconcile(RegAlloc& branch);
void freeze() const { m_freezeCount++; }
void defrost() const { m_freezeCount--; assert(m_freezeCount >= 0); }
bool frozen() const { return m_freezeCount > 0; }
void bumpEpoch() { m_epoch++; }
std::string pretty() const;
};
// RAII ScratchReg holder:
// LazyScratchReg r(m_regMap);
// ...
// r.alloc();
// ... neg_reg64(*r);
//
// ScratchReg r(m_regMap);
// .. neg_reg64(*r);
class LazyScratchReg : boost::noncopyable {
protected:
RegAlloc& m_regMap;
PhysReg m_reg;
public:
LazyScratchReg(RegAlloc& regMap);
~LazyScratchReg();
bool isAllocated() const { return m_reg != reg::noreg; }
void alloc(PhysReg pr = InvalidReg);
void dealloc();
void realloc(PhysReg pr = InvalidReg);
friend PhysReg r(const LazyScratchReg& l) { return l.m_reg; }
friend Reg8 rbyte(const LazyScratchReg& l) { return rbyte(l.m_reg); }
friend Reg32 r32(const LazyScratchReg& l) { return r32(l.m_reg); }
friend Reg64 r64(const LazyScratchReg& l) { return r64(l.m_reg); }
};
class ScratchReg : public LazyScratchReg {
public:
ScratchReg(RegAlloc& regMap);
// Use this constructor to reserve an already-selected register, which
// must be free.
ScratchReg(RegAlloc& regMap, PhysReg pr);
};
/*
* DumbScratch allocates a register out of a RegSet, putting it back
* when it's done. This is used for very simple register selection
* when we don't want to use the whole register allocator
* (e.g. between tracelets).
*
* Since this thing is dumb, there's no recourse if the set has no
* registers available. This thing will assert, then throw, in that
* case.
*/
struct DumbScratchReg : private boost::noncopyable {
explicit DumbScratchReg(RegSet& allocSet);
~DumbScratchReg();
friend PhysReg r(const DumbScratchReg& d) { return d.m_reg; }
friend Reg32 r32(const DumbScratchReg& d) { return r32(d.m_reg); }
friend Reg64 r64(const DumbScratchReg& d) { return r64(d.m_reg); }
private:
RegSet& m_regPool;
const PhysReg m_reg;
};
} } } // HPHP::VM::Transl
#endif /* incl_REG_ALLOC_H_ */