package com.seaofnodes.simple.codegen;
import com.seaofnodes.simple.util.Ary;
import com.seaofnodes.simple.util.Utils;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.Type;
import java.util.Arrays;
import java.util.BitSet;
import java.util.IdentityHashMap;
/**
* "Briggs/Chaitin/Click".
* Graph coloring.
*
* Every def and every use have a bit-set of allowed registers.
* Fully general to bizarre chips.
* Multiple outputs fully supported, e.g. add-with-carry or combined div/rem or "xor rax,rax" setting both rax and flags.
*
* Intel 2-address accumulator-style ops fully supported.
* Op-to-stack-spill supported.
* All addressing modes supported.
* Register pairs could be supported with some grief.
*
* Splitting instead of spilling (simpler implementation).
* Stack slots are "just another register" (tiny stack frames, simpler implementation).
* Stack/unstack during coloring with a marvelous trick (simpler implementation).
*
* Both bitset and adjacency list formats for the interference graph; one of
* the few times it's faster to change data structures mid-flight rather than
* just wrap one of the two.
*
* Liveness computation and interference graph built in the same one pass (one
* fewer passes per round of coloring).
*
* Single-register def or use live ranges deny neighbors their required
* register and thus do not interfere, vs interfering and denying the color
* and coloring time. Basically all function calls do this, but many oddball
* registers also, e.g. older div/mod/mul ops. (5x smaller IFG, the only
* O(n^2) part of this operation).
*/
public class RegAlloc {
// Main Coloring Algorithm:
// Repeat until colored:
// Build Live Ranges (LRGs)
// - Intersect allowed registers
// If hard conflicts (LRGs with no allowed registers)
// - Pre-split conflicted LRGs, and repeat
// Build Interference Graph (IFG)
// - Self conflicts split now
// Color (remove trivial first then any until empty; reverse assign colors
// - If color fails:
// - - Split uncolorable LRGs
// Top-level program graph structure
final CodeGen _code;
public int _spills, _spillScaled;
private boolean _done;
// -----------------------
// Live ranges with self-conflicts or no allowed registers
private final IdentityHashMap _failed = new IdentityHashMap<>();
// Loop Phis get one chance to color after only cold self-conflict splits.
// If the same Phi self-conflicts again, split its hot def/backedge also.
private final IdentityHashMap _deferredLoopSelf = new IdentityHashMap<>();
void fail( LRG lrg ) {
assert lrg.leader();
_failed.put(lrg,"");
}
boolean success() { return _failed.isEmpty(); }
public boolean done() { return _done; }
// -----------------------
// Map from Nodes to Live Ranges
private final IdentityHashMap _lrgs = new IdentityHashMap<>();
short _lrg_num;
// Define a new LRG, and assign n
LRG newLRG( Node n, FunNode fun ) {
LRG lrg = lrg(n);
if( lrg!=null ) return lrg;
lrg = new LRG(_lrg_num++, fun);
LRG old = _lrgs.put(n,lrg); assert old==null;
return lrg;
}
// LRG for n
public LRG lrg( Node n ) {
LRG lrg = _lrgs.get(n);
if( lrg==null ) return null;
LRG lrg2 = lrg.find();
if( lrg != lrg2 )
_lrgs.put(n,lrg2);
return lrg2;
}
// Find LRG for n.in(idx), and also map n to it
LRG lrg2( Node n, int idx ) {
LRG lrg = lrg(n.in(idx));
return union(lrg,n);
}
// Union any lrg for n with lrg and map to the union
LRG union( LRG lrg, Node n ) {
LRG lrgn = _lrgs.get(n);
LRG lrg3 = lrg.union(lrgn);
_lrgs.put(n,lrg3);
return lrg3;
}
// Force all unified to roll up; collect live ranges
LRG[] _LRGS;
void unify() {
Ary lrgs = new Ary<>(LRG.class);
for( Node n : _lrgs.keySet() ) {
LRG lrg = lrg(n);
lrgs.setX(lrg._lrg,lrg);
}
_LRGS = lrgs.asAry();
// Remove unified lrgs from failed set also
for( LRG lrg : _failed.keySet().toArray(new LRG[0]) ) {
if( !lrg.leader() ) {
LRG lrg2 = lrg.find();
_failed.remove(lrg);
_failed.put(lrg2,"");
}
}
}
// Diagnostic lookup: do not compress leaders or rewrite the node mapping.
private LRG _lrg(Node n) {
LRG lrg = _lrgs.get(n);
if( lrg != null )
while( lrg._leader != null ) lrg = lrg._leader;
return lrg;
}
public short regnum( Node n ) {
LRG lrg = _lrg(n);
return lrg==null ? -1 : lrg._reg;
}
// Printable register number for node n
String reg( Node n ) {
LRG lrg = _lrg(n);
return lrg==null ? null : reg(lrg);
}
String reg( LRG lrg ) {
// No register yet, use LRG
if( lrg._reg == -1 ) return "V"+lrg._lrg;
// Chosen machine register unless stack-slot and past RA
String[] regs = _code._mach.regs();
if( lrg._reg < regs.length || _code._phase.ordinal() <= CodeGen.Phase.RegAlloc.ordinal() || lrg._fun==null )
return RegMask.reg(regs,lrg._reg);
// Stack-slot past RA uses the frame layout logic
return "[rsp+"+lrg._fun.computeStackOffset(_code,lrg._reg)+"]";
}
// -----------------------
RegAlloc( CodeGen code ) { _code = code; }
public void regAlloc() {
// Insert callee-save registers
String[] regs = _code._mach.regs();
long neverSave= _code._mach.neverSave();
for( CFGNode bb : _code._cfg )
if( bb instanceof FunNode fun ) {
ReturnNode ret = fun.ret();
int len = Math.min(regs.length,64);
for( int reg=0; reg= 7 ) // Really expect to be done soon
throw new IllegalStateException("Register allocation made no progress after eight rounds");
round++;
}
postColor(); // Remove no-op spills
_done = true;
}
boolean verifyFunctionLocalEdges() {
for( CFGNode bb : _code._cfg )
for( Node use : bb._outputs ) {
FunNode useFun = funOf(use);
for( int i=1; i ")
.append(use.uniqueName()).append("(").append(useFun.label()).append(") input ").append(idx)
.append(" def=").append(def).append(" use=").append(use).append("\n all uses:");
for( Node out : def.outs() ) {
FunNode outFun = funOf(out);
sb.append("\n ").append(out.uniqueName()).append("(")
.append(outFun==null ? "?" : outFun.label()).append(")");
}
return sb.toString();
}
private static boolean allowedCrossFunctionEdge(Node use, int idx, Node def) {
// Function metadata/control edges are not value live ranges.
if( use instanceof FunNode || use instanceof StopNode || use instanceof StopCUNode )
return true;
// Return-linked function pointers carry function identity/lifetime, not
// an allocator value flowing from the callee body into this function.
if( use instanceof FunPtrNode && def instanceof ReturnNode )
return true;
// Parm defaults and call-linked argument edges are the entry protocol;
// BuildLRG also skips Parm input 1.
if( use instanceof ParmNode )
return true;
// Linked CallEnd -> Return edges should be gone before RegAlloc, but
// diagnose allocator value edges rather than call graph metadata here.
if( use instanceof CallEndNode && def instanceof ReturnNode )
return true;
return false;
}
private static FunNode funOf(Node n) {
if( n instanceof ParmNode parm ) return parm.fun();
if( n instanceof ReturnNode ret ) return ret.fun();
if( n instanceof FunNode fun ) return fun;
if( n instanceof CFGNode cfg ) return cfg.fun();
CFGNode cfg = n.cfg0();
return cfg==null ? null : cfg.fun();
}
private boolean graphColor(byte round) {
_failed.clear();
_lrgs.clear();
_lrg_num = 1;
_LRGS=null;
return
// Build Live Ranges
BuildLRG.run(round,this) && // if no hard register conflicts
// Build Interference Graph
IFG.build(round,this) && // If no self conflicts or uncolorable
// Conservative coalesce copies
Coalesce.coalesce(round,this) &&
// Color attempt
IFG.color(round,this); // If colorable
}
// -----------------------
// Split conflicted live ranges.
void split(byte round) {
// In C2, all splits are handling in one pass over the program. Here,
// in the name of clarity, we'll handle each failing live range
// independently... which generally requires a full pass over the
// program for each failing live range. i.e., might be a lot of
// passes.
// Sort, to avoid non-deterministic HashMap ordering
LRG[] splits = _failed.keySet().toArray(new LRG[0]);
Arrays.sort(splits, (x,y) -> x._lrg - y._lrg );
for( LRG lrg : splits )
split(round,lrg);
}
// Split this live range, top level heuristic
boolean split( byte round, LRG lrg ) {
assert lrg.leader(); // Already rolled up
if( lrg._selfConflicts != null )
return splitSelfConflict(round,lrg);
// Register mask when empty; split around defs and uses with limited
// register masks.
if( lrg._mask.isEmpty() ) {
if( lrg._1regDefCnt <= 1 &&
lrg._1regUseCnt <= 1 &&
(lrg._1regDefCnt + lrg._1regUseCnt) > 0 &&
// A fixed-register clone with flexible uses must split at the uses.
!(lrg._1regDefCnt==1 && lrg._machDef.isClone() && lrg._1regUseCnt==0) )
return splitEmptyMaskSimple(round,lrg);
// Repeated single-reg uses from a single def. Special for archs
// with more fixed regs.
if( !lrg._multiDef && lrg._1regDefCnt <= 1 && lrg._1regUseCnt > 2 )
if( splitEmptyMaskByUse(round,lrg) )
return true;
}
if( lrg._mask.isEmpty() && lrg._multiDef && lrg._1regUseCnt > 1 && lrg._1regDefCnt <= 1 ) {
if( splitEmptyMaskSingleUses(round,lrg) )
return true;
}
// Generic split-by-loop depth.
return splitByLoop(round,lrg);
}
// Split live range with an empty mask. Specifically forces splits at
// single-register defs or uses and not elsewhere.
boolean splitEmptyMaskSimple( byte round, LRG lrg ) {
// Live range has a single-def single-register, and/or a single-use
// single-register. Split after the def and before the use. Does not
// require a full pass.
// Compute def nOuts *before* replacing those uses with a split
int defNouts = ((Node)lrg._machDef).nOuts();
// Split just after def
if( lrg._1regDefCnt==1 && !lrg._machDef.isClone() )
// Force must-split, even if a prior split same block because register
// conflicts. Example:
// alloc
// V1/rax - forced by alloc
// alloc
// V2/rax - kills prior RAX
// st4 [V1],len - No good, must split around
insertAfterAndReplace( makeSplit("def/empty1",round,lrg), (Node)lrg._machDef, false/*true*/);
// Split just before use
if( lrg._machUse!=null && (lrg._1regUseCnt==1 || (lrg._1regDefCnt==1 && defNouts==1)) )
insertBefore((Node)lrg._machUse,lrg._uidx,"use/empty1",round,lrg);
return true;
}
// Popular single-def value: share a split among uses with compatible masks.
boolean splitEmptyMaskByUse( byte round, LRG lrg ) {
Node def = (Node)lrg._machDef;
// Snapshot distinct users: rewriting an input changes def's output list,
// and a call can use the same value in more than one argument.
Ary uses = new Ary<>(Node.class);
for( Node use : def._outputs )
if( uses.find(use)==-1 ) uses.push(use);
Ary rclass = new Ary<>(RegMask.class);
int ncalls=0;
for( Node use : uses )
if( use instanceof MachNode mach ) {
// Sharing copies across several calls usually loses to their kills.
if( use instanceof CallNode && ++ncalls > 1 ) return false;
for( int i=1; i1 ) split.setDef(1,def);
for( Node use : uses )
if( use instanceof MachNode mach )
for( int i=1; i rclass, RegMask rmask ) {
for( int i=0; i x._nid - y._nid );
// First encounter with a loop Phi: try only cold splits this round.
// Remember the Phi across rounds so a persistent self-conflict forces
// the hot def/backedge splits on the next encounter.
IdentityHashMap deferred = new IdentityHashMap<>();
for( Node def : conflicts ) {
if( def instanceof PhiNode phi && phi.region() instanceof LoopNode )
deferLoopSelf(phi,deferred);
for( Node use : def._outputs )
if( use instanceof PhiNode phi && phi.region() instanceof LoopNode &&
phi._inputs.find(def)==2 )
deferLoopSelf(phi,deferred);
}
// For all conflicts
for( Node def : conflicts ) {
assert lrg(def)==lrg; // Might be conflict use-side
// Split before each use that extends the live range; i.e. is a
// Phi or two-address
for( int i=0; i loop.idepth() ) ) ||
(use instanceof MachNode mach && mach.twoAddress()!=0 && use.in(mach.twoAddress())==def) )
insertBefore( use, use._inputs.find(def), "use/self/use",round,lrg );
}
// Split after the Phi and before its entry. Also isolate inputs
// shared with another Phi: otherwise the shared input keeps the
// conflicting live ranges joined, even after splitting the entry.
if( def instanceof PhiNode phi && !(def instanceof ParmNode) ) {
if( !deferred.containsKey(phi) ) {
SplitNode split = makeSplit("def/self",round,lrg);
insertAfterAndReplace(split,def,false);
if( split.nOuts()==0 )
split.killOrdered();
}
for( int i=1; i<(deferred.containsKey(phi) ? 2 : phi.nIns()); i++ )
if( i==1 || sharedPhiInput(phi,i) )
insertBefore(phi,i,"use/self/phi",round,lrg);
}
// Split before two-address ops which extend the live range
if( def instanceof MachNode mach && mach.twoAddress()!= 0 )
insertBefore(def,mach.twoAddress(),"use/self/two",round,lrg);
}
return true;
}
private void deferLoopSelf(PhiNode phi, IdentityHashMap deferred) {
if( !_deferredLoopSelf.containsKey(phi) ) {
_deferredLoopSelf.put(phi,"");
deferred.put(phi,"");
}
}
private static boolean sharedPhiInput(PhiNode phi, int i) {
for( Node use : phi.in(i)._outputs )
if( use instanceof PhiNode && use!=phi ) return true;
return false;
}
// Generic: split around the outermost loop with non-split def/uses. This
// covers both self-conflicts (once we split deep enough) and register
// pressure issues.
boolean splitByLoop( byte round, LRG lrg ) {
findAllLRG(lrg);
// Find min loop depth for all non-split defs and uses.
long ld = (-1L<<32) | 9999;
for( Node n : _ns ) {
if( lrg(n)==lrg ) // This is a LRG def
ld = ldepth(ld,n,n.cfg0());
// PhiNodes check all CFG inputs
if( n instanceof PhiNode phi ) {
for( int i=1; i>32);
boolean emptyMulti = lrg._mask.isEmpty() && lrg._multiDef && lrg._1regDefCnt <= 1 &&
(lrg._1regUseCnt > 1 || _ns._len > 5);
// Multi-def empty-mask conflicts need the hot/deep side isolated from
// fixed register uses. Ordinary pressure splits keep the old shallow
// boundary behavior.
for( Node n : _ns ) {
if( n instanceof SplitNode && min!=max ) continue; // Ignoring splits; since spilling need to split in a deeper loop
if( n.isDead() ) continue; // Some Cloneable went dead by other spill changes
// If this is a 2-address commutable op (e.g. AddX86, MulX86) and the rhs has only a single user,
// commute the inputs... which chops the LHS live ranges' upper bound to just the RHS.
if( n instanceof MachNode mach && lrg(n)==lrg && mach.twoAddress()==1 && mach.commutes() && n.in(2).nOuts()==1 )
n.swap12();
if( lrg(n)==lrg && // This is a LRG def
// At loop boundary, or splitting in inner loop
(min==max || (emptyMulti ? n.cfg0().loopDepth() >= max : n.cfg0().loopDepth() <= min)) ) {
// Cloneable constants will be cloned at uses, not after def
if( !(n instanceof MachNode mach && mach.isClone()) &&
// Single user is already a split adjacent
!(n.nOuts()==1 && n.out(0) instanceof SplitNode split && sameBlockNoClobber(split) ) )
// Split after def in min loop nest
insertAfterAndReplace( makeSplit("def/loop",round,lrg), n,true);
}
// PhiNodes check all CFG inputs
if( n instanceof PhiNode phi && !(n instanceof ParmNode)) {
for( int i=1; i= max : phi.region().cfg(i).loopDepth() <= min)) &&
// and not around the backedge of a loop (bad place to force a split, hard to remove)
!(phi.region() instanceof LoopNode && i==2 && (phi.in(i) instanceof PhiNode pp && pp.region()==phi.region())) )
// Split before phi-use in prior block
insertBefore(phi,i, "use/loop/phi",round,lrg);
} else {
// Others check uses
for( int i=1; i= max : n.cfg0().loopDepth() <= min)) )
// Split before in this block
insertBefore( n, i, "use/loop/use", round,lrg, false );
}
}
}
return true;
}
private boolean lrgSame(Node x, LRG lrg) {
LRG xlrg = lrg(x);
while( xlrg==null && x instanceof SplitNode )
xlrg=lrg(x = x.in(1));
return xlrg==lrg;
}
private static long ldepth( long ld, Node n, CFGNode cfg ) {
// Do not count splits
if( n instanceof SplitNode ) return ld;
// Collect min/max loop depth
int min = (int)ld;
int max = (int)(ld>>32);
int d = cfg.loopDepth();
// if n will lower the min loop and is in the tail end of the loop
// header, splitting "around" the loop will not help. Treat n as being
// in the loop.
if( d < min ) {
if( cfg.uctrl() instanceof LoopNode loop && loop.entry()==cfg ) {
for( int i=cfg.nOuts()-2; i>=0; i-- ) {
Node out = cfg.out(i);
if( n==out )
{ d = loop.loopDepth(); break; } // Treat n as being "in the loop"
if( !((out instanceof MachNode mach && mach.isClone()) || out instanceof SplitNode ) )
break; // Treat b as "normal", out of loop
}
}
}
// lower min, raise max, and re-fold
min = Math.min(min,d);
max = Math.max(max,d);
return ((long)max<<32) | min;
}
// Find all members of a live range, both defs and uses
private final Ary _ns = new Ary<>(Node.class);
void findAllLRG( LRG lrg ) {
_ns.clear();
int wd = 0;
if( lrg._machDef != null ) _ns.push((Node)lrg._machDef);
if( lrg._machUse != null ) _ns.push((Node)lrg._machUse);
while( wd < _ns._len ) {
Node n = _ns.at(wd++);
if( lrg(n)!=lrg ) continue;
for( Node def : n._inputs )
if( lrg(def)==lrg && _ns.find(def)== -1 )
_ns.push(def);
for( Node use : n._outputs )
if( _ns.find(use)== -1 )
_ns.push(use);
}
for( Node n : _ns ) assert !n.isDead();
}
void insertBefore(Node n, int i, String kind, byte round, LRG lrg, boolean skip) {
Node def = n.in(i);
// Phi splitting may visit the synthetic never-exit arm.
if( def._type==Type.TOP ) return;
// Effective block for use
CFGNode cfg = n instanceof PhiNode phi ? phi.region().cfg(i) : n.cfg0();
// Use-side RegMask, if available
RegMask umask = n instanceof MachNode mach ? mach.regmap(i) : null;
// Def is a split ?
if( skip && def instanceof SplitNode ) {
boolean singleReg = umask!=null && umask.size1();
// Same block, multiple registers, split is only used by n,
// assume this is good enough and do not split again.
if( cfg==def.cfg0() && def.nOuts()==1 && !singleReg )
return;
}
makeSplit(def,kind,round,lrg,umask).insertBefore(n, i);
// Skip split-of-split same block
if( skip && def instanceof SplitNode && cfg==def.cfg0() )
n.in(i).setDefOrdered(1,def.in(1));
}
void insertBefore(Node n, int i, String kind, byte round, LRG lrg) {
insertBefore(n,i,kind,round,lrg,true);
}
// Replace uses of `def` with `split`, and insert `split` immediately after
// `def` in the basic block.
public void insertAfterAndReplace( Node split, Node def, boolean must ) {
split.insertAfter(def);
if( split.nIns()>1 ) split.setDef(1,def);
for( int j=def.nOuts()-1; j>=0; j-- ) {
Node use = def.out(j);
if( use==split ) continue; // Skip self
// Can we avoid a split of a split? 'this' split is used by
// another split in the same block.
if( !must && use instanceof SplitNode split2 && sameBlockNoClobber(split2) )
continue;
int idx = use._inputs.find(def);
use.setDefOrdered(idx,split);
if( j < def.nOuts() ) j++;
}
}
private Node makeSplit( Node def, String kind, byte round, LRG lrg, RegMask umask ) {
// Clone simple constants if possible
Node split = def instanceof MachNode mach && mach.isClone() && (umask==null || mach.outregmap().overlap(umask))
? mach.copy()
: _code._mach.split(lrg,kind,round);
_lrgs.put(split,lrg);
return split;
}
private SplitNode makeSplit( String kind, byte round, LRG lrg ) {
SplitNode split = _code._mach.split(lrg,kind,round);
_lrgs.put(split,lrg);
return split;
}
// -----------------------
// POST PASS: Remove empty spills that biased-coloring made
private void postColor() {
int maxReg = -1;
for( CFGNode bb : _code._cfg ) { // For all ops
if( bb instanceof FunNode )
maxReg = -1; // Reset for new function
// Compute frame size, based on arguments and largest reg seen
if( bb instanceof ReturnNode ret )
ret.fun().computeFrameAdjust(_code,maxReg);
// Raise frame size by max stack args passed, even if ignored
if( bb instanceof CallEndMach cend )
maxReg = Math.max(maxReg,cend._xslot);
for( int j=0; j= 0;
}
}
}
// A sole, adjacent use can read the source register directly. Nothing can
// clobber it in between; only the operand mask and two-address tie remain.
private boolean splitForward( CFGNode bb, int j, Node split, int reg ) {
if( split.nOuts()!=1 || j+1==bb.nOuts() ) return false;
Node use = split.out(0);
if( use!=bb.out(j+1) || use instanceof PhiNode || use instanceof CFGNode ||
!(use instanceof MachNode mach) ) return false;
int i = use._inputs.find(split);
RegMask mask = mach.regmap(i);
return mask!=null && mask.test(reg) && mach.twoAddress()!=i;
}
private boolean splitBypass( CFGNode bb, int j, Node lo, int defreg ) {
// Attempt to bypass split
Node hi = lo.in(1);
while( true ) {
if( !(hi instanceof SplitNode && lo.cfg0() == hi.cfg0()) )
return false;
if( lrg(hi.in(1))._reg==defreg )
break;
hi = hi.in(1);
}
// Walk backward from the later copy, including instructions without results.
for( int idx = j-1; bb.out(idx) != hi; idx--) {
Node n = bb.out(idx);
if( clobbers(n,defreg) )
return false; // Clobbered
}
lo.setDefOrdered(1,hi.in(1));
return true;
}
private boolean clobbers(Node n, int reg) {
LRG lrg = lrg(n);
return lrg!=null && (lrg._reg==reg || lrg._reg==-1 && lrg._mask.size1() && lrg._mask.firstReg()==reg) ||
n instanceof MachNode mach && mach.killmap()!=null && mach.killmap().test(reg);
}
public boolean sameBlockNoClobber( SplitNode split ) {
Node def = split.in(1);
CFGNode cfg = def.cfg0();
if( cfg != split.cfg0() ) return false; // Not same block
// Get multinode head
Node def0 = def instanceof ProjNode ? def.in(0) : def;
int defreg = lrg(def)._reg;
if( defreg == -1 ) defreg = lrg(def)._mask.firstReg();
if( defreg == -1 ) return false; // no allowed registers -> clobbered
for( int idx = cfg._outputs.find(split) -1; idx >= 0; idx-- ) {
Node n = cfg.out(idx);
if( n==def0 ) return true; // No clobbers
if( lrg(n) == lrg(def) ) return false; // Self conflict
if( clobbers(n,defreg) )
return false; // Clobbered
}
throw Utils.TODO("Should not reach here");
}
}