summaryrefslogtreecommitdiff
path: root/chapter28b/src
diff options
context:
space:
mode:
Diffstat (limited to 'chapter28b/src')
-rw-r--r--chapter28b/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java62
-rw-r--r--chapter28b/src/main/java/com/seaofnodes/simple/codegen/FinalizeIR.java11
-rw-r--r--chapter28b/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java2
-rw-r--r--chapter28b/src/main/java/com/seaofnodes/simple/codegen/Opto.java4
-rw-r--r--chapter28b/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java6
-rw-r--r--chapter28b/src/main/java/com/seaofnodes/simple/codegen/Unlink.java11
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);