diff options
Diffstat (limited to 'chapter28b/src/main/java/com')
6 files changed, 78 insertions, 18 deletions
diff --git a/chapter28b/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java b/chapter28b/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java index 24338927..c2cd657f 100644 --- a/chapter28b/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java +++ b/chapter28b/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java @@ -549,6 +549,68 @@ public class CodeGen { public CodeGen finalizeIR() { return phase(Phase.FinalizeIR, _phase==Phase.IterPostOpto, () -> FinalizeIR.run(this)); } + // Cross-phase `keep()`s belong here! We have a few nodes which we can + // lose all uses of, but later find them and want to re-add output edges. + // If we don't `keep()` them, they'll dead-code-elimnate. + + // These are mostly around adding and removal functions from other + // compilation units. E.g. a StartCU, when all his functions are exactly + // called with no escapes and no default input. Those functions will still + // go out in their own ELF, and will later hook on to the StartCU during + // code emission. Between e.g. Opto and code emission the StartCU has no + // direct users - but we're going to want the StartCU at elf-writer time! + + // The Parser and `idealize()` `keep()`s remain locally balanced. + + // We make a few collections of these cross-phase `keep()`s. + + // A few more cross-phase example lifetimes: + + // Until FinalizeIR: Opto and import loading retain StartCU's memory + // projection while unknown-caller edges disappear and may be rebuilt. + // CompUnit._start.proj(1) finds it; Opto.linkStart uses it for the memory + // Parm, found by scanning fun._outputs. FinalizeIR first holds the StartCU + // itself, then releases these projections and queues ordinary dead cleanup. + + // Until Unlink: FinalizeIR retains StartCU even if it has no users left. + // CompUnit._start is the non-graph reference used by Serialize and Unlink. + // Unlink.run/forEval install scheduling edges before releasing these holds. + // A Java reference alone does not protect a node from graph deletion. + // + // Replacement is the exception to those release points: ParseAll must + // release our old wrapper/projection holds before subsuming the wrapper. + // It registers the replacement projection for the next FinalizeIR call. + private final Ary<Node> _keptUntilFinalize = new Ary<>(Node.class); + private final Ary<Node> _keptUntilUnlink = new Ary<>(Node.class); + + void keepUntilFinalize(Node n) { keepOnce(_keptUntilFinalize,n); } + void keepUntilUnlink (Node n) { keepOnce(_keptUntilUnlink ,n); } + private static void keepOnce(Ary<Node> kept, Node n) { + if( kept.find(n)==-1 ) kept.add(n.keep()); + } + + // FinalizeIR has retained the wrappers; unused projections may now die. + void releaseFinalizedNodes() { + for( Node n : _keptUntilFinalize ) add(n.unkeep()); + _keptUntilFinalize.clear(); + } + + // Unlink has installed the scheduling edges. No optimization is needed. + void releaseUnlinkedNodes() { + for( Node n : _keptUntilUnlink ) n.unkeep(); + _keptUntilUnlink.clear(); + } + + // Only release our holds. A caller's temporary keep is a separate owner. + void releaseReplacedNode(Node n) { + releaseReplacedNode(_keptUntilFinalize,n); + releaseReplacedNode(_keptUntilUnlink,n); + } + private static void releaseReplacedNode(Ary<Node> kept, Node n) { + int idx = kept.find(n); + if( idx!=-1 ) kept.del(idx).unkeep(); + } + // Root a surviving function without claiming that it has unknown callers. public void keepInCompUnit(FunNode fun) { StopCUNode stop = fun._compunit._stop; diff --git a/chapter28b/src/main/java/com/seaofnodes/simple/codegen/FinalizeIR.java b/chapter28b/src/main/java/com/seaofnodes/simple/codegen/FinalizeIR.java index aeb64543..69e0104d 100644 --- a/chapter28b/src/main/java/com/seaofnodes/simple/codegen/FinalizeIR.java +++ b/chapter28b/src/main/java/com/seaofnodes/simple/codegen/FinalizeIR.java @@ -31,13 +31,10 @@ abstract class FinalizeIR { } code.keepInCompUnit(fun); } - for( CompUnit cu : code._compunits.values() ) { - // A private unit may have no unknown callers, hence no Fun using - // its Start. Keep the wrapper until Unlink installs scheduling hooks. - if( !cu._start.iskeep() ) cu._start.keep(); - ProjNode mem = cu._start.proj(1); - if( mem!=null && mem.iskeep() ) code.add(mem.unkeep()); - } + // Hand off the wrapper lifetime before releasing its memory projection. + for( CompUnit cu : code._compunits.values() ) + code.keepUntilUnlink(cu._start); + code.releaseFinalizedNodes(); // Stable print order: initializers first, then other Returns by NID. for( Node stop : code._stop._inputs ) Arrays.sort(stop._inputs._es,0,stop.nIns(), diff --git a/chapter28b/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java b/chapter28b/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java index 8708818b..42999894 100644 --- a/chapter28b/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java +++ b/chapter28b/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java @@ -12,7 +12,7 @@ public abstract class GlobalCodeMotion { // Ideal execution needs a schedule, but neither CLInit lowering nor // interprocedural optimization. Each loaded object is prepared once. public static Ary<CFGNode> forEval(CodeGen code, StartNode start, StopNode stop) { - Ary<FunNode> funs = Unlink.forEval(start,stop); + Ary<FunNode> funs = Unlink.forEval(code,start,stop); start.buildLoopTree(funs,stop); return buildCFG(code,start,stop); } diff --git a/chapter28b/src/main/java/com/seaofnodes/simple/codegen/Opto.java b/chapter28b/src/main/java/com/seaofnodes/simple/codegen/Opto.java index 19450e86..440ffabb 100644 --- a/chapter28b/src/main/java/com/seaofnodes/simple/codegen/Opto.java +++ b/chapter28b/src/main/java/com/seaofnodes/simple/codegen/Opto.java @@ -59,10 +59,10 @@ abstract public class Opto { // Only entry initializers start with unknown callers. Discover every // other function through calls or escaping values during SCCP. private static void unlinkStart(CodeGen code) { - // Keep the Start & Start.ProjMem alive even as we remove all uses + // CodeGen owns these until FinalizeIR; linkStart may need them again. for( CompUnit cu : code._compunits.values() ) { ProjNode mem = cu._start.proj(1); - if( mem!=null ) mem.keep(); + if( mem!=null ) code.keepUntilFinalize(mem); } // Iterate all functions. diff --git a/chapter28b/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java b/chapter28b/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java index 996d5b8b..10b8fd9b 100644 --- a/chapter28b/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java +++ b/chapter28b/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java @@ -289,7 +289,7 @@ public abstract class ParseAll { StartCUNode oldStart = cunit._start; StopCUNode oldStop = cunit._stop; ProjNode oldMem = oldStart==null ? null : oldStart.proj(1); - if( oldMem!=null && oldMem.iskeep() ) oldMem.unkeep(); + if( oldMem!=null ) code.releaseReplacedNode(oldMem); elf.loadSimple(code); cunit._clz = (TypeStruct)Parser.TYPES.get(Parser.addClzPrefix(cunit._cname)); StartNode lstart = (StartNode)elf._nodes.at(0); @@ -328,7 +328,7 @@ public abstract class ParseAll { code._stop.addDef(lstopcu); lstart.subsume(code._start); if( oldStart!=null && oldStart!=cunit._start ) { - if( oldStart.iskeep() ) oldStart.unkeep(); + code.releaseReplacedNode(oldStart); int idx = code._stop._inputs.find(oldStop); if( idx!=-1 ) code._stop.delDef(idx); oldStart.subsume(cunit._start); @@ -337,7 +337,7 @@ public abstract class ParseAll { // only the newly loaded graph; ordinary dependencies carry changes // into its callers without restarting whole-program analysis. ProjNode startMem = cunit._start.proj(1); - if( startMem!=null && !startMem.iskeep() ) startMem.keep(); + if( startMem!=null ) code.keepUntilFinalize(startMem); Iter.imports(code,elf._nodes); // Add the new top-level loaded class diff --git a/chapter28b/src/main/java/com/seaofnodes/simple/codegen/Unlink.java b/chapter28b/src/main/java/com/seaofnodes/simple/codegen/Unlink.java index fe3710e1..bb9a6196 100644 --- a/chapter28b/src/main/java/com/seaofnodes/simple/codegen/Unlink.java +++ b/chapter28b/src/main/java/com/seaofnodes/simple/codegen/Unlink.java @@ -8,7 +8,7 @@ import com.seaofnodes.simple.util.Ary; abstract class Unlink { // Disconnect compiler-only interprocedural edges before scheduling. Keep // signatures in the linker; runtime calls resolve FIDXs, not graph edges. - static Ary<FunNode> forEval(StartNode start, StopNode stop) { + static Ary<FunNode> forEval(CodeGen code, StartNode start, StopNode stop) { Ary<Node> nodes = new Ary<>(Node.class); start.walk(n -> { nodes.add(n); return null; }); Ary<FunNode> funs = new Ary<>(FunNode.class); @@ -35,8 +35,9 @@ abstract class Unlink { } restoreRPC(fun); } + code.releaseUnlinkedNodes(); for( Node n : nodes ) - if( n instanceof StartCUNode unitStart ) releaseStart(unitStart); + if( n instanceof StartCUNode unitStart ) detachEmptyStart(unitStart); // A summary is a dispatch target, not a schedulable implementation. // Retain its own wrapper graph without including it in this schedule. for( int i = 0; i<stop.nIns(); i++ ) { @@ -139,8 +140,9 @@ abstract class Unlink { ((CallNode)fun.in(i)).unlink(fun,i--); restoreRPC(fun); } + code.releaseUnlinkedNodes(); for( CompUnit cu : code._compunits.values() ) - if( cu._start!=null && !cu._start.isDead() ) releaseStart(cu._start); + if( cu._start!=null && !cu._start.isDead() ) detachEmptyStart(cu._start); code._stop.walk(n -> { if( n instanceof CallEndNode cend ) @@ -153,8 +155,7 @@ abstract class Unlink { }); } - private static void releaseStart(StartCUNode start) { - if( start.iskeep() ) start.unkeep(); + private static void detachEmptyStart(StartCUNode start) { // An unused unit can lose its outer Stop edge during optimization. // Detach its empty wrapper from the scheduling root as well. if( start.in(1) instanceof StopCUNode stop && stop.nIns()==0 ) start.setDef(0,null); |
