summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--chapter13/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java50
-rw-r--r--chapter13/src/main/java/com/seaofnodes/simple/IterPeeps.java4
-rw-r--r--chapter13/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java32
-rw-r--r--chapter14/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java50
-rw-r--r--chapter14/src/main/java/com/seaofnodes/simple/IterPeeps.java4
-rw-r--r--chapter14/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java32
-rw-r--r--chapter15/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java56
-rw-r--r--chapter15/src/main/java/com/seaofnodes/simple/IterPeeps.java4
-rw-r--r--chapter15/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java31
-rw-r--r--chapter16/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java56
-rw-r--r--chapter16/src/main/java/com/seaofnodes/simple/IterPeeps.java4
-rw-r--r--chapter16/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java31
-rw-r--r--chapter17a/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java56
-rw-r--r--chapter17a/src/main/java/com/seaofnodes/simple/IterPeeps.java4
-rw-r--r--chapter17a/src/main/java/com/seaofnodes/simple/node/CFGNode.java6
-rw-r--r--chapter17a/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java31
-rw-r--r--chapter17b/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java56
-rw-r--r--chapter17b/src/main/java/com/seaofnodes/simple/IterPeeps.java4
-rw-r--r--chapter17b/src/main/java/com/seaofnodes/simple/node/CFGNode.java6
-rw-r--r--chapter17b/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java31
-rw-r--r--chapter18/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java56
-rw-r--r--chapter18/src/main/java/com/seaofnodes/simple/IterPeeps.java4
-rw-r--r--chapter18/src/main/java/com/seaofnodes/simple/node/CFGNode.java6
-rw-r--r--chapter18/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java29
-rw-r--r--chapter19/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java55
-rw-r--r--chapter19/src/main/java/com/seaofnodes/simple/IterPeeps.java4
-rw-r--r--chapter19/src/main/java/com/seaofnodes/simple/node/CFGNode.java4
-rw-r--r--chapter19/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java31
-rw-r--r--chapter20/src/main/java/com/seaofnodes/simple/IterPeeps.java4
-rw-r--r--chapter20/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java55
-rw-r--r--chapter20/src/main/java/com/seaofnodes/simple/node/CFGNode.java4
-rw-r--r--chapter20/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java32
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/IterPeeps.java4
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java55
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/CFGNode.java4
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/BrainFuckTest.java2
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java32
-rw-r--r--chapter22/src/main/java/com/seaofnodes/simple/IterPeeps.java4
-rw-r--r--chapter22/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java55
-rw-r--r--chapter22/src/main/java/com/seaofnodes/simple/node/CFGNode.java4
-rw-r--r--chapter22/src/test/java/com/seaofnodes/simple/Chapter21Test.java2
-rw-r--r--chapter22/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java32
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/IterPeeps.java4
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java62
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/CFGNode.java4
-rw-r--r--chapter23/src/test/java/com/seaofnodes/simple/Chapter21Test.java2
-rw-r--r--chapter23/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java32
-rw-r--r--chapter24a/src/main/java/com/seaofnodes/simple/IterPeeps.java4
-rw-r--r--chapter24a/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java62
-rw-r--r--chapter24a/src/main/java/com/seaofnodes/simple/node/CFGNode.java4
-rw-r--r--chapter24a/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java32
-rw-r--r--chapter24b/src/main/java/com/seaofnodes/simple/IterPeeps.java4
-rw-r--r--chapter24b/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java62
-rw-r--r--chapter24b/src/main/java/com/seaofnodes/simple/node/CFGNode.java4
-rw-r--r--chapter24b/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java32
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/IterPeeps.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java62
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/CFGNode.java4
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java32
-rw-r--r--chapter26/src/main/java/com/seaofnodes/simple/IterPeeps.java2
-rw-r--r--chapter26/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java62
-rw-r--r--chapter26/src/main/java/com/seaofnodes/simple/node/CFGNode.java4
-rw-r--r--chapter26/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java32
-rw-r--r--chapter27/src/main/java/com/seaofnodes/simple/IterPeeps.java2
-rw-r--r--chapter27/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java62
-rw-r--r--chapter27/src/main/java/com/seaofnodes/simple/node/CFGNode.java4
-rw-r--r--chapter27/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java32
-rw-r--r--chapter28a/src/main/java/com/seaofnodes/simple/IterPeeps.java15
-rw-r--r--chapter28a/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java74
-rw-r--r--chapter28a/src/main/java/com/seaofnodes/simple/node/CFGNode.java3
-rw-r--r--chapter28a/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java32
-rw-r--r--chapter28b/src/main/java/com/seaofnodes/simple/IterPeeps.java15
-rw-r--r--chapter28b/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java74
-rw-r--r--chapter28b/src/main/java/com/seaofnodes/simple/codegen/IFG.java14
-rw-r--r--chapter28b/src/main/java/com/seaofnodes/simple/node/CFGNode.java3
-rw-r--r--chapter28b/src/test/java/com/seaofnodes/simple/PhaseTest.java23
-rw-r--r--chapter28b/src/test/java/com/seaofnodes/simple/SelectiveLoadingTest.java5
-rw-r--r--docs/roadmap.md7
78 files changed, 1543 insertions, 356 deletions
diff --git a/chapter13/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java b/chapter13/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java
index b431f82c..888c1a5e 100644
--- a/chapter13/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java
+++ b/chapter13/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java
@@ -111,24 +111,38 @@ public abstract class GlobalCodeMotion {
}
// ------------------------------------------------------------------------
+ // Scheduling is not recursive. Retain scratch capacity between schedules,
+ // but release graph references and reset only the entries we used.
+ private static CFGNode[] LATE = new CFGNode[1];
+ // Load NIDs at their CFG block choices, checked against Store blocks.
+ private static int[] ANTI = new int[1];
+ private static final ArrayList<Node> TOUCHED = new ArrayList<>();
+ private static final ArrayList<Node> PLACED = new ArrayList<>();
+ private static final WorkList<Node> WORK = new WorkList<>();
+
private static void schedLate( StopNode stop) {
- CFGNode[] late = new CFGNode[Node.UID()];
- Node[] ns = new Node[Node.UID()];
- // Record Load NIDs at all their CFG block choices, then check against
- // Store block choices to force a Load above an anti-dependent Store.
- int[] anti = new int[Node.UID()];
+ assert TOUCHED.isEmpty() && PLACED.isEmpty() && WORK.isEmpty();
+ if( Node.UID() > LATE.length ) {
+ int len = Math.max(Node.UID(),LATE.length<<1);
+ LATE = new CFGNode[len];
+ ANTI = new int[len];
+ }
+ WORK.clear();
// Breadth-first scheduling
- breadth(stop,ns,late,anti);
+ breadth(stop,PLACED,LATE,ANTI);
// Copy the best placement choice into the control slot
- for( int i=0; i<late.length; i++ )
- if( ns[i] != null )
- ns[i].setDef(0,late[i]);
+ for( Node n : PLACED )
+ n.setDef(0,LATE[n._nid]);
+ for( Node n : TOUCHED )
+ LATE[n._nid] = null;
+ TOUCHED.clear();
+ PLACED.clear();
}
- private static void breadth(Node stop, Node[] ns, CFGNode[] late, int[] anti) {
+ private static void breadth(Node stop, ArrayList<Node> placed, CFGNode[] late, int[] anti) {
// Things on the worklist have some (but perhaps not all) uses done.
- WorkList<Node> work = new WorkList<>();
+ WorkList<Node> work = WORK;
work.push(stop);
Node n;
outer:
@@ -153,9 +167,11 @@ public abstract class GlobalCodeMotion {
continue outer;
// All uses done, schedule
- _doSchedLate(n,ns,late,anti);
+ _doSchedLate(n,placed,late,anti);
}
+ TOUCHED.add(n);
+
// A use just finished; reconsider its inputs and waiting loads,
// even when the shared memory input was already scheduled.
for( Node def : n._inputs ) {
@@ -172,7 +188,7 @@ public abstract class GlobalCodeMotion {
}
}
- private static void _doSchedLate(Node n, Node[] ns, CFGNode[] late, int[] anti) {
+ private static void _doSchedLate(Node n, ArrayList<Node> placed, CFGNode[] late, int[] anti) {
// Walk uses, gathering the LCA (Least Common Ancestor) of uses
CFGNode early = (CFGNode)n.in(0);
assert early != null;
@@ -193,7 +209,7 @@ public abstract class GlobalCodeMotion {
if( better(lca,best) )
best = lca;
assert !(best instanceof IfNode);
- ns [n._nid] = n;
+ placed.add(n);
late[n._nid] = best;
}
@@ -233,7 +249,8 @@ public abstract class GlobalCodeMotion {
private static CFGNode find_anti_dep(CFGNode lca, LoadNode load, CFGNode early, CFGNode[] late, int[] anti) {
// We could skip final-field loads here.
// Walk LCA->early, flagging Load's block location choices
- for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
+ CFGNode marked = lca;
+ for( CFGNode cfg = marked; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
anti[cfg._nid] = load._nid;
// Walk load->mem uses, looking for Stores causing an anti-dep
for( Node mem : load.mem()._outputs ) {
@@ -254,6 +271,9 @@ public abstract class GlobalCodeMotion {
default: throw Utils.TODO();
}
}
+ // Release this load's marks before another compilation reuses IDs.
+ for( CFGNode cfg = marked; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
+ anti[cfg._nid] = 0;
return lca;
}
diff --git a/chapter13/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter13/src/main/java/com/seaofnodes/simple/IterPeeps.java
index 7075d526..f7be3ad8 100644
--- a/chapter13/src/main/java/com/seaofnodes/simple/IterPeeps.java
+++ b/chapter13/src/main/java/com/seaofnodes/simple/IterPeeps.java
@@ -194,11 +194,15 @@ public abstract class IterPeeps {
int idx = _R.nextInt(_len);
E x = (E)_es[idx];
_es[idx] = _es[--_len]; // Compress array
+ _es[_len] = null;
_on.clear(x._nid);
return x;
}
+ public boolean isEmpty() { return _len==0; }
+
public void clear() {
+ Arrays.fill(_es,0,_len,null);
_len = 0;
_on.clear();
_R.setSeed(_seed);
diff --git a/chapter13/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java b/chapter13/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java
new file mode 100644
index 00000000..6acdcd52
--- /dev/null
+++ b/chapter13/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java
@@ -0,0 +1,32 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.evaluator.Evaluator;
+import com.seaofnodes.simple.node.StopNode;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+public class SchedulingScratchTest {
+ @Test public void testScratchAcrossCompilations() {
+ String src = """
+ struct Box { int x; };
+ Box b = new Box;
+ b.x = arg;
+ int sum = 0;
+ int i = 0;
+ while( i<5 ) {
+ int old = b.x;
+ b.x = i;
+ sum = sum + old;
+ i = i + 1;
+ }
+ return sum;
+ """;
+ // Each fresh compilation reuses node IDs and the scheduler's scratch.
+ for( int round=0; round<3; round++ ) {
+ StopNode stop = new Parser(src).parse().iterate();
+ assertEquals(6L,Evaluator.evaluate(stop,0));
+ assertEquals(7L,Evaluator.evaluate(stop,1));
+ assertEquals(8L,Evaluator.evaluate(stop,2));
+ }
+ }
+}
diff --git a/chapter14/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java b/chapter14/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java
index b431f82c..888c1a5e 100644
--- a/chapter14/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java
+++ b/chapter14/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java
@@ -111,24 +111,38 @@ public abstract class GlobalCodeMotion {
}
// ------------------------------------------------------------------------
+ // Scheduling is not recursive. Retain scratch capacity between schedules,
+ // but release graph references and reset only the entries we used.
+ private static CFGNode[] LATE = new CFGNode[1];
+ // Load NIDs at their CFG block choices, checked against Store blocks.
+ private static int[] ANTI = new int[1];
+ private static final ArrayList<Node> TOUCHED = new ArrayList<>();
+ private static final ArrayList<Node> PLACED = new ArrayList<>();
+ private static final WorkList<Node> WORK = new WorkList<>();
+
private static void schedLate( StopNode stop) {
- CFGNode[] late = new CFGNode[Node.UID()];
- Node[] ns = new Node[Node.UID()];
- // Record Load NIDs at all their CFG block choices, then check against
- // Store block choices to force a Load above an anti-dependent Store.
- int[] anti = new int[Node.UID()];
+ assert TOUCHED.isEmpty() && PLACED.isEmpty() && WORK.isEmpty();
+ if( Node.UID() > LATE.length ) {
+ int len = Math.max(Node.UID(),LATE.length<<1);
+ LATE = new CFGNode[len];
+ ANTI = new int[len];
+ }
+ WORK.clear();
// Breadth-first scheduling
- breadth(stop,ns,late,anti);
+ breadth(stop,PLACED,LATE,ANTI);
// Copy the best placement choice into the control slot
- for( int i=0; i<late.length; i++ )
- if( ns[i] != null )
- ns[i].setDef(0,late[i]);
+ for( Node n : PLACED )
+ n.setDef(0,LATE[n._nid]);
+ for( Node n : TOUCHED )
+ LATE[n._nid] = null;
+ TOUCHED.clear();
+ PLACED.clear();
}
- private static void breadth(Node stop, Node[] ns, CFGNode[] late, int[] anti) {
+ private static void breadth(Node stop, ArrayList<Node> placed, CFGNode[] late, int[] anti) {
// Things on the worklist have some (but perhaps not all) uses done.
- WorkList<Node> work = new WorkList<>();
+ WorkList<Node> work = WORK;
work.push(stop);
Node n;
outer:
@@ -153,9 +167,11 @@ public abstract class GlobalCodeMotion {
continue outer;
// All uses done, schedule
- _doSchedLate(n,ns,late,anti);
+ _doSchedLate(n,placed,late,anti);
}
+ TOUCHED.add(n);
+
// A use just finished; reconsider its inputs and waiting loads,
// even when the shared memory input was already scheduled.
for( Node def : n._inputs ) {
@@ -172,7 +188,7 @@ public abstract class GlobalCodeMotion {
}
}
- private static void _doSchedLate(Node n, Node[] ns, CFGNode[] late, int[] anti) {
+ private static void _doSchedLate(Node n, ArrayList<Node> placed, CFGNode[] late, int[] anti) {
// Walk uses, gathering the LCA (Least Common Ancestor) of uses
CFGNode early = (CFGNode)n.in(0);
assert early != null;
@@ -193,7 +209,7 @@ public abstract class GlobalCodeMotion {
if( better(lca,best) )
best = lca;
assert !(best instanceof IfNode);
- ns [n._nid] = n;
+ placed.add(n);
late[n._nid] = best;
}
@@ -233,7 +249,8 @@ public abstract class GlobalCodeMotion {
private static CFGNode find_anti_dep(CFGNode lca, LoadNode load, CFGNode early, CFGNode[] late, int[] anti) {
// We could skip final-field loads here.
// Walk LCA->early, flagging Load's block location choices
- for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
+ CFGNode marked = lca;
+ for( CFGNode cfg = marked; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
anti[cfg._nid] = load._nid;
// Walk load->mem uses, looking for Stores causing an anti-dep
for( Node mem : load.mem()._outputs ) {
@@ -254,6 +271,9 @@ public abstract class GlobalCodeMotion {
default: throw Utils.TODO();
}
}
+ // Release this load's marks before another compilation reuses IDs.
+ for( CFGNode cfg = marked; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
+ anti[cfg._nid] = 0;
return lca;
}
diff --git a/chapter14/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter14/src/main/java/com/seaofnodes/simple/IterPeeps.java
index 7075d526..f7be3ad8 100644
--- a/chapter14/src/main/java/com/seaofnodes/simple/IterPeeps.java
+++ b/chapter14/src/main/java/com/seaofnodes/simple/IterPeeps.java
@@ -194,11 +194,15 @@ public abstract class IterPeeps {
int idx = _R.nextInt(_len);
E x = (E)_es[idx];
_es[idx] = _es[--_len]; // Compress array
+ _es[_len] = null;
_on.clear(x._nid);
return x;
}
+ public boolean isEmpty() { return _len==0; }
+
public void clear() {
+ Arrays.fill(_es,0,_len,null);
_len = 0;
_on.clear();
_R.setSeed(_seed);
diff --git a/chapter14/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java b/chapter14/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java
new file mode 100644
index 00000000..6acdcd52
--- /dev/null
+++ b/chapter14/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java
@@ -0,0 +1,32 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.evaluator.Evaluator;
+import com.seaofnodes.simple.node.StopNode;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+public class SchedulingScratchTest {
+ @Test public void testScratchAcrossCompilations() {
+ String src = """
+ struct Box { int x; };
+ Box b = new Box;
+ b.x = arg;
+ int sum = 0;
+ int i = 0;
+ while( i<5 ) {
+ int old = b.x;
+ b.x = i;
+ sum = sum + old;
+ i = i + 1;
+ }
+ return sum;
+ """;
+ // Each fresh compilation reuses node IDs and the scheduler's scratch.
+ for( int round=0; round<3; round++ ) {
+ StopNode stop = new Parser(src).parse().iterate();
+ assertEquals(6L,Evaluator.evaluate(stop,0));
+ assertEquals(7L,Evaluator.evaluate(stop,1));
+ assertEquals(8L,Evaluator.evaluate(stop,2));
+ }
+ }
+}
diff --git a/chapter15/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java b/chapter15/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java
index 4c9fc779..4e836ca4 100644
--- a/chapter15/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java
+++ b/chapter15/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java
@@ -112,24 +112,41 @@ public abstract class GlobalCodeMotion {
}
// ------------------------------------------------------------------------
+ private static final BitSet WAKE_VISIT = new BitSet();
+
+ // Scheduling is not recursive. Retain scratch capacity between schedules,
+ // but release graph references and reset only the entries we used.
+ private static CFGNode[] LATE = new CFGNode[1];
+ // Load NIDs at their CFG block choices, checked against Store blocks.
+ private static int[] ANTI = new int[1];
+ private static final ArrayList<Node> TOUCHED = new ArrayList<>();
+ private static final ArrayList<Node> PLACED = new ArrayList<>();
+ private static final WorkList<Node> WORK = new WorkList<>();
+
private static void schedLate( StopNode stop) {
- CFGNode[] late = new CFGNode[Node.UID()];
- Node[] ns = new Node[Node.UID()];
- // Record Load NIDs at all their CFG block choices, then check against
- // Store block choices to force a Load above an anti-dependent Store.
- int[] anti = new int[Node.UID()];
+ assert TOUCHED.isEmpty() && PLACED.isEmpty() && WORK.isEmpty();
+ if( Node.UID() > LATE.length ) {
+ int len = Math.max(Node.UID(),LATE.length<<1);
+ LATE = new CFGNode[len];
+ ANTI = new int[len];
+ }
+ WORK.clear();
// Breadth-first scheduling
- breadth(stop,ns,late,anti);
+ breadth(stop,PLACED,LATE,ANTI,WAKE_VISIT);
// Copy the best placement choice into the control slot
- for( int i=0; i<late.length; i++ )
- if( ns[i] != null && !(ns[i] instanceof ProjNode) )
- ns[i].setDef(0,late[i]);
+ for( Node n : PLACED )
+ if( !(n instanceof ProjNode) )
+ n.setDef(0,LATE[n._nid]);
+ for( Node n : TOUCHED )
+ LATE[n._nid] = null;
+ TOUCHED.clear();
+ PLACED.clear();
}
- private static void breadth(Node stop, Node[] ns, CFGNode[] late, int[] anti) {
+ private static void breadth(Node stop, ArrayList<Node> placed, CFGNode[] late, int[] anti, BitSet visit) {
// Things on the worklist have some (but perhaps not all) uses done.
- WorkList<Node> work = new WorkList<>();
+ WorkList<Node> work = WORK;
work.push(stop);
Node n;
outer:
@@ -153,16 +170,19 @@ public abstract class GlobalCodeMotion {
continue outer;
// All uses done, schedule
- _doSchedLate(n,ns,late,anti);
+ _doSchedLate(n,placed,late,anti);
}
+ TOUCHED.add(n);
+
// A use just finished; reconsider its inputs and waiting loads,
// even when the shared memory input was already scheduled.
for( Node def : n._inputs ) {
if( def==null ) continue;
if( late[def._nid]==null ) work.push(def);
- wakeLoads(def,late,work,new BitSet());
+ wakeLoads(def,late,work,visit);
}
+ visit.clear();
if( n instanceof LoopNode loop )
for( Node phi : loop._outputs )
if( phi instanceof PhiNode && late[phi._nid]==null )
@@ -170,7 +190,7 @@ public abstract class GlobalCodeMotion {
}
}
- private static void _doSchedLate(Node n, Node[] ns, CFGNode[] late, int[] anti) {
+ private static void _doSchedLate(Node n, ArrayList<Node> placed, CFGNode[] late, int[] anti) {
// Walk uses, gathering the LCA (Least Common Ancestor) of uses
CFGNode early = n.in(0) instanceof CFGNode cfg ? cfg : n.in(0).cfg0();
assert early != null;
@@ -192,7 +212,7 @@ public abstract class GlobalCodeMotion {
if( better(lca,best) )
best = lca;
assert !(best instanceof IfNode);
- ns [n._nid] = n;
+ placed.add(n);
late[n._nid] = best;
}
@@ -232,7 +252,8 @@ public abstract class GlobalCodeMotion {
private static CFGNode find_anti_dep(CFGNode lca, LoadNode load, CFGNode early, CFGNode[] late, int[] anti) {
// We could skip final-field loads here.
// Walk LCA->early, flagging Load's block location choices
- for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
+ CFGNode marked = lca;
+ for( CFGNode cfg = marked; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
anti[cfg._nid] = load._nid;
// Walk load->mem uses, looking for Stores causing an anti-dep
for( Node mem : load.antiDeps() ) {
@@ -256,6 +277,9 @@ public abstract class GlobalCodeMotion {
default: throw Utils.TODO();
}
}
+ // Release this load's marks before another compilation reuses IDs.
+ for( CFGNode cfg = marked; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
+ anti[cfg._nid] = 0;
return lca;
}
diff --git a/chapter15/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter15/src/main/java/com/seaofnodes/simple/IterPeeps.java
index 7075d526..f7be3ad8 100644
--- a/chapter15/src/main/java/com/seaofnodes/simple/IterPeeps.java
+++ b/chapter15/src/main/java/com/seaofnodes/simple/IterPeeps.java
@@ -194,11 +194,15 @@ public abstract class IterPeeps {
int idx = _R.nextInt(_len);
E x = (E)_es[idx];
_es[idx] = _es[--_len]; // Compress array
+ _es[_len] = null;
_on.clear(x._nid);
return x;
}
+ public boolean isEmpty() { return _len==0; }
+
public void clear() {
+ Arrays.fill(_es,0,_len,null);
_len = 0;
_on.clear();
_R.setSeed(_seed);
diff --git a/chapter15/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java b/chapter15/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java
new file mode 100644
index 00000000..b3984f15
--- /dev/null
+++ b/chapter15/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java
@@ -0,0 +1,31 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.evaluator.Evaluator;
+import com.seaofnodes.simple.node.StopNode;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+public class SchedulingScratchTest {
+ @Test public void testScratchAcrossCompilations() {
+ String src = """
+ int[] xs = new int[2];
+ xs[0] = arg;
+ int sum = 0;
+ int i = 0;
+ while( i<5 ) {
+ int old = xs[arg&1];
+ xs[0] = i;
+ sum = sum + old;
+ i = i + 1;
+ }
+ return sum;
+ """;
+ // Each fresh compilation reuses node IDs and the scheduler's scratch.
+ for( int round=0; round<3; round++ ) {
+ StopNode stop = new Parser(src).parse().iterate();
+ assertEquals(6L,Evaluator.evaluate(stop,0));
+ assertEquals(0L,Evaluator.evaluate(stop,1));
+ assertEquals(8L,Evaluator.evaluate(stop,2));
+ }
+ }
+}
diff --git a/chapter16/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java b/chapter16/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java
index e153fcc7..73c53123 100644
--- a/chapter16/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java
+++ b/chapter16/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java
@@ -112,24 +112,41 @@ public abstract class GlobalCodeMotion {
}
// ------------------------------------------------------------------------
+ private static final BitSet WAKE_VISIT = new BitSet();
+
+ // Scheduling is not recursive. Retain scratch capacity between schedules,
+ // but release graph references and reset only the entries we used.
+ private static CFGNode[] LATE = new CFGNode[1];
+ // Load NIDs at their CFG block choices, checked against Store blocks.
+ private static int[] ANTI = new int[1];
+ private static final ArrayList<Node> TOUCHED = new ArrayList<>();
+ private static final ArrayList<Node> PLACED = new ArrayList<>();
+ private static final WorkList<Node> WORK = new WorkList<>();
+
private static void schedLate( StopNode stop) {
- CFGNode[] late = new CFGNode[Node.UID()];
- Node[] ns = new Node[Node.UID()];
- // Record Load NIDs at all their CFG block choices, then check against
- // Store block choices to force a Load above an anti-dependent Store.
- int[] anti = new int[Node.UID()];
+ assert TOUCHED.isEmpty() && PLACED.isEmpty() && WORK.isEmpty();
+ if( Node.UID() > LATE.length ) {
+ int len = Math.max(Node.UID(),LATE.length<<1);
+ LATE = new CFGNode[len];
+ ANTI = new int[len];
+ }
+ WORK.clear();
// Breadth-first scheduling
- breadth(stop,ns,late,anti);
+ breadth(stop,PLACED,LATE,ANTI,WAKE_VISIT);
// Copy the best placement choice into the control slot
- for( int i=0; i<late.length; i++ )
- if( ns[i] != null && !(ns[i] instanceof ProjNode) )
- ns[i].setDef(0,late[i]);
+ for( Node n : PLACED )
+ if( !(n instanceof ProjNode) )
+ n.setDef(0,LATE[n._nid]);
+ for( Node n : TOUCHED )
+ LATE[n._nid] = null;
+ TOUCHED.clear();
+ PLACED.clear();
}
- private static void breadth(Node stop, Node[] ns, CFGNode[] late, int[] anti) {
+ private static void breadth(Node stop, ArrayList<Node> placed, CFGNode[] late, int[] anti, BitSet visit) {
// Things on the worklist have some (but perhaps not all) uses done.
- WorkList<Node> work = new WorkList<>();
+ WorkList<Node> work = WORK;
work.push(stop);
Node n;
outer:
@@ -153,16 +170,19 @@ public abstract class GlobalCodeMotion {
continue outer;
// All uses done, schedule
- _doSchedLate(n,ns,late,anti);
+ _doSchedLate(n,placed,late,anti);
}
+ TOUCHED.add(n);
+
// A use just finished; reconsider its inputs and waiting loads,
// even when the shared memory input was already scheduled.
for( Node def : n._inputs ) {
if( def==null ) continue;
if( late[def._nid]==null ) work.push(def);
- wakeLoads(def,late,work,new BitSet());
+ wakeLoads(def,late,work,visit);
}
+ visit.clear();
if( n instanceof LoopNode loop )
for( Node phi : loop._outputs )
if( phi instanceof PhiNode && late[phi._nid]==null )
@@ -170,7 +190,7 @@ public abstract class GlobalCodeMotion {
}
}
- private static void _doSchedLate(Node n, Node[] ns, CFGNode[] late, int[] anti) {
+ private static void _doSchedLate(Node n, ArrayList<Node> placed, CFGNode[] late, int[] anti) {
// Walk uses, gathering the LCA (Least Common Ancestor) of uses
CFGNode early = n.in(0) instanceof CFGNode cfg ? cfg : n.in(0).cfg0();
assert early != null;
@@ -192,7 +212,7 @@ public abstract class GlobalCodeMotion {
if( better(lca,best) )
best = lca;
assert !(best instanceof IfNode);
- ns [n._nid] = n;
+ placed.add(n);
late[n._nid] = best;
}
@@ -232,7 +252,8 @@ public abstract class GlobalCodeMotion {
private static CFGNode find_anti_dep(CFGNode lca, LoadNode load, CFGNode early, CFGNode[] late, int[] anti) {
// We could skip final-field loads here.
// Walk LCA->early, flagging Load's block location choices
- for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
+ CFGNode marked = lca;
+ for( CFGNode cfg = marked; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
anti[cfg._nid] = load._nid;
// Walk load->mem uses, looking for Stores causing an anti-dep
for( Node mem : load.antiDeps() ) {
@@ -256,6 +277,9 @@ public abstract class GlobalCodeMotion {
default: throw Utils.TODO();
}
}
+ // Release this load's marks before another compilation reuses IDs.
+ for( CFGNode cfg = marked; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
+ anti[cfg._nid] = 0;
return lca;
}
diff --git a/chapter16/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter16/src/main/java/com/seaofnodes/simple/IterPeeps.java
index cabc4a15..415f85e6 100644
--- a/chapter16/src/main/java/com/seaofnodes/simple/IterPeeps.java
+++ b/chapter16/src/main/java/com/seaofnodes/simple/IterPeeps.java
@@ -194,11 +194,15 @@ public abstract class IterPeeps {
int idx = _R.nextInt(_len);
E x = (E)_es[idx];
_es[idx] = _es[--_len]; // Compress array
+ _es[_len] = null;
_on.clear(x._nid);
return x;
}
+ public boolean isEmpty() { return _len==0; }
+
public void clear() {
+ Arrays.fill(_es,0,_len,null);
_len = 0;
_on.clear();
_R.setSeed(_seed);
diff --git a/chapter16/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java b/chapter16/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java
new file mode 100644
index 00000000..b3984f15
--- /dev/null
+++ b/chapter16/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java
@@ -0,0 +1,31 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.evaluator.Evaluator;
+import com.seaofnodes.simple.node.StopNode;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+public class SchedulingScratchTest {
+ @Test public void testScratchAcrossCompilations() {
+ String src = """
+ int[] xs = new int[2];
+ xs[0] = arg;
+ int sum = 0;
+ int i = 0;
+ while( i<5 ) {
+ int old = xs[arg&1];
+ xs[0] = i;
+ sum = sum + old;
+ i = i + 1;
+ }
+ return sum;
+ """;
+ // Each fresh compilation reuses node IDs and the scheduler's scratch.
+ for( int round=0; round<3; round++ ) {
+ StopNode stop = new Parser(src).parse().iterate();
+ assertEquals(6L,Evaluator.evaluate(stop,0));
+ assertEquals(0L,Evaluator.evaluate(stop,1));
+ assertEquals(8L,Evaluator.evaluate(stop,2));
+ }
+ }
+}
diff --git a/chapter17a/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java b/chapter17a/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java
index c6d02b27..8dc1a626 100644
--- a/chapter17a/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java
+++ b/chapter17a/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java
@@ -73,24 +73,41 @@ public abstract class GlobalCodeMotion {
}
// ------------------------------------------------------------------------
+ private static final BitSet WAKE_VISIT = new BitSet();
+
+ // Scheduling is not recursive. Retain scratch capacity between schedules,
+ // but release graph references and reset only the entries we used.
+ private static CFGNode[] LATE = new CFGNode[1];
+ // Load NIDs at their CFG block choices, checked against Store blocks.
+ private static int[] ANTI = new int[1];
+ private static final ArrayList<Node> TOUCHED = new ArrayList<>();
+ private static final ArrayList<Node> PLACED = new ArrayList<>();
+ private static final WorkList<Node> WORK = new WorkList<>();
+
private static void schedLate( StopNode stop) {
- CFGNode[] late = new CFGNode[Node.UID()];
- Node[] ns = new Node[Node.UID()];
- // Record Load NIDs at all their CFG block choices, then check against
- // Store block choices to force a Load above an anti-dependent Store.
- int[] anti = new int[Node.UID()];
+ assert TOUCHED.isEmpty() && PLACED.isEmpty() && WORK.isEmpty();
+ if( Node.UID() > LATE.length ) {
+ int len = Math.max(Node.UID(),LATE.length<<1);
+ LATE = new CFGNode[len];
+ ANTI = new int[len];
+ }
+ WORK.clear();
// Breadth-first scheduling
- breadth(stop,ns,late,anti);
+ breadth(stop,PLACED,LATE,ANTI,WAKE_VISIT);
// Copy the best placement choice into the control slot
- for( int i=0; i<late.length; i++ )
- if( ns[i] != null && !(ns[i] instanceof ProjNode) )
- ns[i].setDef(0,late[i]);
+ for( Node n : PLACED )
+ if( !(n instanceof ProjNode) )
+ n.setDef(0,LATE[n._nid]);
+ for( Node n : TOUCHED )
+ LATE[n._nid] = null;
+ TOUCHED.clear();
+ PLACED.clear();
}
- private static void breadth(Node stop, Node[] ns, CFGNode[] late, int[] anti) {
+ private static void breadth(Node stop, ArrayList<Node> placed, CFGNode[] late, int[] anti, BitSet visit) {
// Things on the worklist have some (but perhaps not all) uses done.
- WorkList<Node> work = new WorkList<>();
+ WorkList<Node> work = WORK;
work.push(stop);
Node n;
outer:
@@ -114,16 +131,19 @@ public abstract class GlobalCodeMotion {
continue outer;
// All uses done, schedule
- _doSchedLate(n,ns,late,anti);
+ _doSchedLate(n,placed,late,anti);
}
+ TOUCHED.add(n);
+
// A use just finished; reconsider its inputs and waiting loads,
// even when the shared memory input was already scheduled.
for( Node def : n._inputs ) {
if( def==null ) continue;
if( late[def._nid]==null ) work.push(def);
- wakeLoads(def,late,work,new BitSet());
+ wakeLoads(def,late,work,visit);
}
+ visit.clear();
if( n instanceof LoopNode loop )
for( Node phi : loop._outputs )
if( phi instanceof PhiNode && late[phi._nid]==null )
@@ -131,7 +151,7 @@ public abstract class GlobalCodeMotion {
}
}
- private static void _doSchedLate(Node n, Node[] ns, CFGNode[] late, int[] anti) {
+ private static void _doSchedLate(Node n, ArrayList<Node> placed, CFGNode[] late, int[] anti) {
// Walk uses, gathering the LCA (Least Common Ancestor) of uses
CFGNode early = n.in(0) instanceof CFGNode cfg ? cfg : n.in(0).cfg0();
assert early != null;
@@ -153,7 +173,7 @@ public abstract class GlobalCodeMotion {
if( better(lca,best) )
best = lca;
assert !(best instanceof IfNode);
- ns [n._nid] = n;
+ placed.add(n);
late[n._nid] = best;
}
@@ -193,7 +213,8 @@ public abstract class GlobalCodeMotion {
private static CFGNode find_anti_dep(CFGNode lca, LoadNode load, CFGNode early, CFGNode[] late, int[] anti) {
// We could skip final-field loads here.
// Walk LCA->early, flagging Load's block location choices
- for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
+ CFGNode marked = lca;
+ for( CFGNode cfg = marked; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
anti[cfg._nid] = load._nid;
// Walk load->mem uses, looking for Stores causing an anti-dep
for( Node mem : load.antiDeps() ) {
@@ -217,6 +238,9 @@ public abstract class GlobalCodeMotion {
default: throw Utils.TODO();
}
}
+ // Release this load's marks before another compilation reuses IDs.
+ for( CFGNode cfg = marked; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
+ anti[cfg._nid] = 0;
return lca;
}
diff --git a/chapter17a/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter17a/src/main/java/com/seaofnodes/simple/IterPeeps.java
index 5a80698a..792fd507 100644
--- a/chapter17a/src/main/java/com/seaofnodes/simple/IterPeeps.java
+++ b/chapter17a/src/main/java/com/seaofnodes/simple/IterPeeps.java
@@ -197,11 +197,15 @@ public abstract class IterPeeps {
int idx = _R.nextInt(_len);
E x = (E)_es[idx];
_es[idx] = _es[--_len]; // Compress array
+ _es[_len] = null;
_on.clear(x._nid);
return x;
}
+ public boolean isEmpty() { return _len==0; }
+
public void clear() {
+ Arrays.fill(_es,0,_len,null);
_len = 0;
_on.clear();
_R.setSeed(_seed);
diff --git a/chapter17a/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/chapter17a/src/main/java/com/seaofnodes/simple/node/CFGNode.java
index e85fc6ae..37240b18 100644
--- a/chapter17a/src/main/java/com/seaofnodes/simple/node/CFGNode.java
+++ b/chapter17a/src/main/java/com/seaofnodes/simple/node/CFGNode.java
@@ -84,9 +84,13 @@ public abstract class CFGNode extends Node {
// Tag all CFG Nodes with their containing LoopNode; LoopNodes themselves
// also refer to *their* containing LoopNode, as well as have their depth.
// Start is a LoopNode which contains all at depth 1.
+ private static final BitSet POST = new BitSet();
+
public void buildLoopTree(StopNode stop) {
_ltree = stop._ltree = Parser.XCTRL._ltree = new LoopTree((StartNode)this);
- _bltWalk(2,stop, new BitSet());
+ BitSet post = POST;
+ _bltWalk(2,stop, post);
+ post.clear();
}
private int _bltWalk(int pre, StopNode stop, BitSet post) {
// Pre-walked?
diff --git a/chapter17a/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java b/chapter17a/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java
new file mode 100644
index 00000000..b3984f15
--- /dev/null
+++ b/chapter17a/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java
@@ -0,0 +1,31 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.evaluator.Evaluator;
+import com.seaofnodes.simple.node.StopNode;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+public class SchedulingScratchTest {
+ @Test public void testScratchAcrossCompilations() {
+ String src = """
+ int[] xs = new int[2];
+ xs[0] = arg;
+ int sum = 0;
+ int i = 0;
+ while( i<5 ) {
+ int old = xs[arg&1];
+ xs[0] = i;
+ sum = sum + old;
+ i = i + 1;
+ }
+ return sum;
+ """;
+ // Each fresh compilation reuses node IDs and the scheduler's scratch.
+ for( int round=0; round<3; round++ ) {
+ StopNode stop = new Parser(src).parse().iterate();
+ assertEquals(6L,Evaluator.evaluate(stop,0));
+ assertEquals(0L,Evaluator.evaluate(stop,1));
+ assertEquals(8L,Evaluator.evaluate(stop,2));
+ }
+ }
+}
diff --git a/chapter17b/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java b/chapter17b/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java
index c6d02b27..8dc1a626 100644
--- a/chapter17b/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java
+++ b/chapter17b/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java
@@ -73,24 +73,41 @@ public abstract class GlobalCodeMotion {
}
// ------------------------------------------------------------------------
+ private static final BitSet WAKE_VISIT = new BitSet();
+
+ // Scheduling is not recursive. Retain scratch capacity between schedules,
+ // but release graph references and reset only the entries we used.
+ private static CFGNode[] LATE = new CFGNode[1];
+ // Load NIDs at their CFG block choices, checked against Store blocks.
+ private static int[] ANTI = new int[1];
+ private static final ArrayList<Node> TOUCHED = new ArrayList<>();
+ private static final ArrayList<Node> PLACED = new ArrayList<>();
+ private static final WorkList<Node> WORK = new WorkList<>();
+
private static void schedLate( StopNode stop) {
- CFGNode[] late = new CFGNode[Node.UID()];
- Node[] ns = new Node[Node.UID()];
- // Record Load NIDs at all their CFG block choices, then check against
- // Store block choices to force a Load above an anti-dependent Store.
- int[] anti = new int[Node.UID()];
+ assert TOUCHED.isEmpty() && PLACED.isEmpty() && WORK.isEmpty();
+ if( Node.UID() > LATE.length ) {
+ int len = Math.max(Node.UID(),LATE.length<<1);
+ LATE = new CFGNode[len];
+ ANTI = new int[len];
+ }
+ WORK.clear();
// Breadth-first scheduling
- breadth(stop,ns,late,anti);
+ breadth(stop,PLACED,LATE,ANTI,WAKE_VISIT);
// Copy the best placement choice into the control slot
- for( int i=0; i<late.length; i++ )
- if( ns[i] != null && !(ns[i] instanceof ProjNode) )
- ns[i].setDef(0,late[i]);
+ for( Node n : PLACED )
+ if( !(n instanceof ProjNode) )
+ n.setDef(0,LATE[n._nid]);
+ for( Node n : TOUCHED )
+ LATE[n._nid] = null;
+ TOUCHED.clear();
+ PLACED.clear();
}
- private static void breadth(Node stop, Node[] ns, CFGNode[] late, int[] anti) {
+ private static void breadth(Node stop, ArrayList<Node> placed, CFGNode[] late, int[] anti, BitSet visit) {
// Things on the worklist have some (but perhaps not all) uses done.
- WorkList<Node> work = new WorkList<>();
+ WorkList<Node> work = WORK;
work.push(stop);
Node n;
outer:
@@ -114,16 +131,19 @@ public abstract class GlobalCodeMotion {
continue outer;
// All uses done, schedule
- _doSchedLate(n,ns,late,anti);
+ _doSchedLate(n,placed,late,anti);
}
+ TOUCHED.add(n);
+
// A use just finished; reconsider its inputs and waiting loads,
// even when the shared memory input was already scheduled.
for( Node def : n._inputs ) {
if( def==null ) continue;
if( late[def._nid]==null ) work.push(def);
- wakeLoads(def,late,work,new BitSet());
+ wakeLoads(def,late,work,visit);
}
+ visit.clear();
if( n instanceof LoopNode loop )
for( Node phi : loop._outputs )
if( phi instanceof PhiNode && late[phi._nid]==null )
@@ -131,7 +151,7 @@ public abstract class GlobalCodeMotion {
}
}
- private static void _doSchedLate(Node n, Node[] ns, CFGNode[] late, int[] anti) {
+ private static void _doSchedLate(Node n, ArrayList<Node> placed, CFGNode[] late, int[] anti) {
// Walk uses, gathering the LCA (Least Common Ancestor) of uses
CFGNode early = n.in(0) instanceof CFGNode cfg ? cfg : n.in(0).cfg0();
assert early != null;
@@ -153,7 +173,7 @@ public abstract class GlobalCodeMotion {
if( better(lca,best) )
best = lca;
assert !(best instanceof IfNode);
- ns [n._nid] = n;
+ placed.add(n);
late[n._nid] = best;
}
@@ -193,7 +213,8 @@ public abstract class GlobalCodeMotion {
private static CFGNode find_anti_dep(CFGNode lca, LoadNode load, CFGNode early, CFGNode[] late, int[] anti) {
// We could skip final-field loads here.
// Walk LCA->early, flagging Load's block location choices
- for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
+ CFGNode marked = lca;
+ for( CFGNode cfg = marked; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
anti[cfg._nid] = load._nid;
// Walk load->mem uses, looking for Stores causing an anti-dep
for( Node mem : load.antiDeps() ) {
@@ -217,6 +238,9 @@ public abstract class GlobalCodeMotion {
default: throw Utils.TODO();
}
}
+ // Release this load's marks before another compilation reuses IDs.
+ for( CFGNode cfg = marked; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
+ anti[cfg._nid] = 0;
return lca;
}
diff --git a/chapter17b/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter17b/src/main/java/com/seaofnodes/simple/IterPeeps.java
index 5a80698a..792fd507 100644
--- a/chapter17b/src/main/java/com/seaofnodes/simple/IterPeeps.java
+++ b/chapter17b/src/main/java/com/seaofnodes/simple/IterPeeps.java
@@ -197,11 +197,15 @@ public abstract class IterPeeps {
int idx = _R.nextInt(_len);
E x = (E)_es[idx];
_es[idx] = _es[--_len]; // Compress array
+ _es[_len] = null;
_on.clear(x._nid);
return x;
}
+ public boolean isEmpty() { return _len==0; }
+
public void clear() {
+ Arrays.fill(_es,0,_len,null);
_len = 0;
_on.clear();
_R.setSeed(_seed);
diff --git a/chapter17b/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/chapter17b/src/main/java/com/seaofnodes/simple/node/CFGNode.java
index e85fc6ae..37240b18 100644
--- a/chapter17b/src/main/java/com/seaofnodes/simple/node/CFGNode.java
+++ b/chapter17b/src/main/java/com/seaofnodes/simple/node/CFGNode.java
@@ -84,9 +84,13 @@ public abstract class CFGNode extends Node {
// Tag all CFG Nodes with their containing LoopNode; LoopNodes themselves
// also refer to *their* containing LoopNode, as well as have their depth.
// Start is a LoopNode which contains all at depth 1.
+ private static final BitSet POST = new BitSet();
+
public void buildLoopTree(StopNode stop) {
_ltree = stop._ltree = Parser.XCTRL._ltree = new LoopTree((StartNode)this);
- _bltWalk(2,stop, new BitSet());
+ BitSet post = POST;
+ _bltWalk(2,stop, post);
+ post.clear();
}
private int _bltWalk(int pre, StopNode stop, BitSet post) {
// Pre-walked?
diff --git a/chapter17b/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java b/chapter17b/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java
new file mode 100644
index 00000000..91e00906
--- /dev/null
+++ b/chapter17b/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java
@@ -0,0 +1,31 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.evaluator.Evaluator;
+import com.seaofnodes.simple.node.StopNode;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+public class SchedulingScratchTest {
+ @Test public void testScratchAcrossCompilations() {
+ String src = """
+ int[] !xs = new int[2];
+ xs[0] = arg;
+ int sum = 0;
+ int i = 0;
+ while( i<5 ) {
+ int old = xs[arg&1];
+ xs[0] = i;
+ sum = sum + old;
+ i = i + 1;
+ }
+ return sum;
+ """;
+ // Each fresh compilation reuses node IDs and the scheduler's scratch.
+ for( int round=0; round<3; round++ ) {
+ StopNode stop = new Parser(src).parse().iterate();
+ assertEquals(6L,Evaluator.evaluate(stop,0));
+ assertEquals(0L,Evaluator.evaluate(stop,1));
+ assertEquals(8L,Evaluator.evaluate(stop,2));
+ }
+ }
+}
diff --git a/chapter18/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java b/chapter18/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java
index 2019b714..2a1ca2f5 100644
--- a/chapter18/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java
+++ b/chapter18/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java
@@ -125,24 +125,41 @@ public abstract class GlobalCodeMotion {
}
// ------------------------------------------------------------------------
+ private static final BitSet WAKE_VISIT = new BitSet();
+
+ // Scheduling is not recursive. Retain scratch capacity between schedules,
+ // but release graph references and reset only the entries we used.
+ private static CFGNode[] LATE = new CFGNode[1];
+ // Load NIDs at their CFG block choices, checked against Store blocks.
+ private static int[] ANTI = new int[1];
+ private static final ArrayList<Node> TOUCHED = new ArrayList<>();
+ private static final ArrayList<Node> PLACED = new ArrayList<>();
+ private static final WorkList<Node> WORK = new WorkList<>();
+
private static void schedLate( StopNode stop) {
- CFGNode[] late = new CFGNode[Node.UID()];
- Node[] ns = new Node[Node.UID()];
- // Record Load NIDs at all their CFG block choices, then check against
- // Store block choices to force a Load above an anti-dependent Store.
- int[] anti = new int[Node.UID()];
+ assert TOUCHED.isEmpty() && PLACED.isEmpty() && WORK.isEmpty();
+ if( Node.UID() > LATE.length ) {
+ int len = Math.max(Node.UID(),LATE.length<<1);
+ LATE = new CFGNode[len];
+ ANTI = new int[len];
+ }
+ WORK.clear();
// Breadth-first scheduling
- breadth(stop,ns,late,anti);
+ breadth(stop,PLACED,LATE,ANTI,WAKE_VISIT);
// Copy the best placement choice into the control slot
- for( int i=0; i<late.length; i++ )
- if( ns[i] != null && !(ns[i] instanceof ProjNode) )
- ns[i].setDef(0,late[i]);
+ for( Node n : PLACED )
+ if( !(n instanceof ProjNode) )
+ n.setDef(0,LATE[n._nid]);
+ for( Node n : TOUCHED )
+ LATE[n._nid] = null;
+ TOUCHED.clear();
+ PLACED.clear();
}
- private static void breadth(Node stop, Node[] ns, CFGNode[] late, int[] anti) {
+ private static void breadth(Node stop, ArrayList<Node> placed, CFGNode[] late, int[] anti, BitSet visit) {
// Things on the worklist have some (but perhaps not all) uses done.
- WorkList<Node> work = new WorkList<>();
+ WorkList<Node> work = WORK;
work.push(stop);
Node n;
outer:
@@ -166,16 +183,19 @@ public abstract class GlobalCodeMotion {
continue outer;
// All uses done, schedule
- _doSchedLate(n,ns,late,anti);
+ _doSchedLate(n,placed,late,anti);
}
+ TOUCHED.add(n);
+
// A use just finished; reconsider its inputs and waiting loads,
// even when the shared memory input was already scheduled.
for( Node def : n._inputs ) {
if( def==null ) continue;
if( late[def._nid]==null ) work.push(def);
- wakeLoads(def,late,work,new BitSet());
+ wakeLoads(def,late,work,visit);
}
+ visit.clear();
if( n instanceof LoopNode loop )
for( Node phi : loop._outputs )
if( phi instanceof PhiNode && late[phi._nid]==null )
@@ -183,7 +203,7 @@ public abstract class GlobalCodeMotion {
}
}
- private static void _doSchedLate(Node n, Node[] ns, CFGNode[] late, int[] anti) {
+ private static void _doSchedLate(Node n, ArrayList<Node> placed, CFGNode[] late, int[] anti) {
// Walk uses, gathering the LCA (Least Common Ancestor) of uses
CFGNode early = n.in(0) instanceof CFGNode cfg ? cfg : n.in(0).cfg0();
assert early != null;
@@ -205,7 +225,7 @@ public abstract class GlobalCodeMotion {
if( better(lca,best) )
best = lca;
assert !(best instanceof IfNode);
- ns [n._nid] = n;
+ placed.add(n);
late[n._nid] = best;
}
@@ -246,7 +266,8 @@ public abstract class GlobalCodeMotion {
private static CFGNode find_anti_dep(CFGNode lca, LoadNode load, CFGNode early, CFGNode[] late, int[] anti) {
// We could skip final-field loads here.
// Walk LCA->early, flagging Load's block location choices
- for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
+ CFGNode marked = lca;
+ for( CFGNode cfg = marked; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
anti[cfg._nid] = load._nid;
// Walk load->mem uses, looking for Stores causing an anti-dep
for( Node mem : load.antiDeps() ) {
@@ -274,6 +295,9 @@ public abstract class GlobalCodeMotion {
default: throw Utils.TODO();
}
}
+ // Release this load's marks before another compilation reuses IDs.
+ for( CFGNode cfg = marked; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
+ anti[cfg._nid] = 0;
return lca;
}
diff --git a/chapter18/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter18/src/main/java/com/seaofnodes/simple/IterPeeps.java
index 1792dada..46bdd4e1 100644
--- a/chapter18/src/main/java/com/seaofnodes/simple/IterPeeps.java
+++ b/chapter18/src/main/java/com/seaofnodes/simple/IterPeeps.java
@@ -187,7 +187,7 @@ public abstract class IterPeeps {
* True if Node is on the WorkList
*/
boolean on( E x ) { return _on.get(x._nid); }
- boolean isEmpty() { return _len==0; }
+ public boolean isEmpty() { return _len==0; }
/**
* Removes a random Node from the WorkList; null if WorkList is empty
@@ -197,11 +197,13 @@ public abstract class IterPeeps {
int idx = _R.nextInt(_len);
E x = (E)_es[idx];
_es[idx] = _es[--_len]; // Compress array
+ _es[_len] = null;
_on.clear(x._nid);
return x;
}
public void clear() {
+ Arrays.fill(_es,0,_len,null);
_len = 0;
_on.clear();
_R.setSeed(_seed);
diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/CFGNode.java
index 49398838..93c86ff6 100644
--- a/chapter18/src/main/java/com/seaofnodes/simple/node/CFGNode.java
+++ b/chapter18/src/main/java/com/seaofnodes/simple/node/CFGNode.java
@@ -100,9 +100,13 @@ public abstract class CFGNode extends Node {
// Tag all CFG Nodes with their containing LoopNode; LoopNodes themselves
// also refer to *their* containing LoopNode, as well as have their depth.
// Start contains the function bodies at depth 0; real loops add depth.
+ private static final BitSet POST = new BitSet();
+
public void buildLoopTree(StopNode stop) {
_ltree = stop._ltree = Parser.XCTRL._ltree = new LoopTree((StartNode)this);
- _bltWalk(2,null,stop, new BitSet());
+ BitSet post = POST;
+ _bltWalk(2,null,stop, post);
+ post.clear();
}
int _bltWalk( int pre, FunNode fun, StopNode stop, BitSet post ) {
// Pre-walked?
diff --git a/chapter18/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java b/chapter18/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java
new file mode 100644
index 00000000..3927d517
--- /dev/null
+++ b/chapter18/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java
@@ -0,0 +1,29 @@
+package com.seaofnodes.simple;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+public class SchedulingScratchTest {
+ @Test public void testScratchAcrossCompilations() {
+ String src = """
+ int[] !xs = new int[2];
+ xs[0] = arg;
+ int sum = 0;
+ int i = 0;
+ while( i<5 ) {
+ int old = xs[arg&1];
+ xs[0] = i;
+ sum = sum + old;
+ i = i + 1;
+ }
+ return sum;
+ """;
+ // Each fresh compilation reuses node IDs and the scheduler's scratch.
+ for( int round=0; round<3; round++ ) {
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("6",Eval2.eval(code,0));
+ assertEquals("0",Eval2.eval(code,1));
+ assertEquals("8",Eval2.eval(code,2));
+ }
+ }
+}
diff --git a/chapter19/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java b/chapter19/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java
index 9454e968..0f53ce36 100644
--- a/chapter19/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java
+++ b/chapter19/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java
@@ -134,24 +134,40 @@ public abstract class GlobalCodeMotion {
}
// ------------------------------------------------------------------------
+ // Scheduling is not recursive. Retain scratch capacity between schedules,
+ // but release graph references and reset only the entries we used.
+ private static CFGNode[] LATE = new CFGNode[1];
+ // Load NIDs at their CFG block choices, checked against Store blocks.
+ private static int[] ANTI = new int[1];
+ private static final ArrayList<Node> TOUCHED = new ArrayList<>();
+ private static final ArrayList<Node> PLACED = new ArrayList<>();
+ private static final WorkList<Node> WORK = new WorkList<>();
+
private static void schedLate( CodeGen code ) {
- CFGNode[] late = new CFGNode[code.UID()];
- Node[] ns = new Node[code.UID()];
- // Record Load NIDs at all their CFG block choices, then check against
- // Store block choices to force a Load above an anti-dependent Store.
- int[] anti = new int[code.UID()];
+ assert TOUCHED.isEmpty() && PLACED.isEmpty() && WORK.isEmpty();
+ if( code.UID() > LATE.length ) {
+ int len = Math.max(code.UID(),LATE.length<<1);
+ LATE = new CFGNode[len];
+ ANTI = new int[len];
+ }
+ WORK.clear();
// Breadth-first scheduling
- breadth(code._stop,ns,late,anti);
+ // Early scheduling has released the shared visit bits.
+ breadth(code._stop,PLACED,LATE,ANTI,code.visit());
// Copy the best placement choice into the control slot
- for( int i=0; i<late.length; i++ )
- if( ns[i] != null && !(ns[i] instanceof ProjNode) )
- ns[i].setDef(0,late[i]);
+ for( Node n : PLACED )
+ if( !(n instanceof ProjNode) )
+ n.setDef(0,LATE[n._nid]);
+ for( Node n : TOUCHED )
+ LATE[n._nid] = null;
+ TOUCHED.clear();
+ PLACED.clear();
}
- private static void breadth(Node stop, Node[] ns, CFGNode[] late, int[] anti) {
+ private static void breadth(Node stop, ArrayList<Node> placed, CFGNode[] late, int[] anti, BitSet visit) {
// Things on the worklist have some (but perhaps not all) uses done.
- WorkList<Node> work = new WorkList<>();
+ WorkList<Node> work = WORK;
work.push(stop);
Node n;
outer:
@@ -175,16 +191,19 @@ public abstract class GlobalCodeMotion {
continue outer;
// All uses done, schedule
- _doSchedLate(n,ns,late,anti);
+ _doSchedLate(n,placed,late,anti);
}
+ TOUCHED.add(n);
+
// A use just finished; reconsider its inputs and waiting loads,
// even when the shared memory input was already scheduled.
for( Node def : n._inputs ) {
if( def==null ) continue;
if( late[def._nid]==null ) work.push(def);
- wakeLoads(def,late,work,new BitSet());
+ wakeLoads(def,late,work,visit);
}
+ visit.clear();
if( n instanceof LoopNode loop )
for( Node phi : loop._outputs )
if( phi instanceof PhiNode && late[phi._nid]==null )
@@ -192,7 +211,7 @@ public abstract class GlobalCodeMotion {
}
}
- private static void _doSchedLate(Node n, Node[] ns, CFGNode[] late, int[] anti) {
+ private static void _doSchedLate(Node n, ArrayList<Node> placed, CFGNode[] late, int[] anti) {
// Walk uses, gathering the LCA (Least Common Ancestor) of uses
CFGNode early = n.in(0) instanceof CFGNode cfg ? cfg : n.in(0).cfg0();
assert early != null;
@@ -214,7 +233,7 @@ public abstract class GlobalCodeMotion {
if( better(lca,best) )
best = lca;
assert !(best instanceof IfNode);
- ns [n._nid] = n;
+ placed.add(n);
late[n._nid] = best;
}
@@ -255,7 +274,8 @@ public abstract class GlobalCodeMotion {
private static CFGNode find_anti_dep(CFGNode lca, MemOpNode load, CFGNode early, CFGNode[] late, int[] anti) {
// We could skip final-field loads here.
// Walk LCA->early, flagging Load's block location choices
- for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
+ CFGNode marked = lca;
+ for( CFGNode cfg = marked; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
anti[cfg._nid] = load._nid;
// Walk load->mem uses, looking for Stores causing an anti-dep
for( Node mem : load.antiDeps() ) {
@@ -283,6 +303,9 @@ public abstract class GlobalCodeMotion {
default: throw Utils.TODO();
}
}
+ // Release this load's marks before another compilation reuses IDs.
+ for( CFGNode cfg = marked; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
+ anti[cfg._nid] = 0;
return lca;
}
diff --git a/chapter19/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter19/src/main/java/com/seaofnodes/simple/IterPeeps.java
index 087a990b..14a7cb67 100644
--- a/chapter19/src/main/java/com/seaofnodes/simple/IterPeeps.java
+++ b/chapter19/src/main/java/com/seaofnodes/simple/IterPeeps.java
@@ -177,7 +177,7 @@ public class IterPeeps {
* True if Node is on the WorkList
*/
boolean on( E x ) { return _on.get(x._nid); }
- boolean isEmpty() { return _len==0; }
+ public boolean isEmpty() { return _len==0; }
/**
* Removes a random Node from the WorkList; null if WorkList is empty
@@ -187,11 +187,13 @@ public class IterPeeps {
int idx = _R.nextInt(_len);
E x = (E)_es[idx];
_es[idx] = _es[--_len]; // Compress array
+ _es[_len] = null;
_on.clear(x._nid);
return x;
}
public void clear() {
+ Arrays.fill(_es,0,_len,null);
_len = 0;
_on.clear();
_R.setSeed(_seed);
diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/CFGNode.java
index 383f18d0..d7b2f362 100644
--- a/chapter19/src/main/java/com/seaofnodes/simple/node/CFGNode.java
+++ b/chapter19/src/main/java/com/seaofnodes/simple/node/CFGNode.java
@@ -109,7 +109,9 @@ public abstract class CFGNode extends Node {
// Start contains the function bodies at depth 0; real loops add depth.
public void buildLoopTree(StopNode stop) {
_ltree = stop._ltree = Parser.XCTRL._ltree = new LoopTree((StartNode)this);
- _bltWalk(2,null,stop, new BitSet());
+ BitSet post = CodeGen.CODE.visit();
+ _bltWalk(2,null,stop, post);
+ post.clear();
}
int _bltWalk( int pre, FunNode fun, StopNode stop, BitSet post ) {
// Pre-walked?
diff --git a/chapter19/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java b/chapter19/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java
new file mode 100644
index 00000000..d7173612
--- /dev/null
+++ b/chapter19/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java
@@ -0,0 +1,31 @@
+package com.seaofnodes.simple;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+public class SchedulingScratchTest {
+ @Test public void testScratchAcrossCompilations() {
+ String src = """
+ int[] !xs = new int[2];
+ xs[0] = arg;
+ int sum = 0;
+ int i = 0;
+ while( i<5 ) {
+ int old = xs[arg&1];
+ xs[0] = i;
+ sum = sum + old;
+ i = i + 1;
+ }
+ return sum;
+ """;
+ // Each fresh compilation reuses node IDs and the scheduler's scratch.
+ for( int round=0; round<3; round++ ) {
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
+ // A small graph after many earlier node allocations.
+ if( round==0 ) code._uid += 100_000;
+ assertEquals("6",Eval2.eval(code,0));
+ assertEquals("0",Eval2.eval(code,1));
+ assertEquals("8",Eval2.eval(code,2));
+ }
+ }
+}
diff --git a/chapter20/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter20/src/main/java/com/seaofnodes/simple/IterPeeps.java
index a21fb517..f68d83f8 100644
--- a/chapter20/src/main/java/com/seaofnodes/simple/IterPeeps.java
+++ b/chapter20/src/main/java/com/seaofnodes/simple/IterPeeps.java
@@ -194,7 +194,7 @@ public class IterPeeps {
* True if Node is on the WorkList
*/
boolean on( E x ) { return _on.get(x._nid); }
- boolean isEmpty() { return _len==0; }
+ public boolean isEmpty() { return _len==0; }
/**
* Removes a random Node from the WorkList; null if WorkList is empty
@@ -204,11 +204,13 @@ public class IterPeeps {
int idx = _R.nextInt(_len);
E x = (E)_es[idx];
_es[idx] = _es[--_len]; // Compress array
+ _es[_len] = null;
_on.clear(x._nid);
return x;
}
public void clear() {
+ Arrays.fill(_es,0,_len,null);
_len = 0;
_on.clear();
_R.setSeed(_seed);
diff --git a/chapter20/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java b/chapter20/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
index 225e5fb8..95da525b 100644
--- a/chapter20/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
+++ b/chapter20/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
@@ -137,24 +137,40 @@ public abstract class GlobalCodeMotion {
}
// ------------------------------------------------------------------------
+ // Scheduling is not recursive. Retain scratch capacity between schedules,
+ // but release graph references and reset only the entries we used.
+ private static CFGNode[] LATE = new CFGNode[1];
+ // Load NIDs at their CFG block choices, checked against Store blocks.
+ private static int[] ANTI = new int[1];
+ private static final ArrayList<Node> TOUCHED = new ArrayList<>();
+ private static final ArrayList<Node> PLACED = new ArrayList<>();
+ private static final WorkList<Node> WORK = new WorkList<>();
+
private static void schedLate( CodeGen code ) {
- CFGNode[] late = new CFGNode[code.UID()];
- Node[] ns = new Node[code.UID()];
- // Record Load NIDs at all their CFG block choices, then check against
- // Store block choices to force a Load above an anti-dependent Store.
- int[] anti = new int[code.UID()];
+ assert TOUCHED.isEmpty() && PLACED.isEmpty() && WORK.isEmpty();
+ if( code.UID() > LATE.length ) {
+ int len = Math.max(code.UID(),LATE.length<<1);
+ LATE = new CFGNode[len];
+ ANTI = new int[len];
+ }
+ WORK.clear();
// Breadth-first scheduling
- breadth(code._stop,ns,late,anti);
+ // Early scheduling has released the shared visit bits.
+ breadth(code._stop,PLACED,LATE,ANTI,code.visit());
// Copy the best placement choice into the control slot
- for( int i=0; i<late.length; i++ )
- if( ns[i] != null && !(ns[i] instanceof ProjNode) )
- ns[i].setDef(0,late[i]);
+ for( Node n : PLACED )
+ if( !(n instanceof ProjNode) )
+ n.setDef(0,LATE[n._nid]);
+ for( Node n : TOUCHED )
+ LATE[n._nid] = null;
+ TOUCHED.clear();
+ PLACED.clear();
}
- private static void breadth(Node stop, Node[] ns, CFGNode[] late, int[] anti) {
+ private static void breadth(Node stop, ArrayList<Node> placed, CFGNode[] late, int[] anti, BitSet visit) {
// Things on the worklist have some (but perhaps not all) uses done.
- WorkList<Node> work = new WorkList<>();
+ WorkList<Node> work = WORK;
work.push(stop);
Node n;
outer:
@@ -178,16 +194,19 @@ public abstract class GlobalCodeMotion {
continue outer;
// All uses done, schedule
- _doSchedLate(n,ns,late,anti);
+ _doSchedLate(n,placed,late,anti);
}
+ TOUCHED.add(n);
+
// A use just finished; reconsider its inputs and waiting loads,
// even when the shared memory input was already scheduled.
for( Node def : n._inputs ) {
if( def==null ) continue;
if( late[def._nid]==null ) work.push(def);
- wakeLoads(def,late,work,new BitSet());
+ wakeLoads(def,late,work,visit);
}
+ visit.clear();
if( n instanceof LoopNode loop )
for( Node phi : loop._outputs )
if( phi instanceof PhiNode && late[phi._nid]==null )
@@ -195,7 +214,7 @@ public abstract class GlobalCodeMotion {
}
}
- private static void _doSchedLate(Node n, Node[] ns, CFGNode[] late, int[] anti) {
+ private static void _doSchedLate(Node n, ArrayList<Node> placed, CFGNode[] late, int[] anti) {
// Walk uses, gathering the LCA (Least Common Ancestor) of uses
CFGNode early = n.in(0) instanceof CFGNode cfg ? cfg : n.in(0).cfg0();
assert early != null;
@@ -217,7 +236,7 @@ public abstract class GlobalCodeMotion {
if( better(lca,best) )
best = lca;
assert !(best instanceof IfNode);
- ns [n._nid] = n;
+ placed.add(n);
late[n._nid] = best;
}
@@ -259,7 +278,8 @@ public abstract class GlobalCodeMotion {
private static CFGNode find_anti_dep(CFGNode lca, MemOpNode load, CFGNode early, CFGNode[] late, int[] anti) {
// We could skip final-field loads here.
// Walk LCA->early, flagging Load's block location choices
- for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
+ CFGNode marked = lca;
+ for( CFGNode cfg = marked; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
anti[cfg._nid] = load._nid;
// Walk load->mem uses, looking for Stores causing an anti-dep
for( Node mem : load.antiDeps() ) {
@@ -287,6 +307,9 @@ public abstract class GlobalCodeMotion {
default: throw Utils.TODO();
}
}
+ // Release this load's marks before another compilation reuses IDs.
+ for( CFGNode cfg = marked; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
+ anti[cfg._nid] = 0;
return lca;
}
diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/CFGNode.java
index 0e770659..6c708814 100644
--- a/chapter20/src/main/java/com/seaofnodes/simple/node/CFGNode.java
+++ b/chapter20/src/main/java/com/seaofnodes/simple/node/CFGNode.java
@@ -129,7 +129,9 @@ public abstract class CFGNode extends Node {
// Start contains the function bodies at depth 0; real loops add depth.
public void buildLoopTree(StopNode stop) {
_ltree = stop._ltree = Parser.XCTRL._ltree = new LoopTree((StartNode)this);
- _bltWalk(2,null,stop, new BitSet());
+ BitSet post = CodeGen.CODE.visit();
+ _bltWalk(2,null,stop, post);
+ post.clear();
}
int _bltWalk( int pre, FunNode fun, StopNode stop, BitSet post ) {
// Pre-walked?
diff --git a/chapter20/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java b/chapter20/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java
new file mode 100644
index 00000000..319c94b0
--- /dev/null
+++ b/chapter20/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java
@@ -0,0 +1,32 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+public class SchedulingScratchTest {
+ @Test public void testScratchAcrossCompilations() {
+ String src = """
+ int[] !xs = new int[2];
+ xs[0] = arg;
+ int sum = 0;
+ int i = 0;
+ while( i<5 ) {
+ int old = xs[arg&1];
+ xs[0] = i;
+ sum = sum + old;
+ i = i + 1;
+ }
+ return sum;
+ """;
+ // Each fresh compilation reuses node IDs and the scheduler's scratch.
+ for( int round=0; round<3; round++ ) {
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
+ // A small graph after many earlier node allocations.
+ if( round==0 ) code._uid += 100_000;
+ assertEquals("6",Eval2.eval(code,0));
+ assertEquals("0",Eval2.eval(code,1));
+ assertEquals("8",Eval2.eval(code,2));
+ }
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter21/src/main/java/com/seaofnodes/simple/IterPeeps.java
index 7c2f1b0c..2655b423 100644
--- a/chapter21/src/main/java/com/seaofnodes/simple/IterPeeps.java
+++ b/chapter21/src/main/java/com/seaofnodes/simple/IterPeeps.java
@@ -196,7 +196,7 @@ public class IterPeeps {
* True if Node is on the WorkList
*/
boolean on( E x ) { return _on.get(x._nid); }
- boolean isEmpty() { return _len==0; }
+ public boolean isEmpty() { return _len==0; }
/**
* Removes a random Node from the WorkList; null if WorkList is empty
@@ -206,11 +206,13 @@ public class IterPeeps {
int idx = _R.nextInt(_len);
E x = (E)_es[idx];
_es[idx] = _es[--_len]; // Compress array
+ _es[_len] = null;
_on.clear(x._nid);
return x;
}
public void clear() {
+ Arrays.fill(_es,0,_len,null);
_len = 0;
_on.clear();
_R.setSeed(_seed);
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java b/chapter21/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
index d4db558a..7f1e98d2 100644
--- a/chapter21/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
+++ b/chapter21/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
@@ -137,24 +137,40 @@ public abstract class GlobalCodeMotion {
}
// ------------------------------------------------------------------------
+ // Scheduling is not recursive. Retain scratch capacity between schedules,
+ // but release graph references and reset only the entries we used.
+ private static CFGNode[] LATE = new CFGNode[1];
+ // Load NIDs at their CFG block choices, checked against Store blocks.
+ private static int[] ANTI = new int[1];
+ private static final ArrayList<Node> TOUCHED = new ArrayList<>();
+ private static final ArrayList<Node> PLACED = new ArrayList<>();
+ private static final WorkList<Node> WORK = new WorkList<>();
+
private static void schedLate( CodeGen code ) {
- CFGNode[] late = new CFGNode[code.UID()];
- Node[] ns = new Node[code.UID()];
- // Record Load NIDs at all their CFG block choices, then check against
- // Store block choices to force a Load above an anti-dependent Store.
- int[] anti = new int[code.UID()];
+ assert TOUCHED.isEmpty() && PLACED.isEmpty() && WORK.isEmpty();
+ if( code.UID() > LATE.length ) {
+ int len = Math.max(code.UID(),LATE.length<<1);
+ LATE = new CFGNode[len];
+ ANTI = new int[len];
+ }
+ WORK.clear();
// Breadth-first scheduling
- breadth(code._stop,ns,late,anti);
+ // Early scheduling has released the shared visit bits.
+ breadth(code._stop,PLACED,LATE,ANTI,code.visit());
// Copy the best placement choice into the control slot
- for( int i=0; i<late.length; i++ )
- if( ns[i] != null && !(ns[i] instanceof ProjNode) )
- ns[i].setDef(0,late[i]);
+ for( Node n : PLACED )
+ if( !(n instanceof ProjNode) )
+ n.setDef(0,LATE[n._nid]);
+ for( Node n : TOUCHED )
+ LATE[n._nid] = null;
+ TOUCHED.clear();
+ PLACED.clear();
}
- private static void breadth(Node stop, Node[] ns, CFGNode[] late, int[] anti) {
+ private static void breadth(Node stop, ArrayList<Node> placed, CFGNode[] late, int[] anti, BitSet visit) {
// Things on the worklist have some (but perhaps not all) uses done.
- WorkList<Node> work = new WorkList<>();
+ WorkList<Node> work = WORK;
work.push(stop);
Node n;
outer:
@@ -178,16 +194,19 @@ public abstract class GlobalCodeMotion {
continue outer;
// All uses done, schedule
- _doSchedLate(n,ns,late,anti);
+ _doSchedLate(n,placed,late,anti);
}
+ TOUCHED.add(n);
+
// A use just finished; reconsider its inputs and waiting loads,
// even when the shared memory input was already scheduled.
for( Node def : n._inputs ) {
if( def==null ) continue;
if( late[def._nid]==null ) work.push(def);
- wakeLoads(def,late,work,new BitSet());
+ wakeLoads(def,late,work,visit);
}
+ visit.clear();
if( n instanceof LoopNode loop )
for( Node phi : loop._outputs )
if( phi instanceof PhiNode && late[phi._nid]==null )
@@ -195,7 +214,7 @@ public abstract class GlobalCodeMotion {
}
}
- private static void _doSchedLate(Node n, Node[] ns, CFGNode[] late, int[] anti) {
+ private static void _doSchedLate(Node n, ArrayList<Node> placed, CFGNode[] late, int[] anti) {
// Walk uses, gathering the LCA (Least Common Ancestor) of uses
CFGNode early = n.in(0) instanceof CFGNode cfg ? cfg : n.in(0).cfg0();
assert early != null;
@@ -218,7 +237,7 @@ public abstract class GlobalCodeMotion {
if( better(lca,best) )
best = lca;
assert !(best instanceof IfNode);
- ns [n._nid] = n;
+ placed.add(n);
late[n._nid] = best;
}
@@ -260,7 +279,8 @@ public abstract class GlobalCodeMotion {
private static CFGNode find_anti_dep(CFGNode lca, MemOpNode load, CFGNode early, CFGNode[] late, int[] anti) {
// We could skip final-field loads here.
// Walk LCA->early, flagging Load's block location choices
- for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
+ CFGNode marked = lca;
+ for( CFGNode cfg = marked; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
anti[cfg._nid] = load._nid;
// Walk load->mem uses, looking for Stores causing an anti-dep
for( Node mem : load.antiDeps() ) {
@@ -288,6 +308,9 @@ public abstract class GlobalCodeMotion {
default: throw Utils.TODO();
}
}
+ // Release this load's marks before another compilation reuses IDs.
+ for( CFGNode cfg = marked; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
+ anti[cfg._nid] = 0;
return lca;
}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/CFGNode.java
index 47dfa311..1cf1946a 100644
--- a/chapter21/src/main/java/com/seaofnodes/simple/node/CFGNode.java
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/CFGNode.java
@@ -135,7 +135,9 @@ public abstract class CFGNode extends Node {
// Start contains the function bodies at depth 0; real loops add depth.
public void buildLoopTree(StopNode stop) {
_ltree = stop._ltree = Parser.XCTRL._ltree = new LoopTree((StartNode)this);
- _bltWalk(2,null,stop, new BitSet());
+ BitSet post = CodeGen.CODE.visit();
+ _bltWalk(2,null,stop, post);
+ post.clear();
}
int _bltWalk( int pre, FunNode fun, StopNode stop, BitSet post ) {
// Pre-walked?
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/BrainFuckTest.java b/chapter21/src/test/java/com/seaofnodes/simple/BrainFuckTest.java
index 97d33b70..40eb9719 100644
--- a/chapter21/src/test/java/com/seaofnodes/simple/BrainFuckTest.java
+++ b/chapter21/src/test/java/com/seaofnodes/simple/BrainFuckTest.java
@@ -20,7 +20,7 @@ public class BrainFuckTest {
String brain_fuck = "Hello World!\n";
TestC.run("brain_fuck", brain_fuck, 40);
- EvalRisc5 R5 = TestRisc5.build("brain_fuck", 0, 42, false);
+ EvalRisc5 R5 = TestRisc5.build("brain_fuck", 0, 114, false);
int trap = R5.step(100000);
assertEquals(0,trap);
int ptr = (int)R5.regs[riscv.A0];
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java b/chapter21/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java
new file mode 100644
index 00000000..319c94b0
--- /dev/null
+++ b/chapter21/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java
@@ -0,0 +1,32 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+public class SchedulingScratchTest {
+ @Test public void testScratchAcrossCompilations() {
+ String src = """
+ int[] !xs = new int[2];
+ xs[0] = arg;
+ int sum = 0;
+ int i = 0;
+ while( i<5 ) {
+ int old = xs[arg&1];
+ xs[0] = i;
+ sum = sum + old;
+ i = i + 1;
+ }
+ return sum;
+ """;
+ // Each fresh compilation reuses node IDs and the scheduler's scratch.
+ for( int round=0; round<3; round++ ) {
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
+ // A small graph after many earlier node allocations.
+ if( round==0 ) code._uid += 100_000;
+ assertEquals("6",Eval2.eval(code,0));
+ assertEquals("0",Eval2.eval(code,1));
+ assertEquals("8",Eval2.eval(code,2));
+ }
+ }
+}
diff --git a/chapter22/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter22/src/main/java/com/seaofnodes/simple/IterPeeps.java
index 7c2f1b0c..2655b423 100644
--- a/chapter22/src/main/java/com/seaofnodes/simple/IterPeeps.java
+++ b/chapter22/src/main/java/com/seaofnodes/simple/IterPeeps.java
@@ -196,7 +196,7 @@ public class IterPeeps {
* True if Node is on the WorkList
*/
boolean on( E x ) { return _on.get(x._nid); }
- boolean isEmpty() { return _len==0; }
+ public boolean isEmpty() { return _len==0; }
/**
* Removes a random Node from the WorkList; null if WorkList is empty
@@ -206,11 +206,13 @@ public class IterPeeps {
int idx = _R.nextInt(_len);
E x = (E)_es[idx];
_es[idx] = _es[--_len]; // Compress array
+ _es[_len] = null;
_on.clear(x._nid);
return x;
}
public void clear() {
+ Arrays.fill(_es,0,_len,null);
_len = 0;
_on.clear();
_R.setSeed(_seed);
diff --git a/chapter22/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java b/chapter22/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
index 1818be73..dbe6e786 100644
--- a/chapter22/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
+++ b/chapter22/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
@@ -136,24 +136,40 @@ public abstract class GlobalCodeMotion {
}
// ------------------------------------------------------------------------
+ // Scheduling is not recursive. Retain scratch capacity between schedules,
+ // but release graph references and reset only the entries we used.
+ private static CFGNode[] LATE = new CFGNode[1];
+ // Load NIDs at their CFG block choices, checked against Store blocks.
+ private static int[] ANTI = new int[1];
+ private static final ArrayList<Node> TOUCHED = new ArrayList<>();
+ private static final ArrayList<Node> PLACED = new ArrayList<>();
+ private static final WorkList<Node> WORK = new WorkList<>();
+
private static void schedLate( CodeGen code ) {
- CFGNode[] late = new CFGNode[code.UID()];
- Node[] ns = new Node[code.UID()];
- // Record Load NIDs at all their CFG block choices, then check against
- // Store block choices to force a Load above an anti-dependent Store.
- int[] anti = new int[code.UID()];
+ assert TOUCHED.isEmpty() && PLACED.isEmpty() && WORK.isEmpty();
+ if( code.UID() > LATE.length ) {
+ int len = Math.max(code.UID(),LATE.length<<1);
+ LATE = new CFGNode[len];
+ ANTI = new int[len];
+ }
+ WORK.clear();
// Breadth-first scheduling
- breadth(code._stop,ns,late,anti);
+ // Early scheduling has released the shared visit bits.
+ breadth(code._stop,PLACED,LATE,ANTI,code.visit());
// Copy the best placement choice into the control slot
- for( int i=0; i<late.length; i++ )
- if( ns[i] != null && !(ns[i] instanceof ProjNode) )
- ns[i].setDef(0,late[i]);
+ for( Node n : PLACED )
+ if( !(n instanceof ProjNode) )
+ n.setDef(0,LATE[n._nid]);
+ for( Node n : TOUCHED )
+ LATE[n._nid] = null;
+ TOUCHED.clear();
+ PLACED.clear();
}
- private static void breadth(Node stop, Node[] ns, CFGNode[] late, int[] anti) {
+ private static void breadth(Node stop, ArrayList<Node> placed, CFGNode[] late, int[] anti, BitSet visit) {
// Things on the worklist have some (but perhaps not all) uses done.
- WorkList<Node> work = new WorkList<>();
+ WorkList<Node> work = WORK;
work.push(stop);
Node n;
outer:
@@ -177,16 +193,19 @@ public abstract class GlobalCodeMotion {
continue outer;
// All uses done, schedule
- _doSchedLate(n,ns,late,anti);
+ _doSchedLate(n,placed,late,anti);
}
+ TOUCHED.add(n);
+
// A use just finished; reconsider its inputs and waiting loads,
// even when the shared memory input was already scheduled.
for( Node def : n._inputs ) {
if( def==null ) continue;
if( late[def._nid]==null ) work.push(def);
- wakeLoads(def,late,work,new BitSet());
+ wakeLoads(def,late,work,visit);
}
+ visit.clear();
if( n instanceof LoopNode loop )
for( Node phi : loop._outputs )
if( phi instanceof PhiNode && late[phi._nid]==null )
@@ -194,7 +213,7 @@ public abstract class GlobalCodeMotion {
}
}
- private static void _doSchedLate(Node n, Node[] ns, CFGNode[] late, int[] anti) {
+ private static void _doSchedLate(Node n, ArrayList<Node> placed, CFGNode[] late, int[] anti) {
// Walk uses, gathering the LCA (Least Common Ancestor) of uses
CFGNode early = n.in(0) instanceof CFGNode cfg ? cfg : n.in(0).cfg0();
assert early != null;
@@ -217,7 +236,7 @@ public abstract class GlobalCodeMotion {
if( better(lca,best) )
best = lca;
assert !(best instanceof IfNode);
- ns [n._nid] = n;
+ placed.add(n);
late[n._nid] = best;
}
@@ -259,7 +278,8 @@ public abstract class GlobalCodeMotion {
private static CFGNode find_anti_dep(CFGNode lca, MemOpNode load, CFGNode early, CFGNode[] late, int[] anti) {
// We could skip final-field loads here.
// Walk LCA->early, flagging Load's block location choices
- for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
+ CFGNode marked = lca;
+ for( CFGNode cfg = marked; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
anti[cfg._nid] = load._nid;
// Walk load->mem uses, looking for Stores causing an anti-dep
for( Node mem : load.antiDeps() ) {
@@ -287,6 +307,9 @@ public abstract class GlobalCodeMotion {
default: throw Utils.TODO();
}
}
+ // Release this load's marks before another compilation reuses IDs.
+ for( CFGNode cfg = marked; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
+ anti[cfg._nid] = 0;
return lca;
}
diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/CFGNode.java
index 4f346ee3..6317166e 100644
--- a/chapter22/src/main/java/com/seaofnodes/simple/node/CFGNode.java
+++ b/chapter22/src/main/java/com/seaofnodes/simple/node/CFGNode.java
@@ -147,7 +147,9 @@ public abstract class CFGNode extends Node {
}
_ltree = stop._ltree = Parser.XCTRL._ltree = new LoopTree((StartNode)this);
- _bltWalk(2,null,stop, new BitSet());
+ BitSet post = CodeGen.CODE.visit();
+ _bltWalk(2,null,stop, post);
+ post.clear();
}
int _bltWalk( int pre, FunNode fun, StopNode stop, BitSet post ) {
// Pre-walked?
diff --git a/chapter22/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/chapter22/src/test/java/com/seaofnodes/simple/Chapter21Test.java
index 4bce96ab..b603f6ce 100644
--- a/chapter22/src/test/java/com/seaofnodes/simple/Chapter21Test.java
+++ b/chapter22/src/test/java/com/seaofnodes/simple/Chapter21Test.java
@@ -389,7 +389,7 @@ public class Chapter21Test {
String sprimes = sb.p("]").toString();
// Compile, link against native C; expect the above string of primes to be printed out by C
- TestC.run("sieve",sprimes, 185);
+ TestC.run("sieve",sprimes, 113);
// Evaluate on RISC5 emulator; expect return of an array of primes in
// the simulated heap.
diff --git a/chapter22/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java b/chapter22/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java
new file mode 100644
index 00000000..319c94b0
--- /dev/null
+++ b/chapter22/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java
@@ -0,0 +1,32 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+public class SchedulingScratchTest {
+ @Test public void testScratchAcrossCompilations() {
+ String src = """
+ int[] !xs = new int[2];
+ xs[0] = arg;
+ int sum = 0;
+ int i = 0;
+ while( i<5 ) {
+ int old = xs[arg&1];
+ xs[0] = i;
+ sum = sum + old;
+ i = i + 1;
+ }
+ return sum;
+ """;
+ // Each fresh compilation reuses node IDs and the scheduler's scratch.
+ for( int round=0; round<3; round++ ) {
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
+ // A small graph after many earlier node allocations.
+ if( round==0 ) code._uid += 100_000;
+ assertEquals("6",Eval2.eval(code,0));
+ assertEquals("0",Eval2.eval(code,1));
+ assertEquals("8",Eval2.eval(code,2));
+ }
+ }
+}
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter23/src/main/java/com/seaofnodes/simple/IterPeeps.java
index c4b96c5c..fbbdf5e6 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/IterPeeps.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/IterPeeps.java
@@ -198,7 +198,7 @@ public class IterPeeps {
* True if Node is on the WorkList
*/
boolean on( E x ) { return _on.get(x._nid); }
- boolean isEmpty() { return _len==0; }
+ public boolean isEmpty() { return _len==0; }
/**
* Removes a random Node from the WorkList; null if WorkList is empty
@@ -208,11 +208,13 @@ public class IterPeeps {
int idx = _R.nextInt(_len);
E x = (E)_es[idx];
_es[idx] = _es[--_len]; // Compress array
+ _es[_len] = null;
_on.clear(x._nid);
return x;
}
public void clear() {
+ Arrays.fill(_es,0,_len,null);
_len = 0;
_on.clear();
_R.setSeed(_seed);
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java b/chapter23/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
index da45db4f..205a5cb2 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
@@ -4,6 +4,7 @@ import com.seaofnodes.simple.IterPeeps.WorkList;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.AryInt;
import com.seaofnodes.simple.util.Utils;
import java.util.*;
@@ -136,24 +137,46 @@ public abstract class GlobalCodeMotion {
}
// ------------------------------------------------------------------------
+ // Scheduling is not recursive. Retain scratch capacity between schedules,
+ // but release graph references and reset only the entries we used.
+ private static CFGNode[] LATE = new CFGNode[1];
+ // Load NIDs at their CFG block choices, checked against Store blocks.
+ private static int[] ANTI = new int[1];
+ private static final AryInt TOUCHED = new AryInt();
+ private static final Ary<Node> PLACED = new Ary<>(Node.class);
+ private static final WorkList<Node> WORK = new WorkList<>();
+
private static void schedLate( CodeGen code ) {
- CFGNode[] late = new CFGNode[code.UID()];
- Node[] ns = new Node[code.UID()];
- // Record Load NIDs at all their CFG block choices, then check against
- // Store block choices to force a Load above an anti-dependent Store.
- int[] anti = new int[code.UID()];
+ assert TOUCHED.isEmpty() && PLACED.isEmpty() && WORK.isEmpty();
+ if( code.UID() > LATE.length ) {
+ int len = Math.max(code.UID(),LATE.length<<1);
+ LATE = new CFGNode[len];
+ ANTI = new int[len];
+ }
+ WORK.clear();
// Breadth-first scheduling
- breadth(code._stop,ns,late,anti);
+ // Early scheduling has released the shared visit bits.
+ breadth(code._stop,PLACED,LATE,ANTI,code.visit());
// Copy the best placement choice into the control slot
- for( int i=0; i<late.length; i++ )
- if( ns[i] != null && !(ns[i] instanceof ProjNode) )
- ns[i].setDef(0,late[i]);
+ for( Node n : PLACED )
+ if( !(n instanceof ProjNode) )
+ n.setDef(0,LATE[n._nid]);
+ for( int i=0; i<TOUCHED.len(); i++ )
+ LATE[TOUCHED.at(i)] = null;
+ TOUCHED.clear();
+ clear(PLACED);
}
- private static void breadth(Node stop, Node[] ns, CFGNode[] late, int[] anti) {
+ // Ary.clear retains references; static scratch must release them as well.
+ private static void clear(Ary<Node> nodes) {
+ Arrays.fill(nodes._es,0,nodes._len,null);
+ nodes.clear();
+ }
+
+ private static void breadth(Node stop, Ary<Node> placed, CFGNode[] late, int[] anti, BitSet visit) {
// Things on the worklist have some (but perhaps not all) uses done.
- WorkList<Node> work = new WorkList<>();
+ WorkList<Node> work = WORK;
work.push(stop);
Node n;
outer:
@@ -177,16 +200,19 @@ public abstract class GlobalCodeMotion {
continue outer;
// All uses done, schedule
- _doSchedLate(n,ns,late,anti);
+ _doSchedLate(n,placed,late,anti);
}
+ TOUCHED.push(n._nid);
+
// A use just finished; reconsider its inputs and waiting loads,
// even when the shared memory input was already scheduled.
for( Node def : n._inputs ) {
if( def==null ) continue;
if( late[def._nid]==null ) work.push(def);
- wakeLoads(def,late,work,new BitSet());
+ wakeLoads(def,late,work,visit);
}
+ visit.clear();
if( n instanceof LoopNode loop )
for( Node phi : loop._outputs )
if( phi instanceof PhiNode && late[phi._nid]==null )
@@ -194,7 +220,7 @@ public abstract class GlobalCodeMotion {
}
}
- private static void _doSchedLate(Node n, Node[] ns, CFGNode[] late, int[] anti) {
+ private static void _doSchedLate(Node n, Ary<Node> placed, CFGNode[] late, int[] anti) {
// Walk uses, gathering the LCA (Least Common Ancestor) of uses
CFGNode early = n.in(0) instanceof CFGNode cfg ? cfg : n.in(0).cfg0();
assert early != null;
@@ -217,7 +243,7 @@ public abstract class GlobalCodeMotion {
if( better(lca,best) )
best = lca;
assert !(best instanceof IfNode);
- ns [n._nid] = n;
+ placed.push(n);
late[n._nid] = best;
}
@@ -259,7 +285,8 @@ public abstract class GlobalCodeMotion {
private static CFGNode find_anti_dep(CFGNode lca, MemOpNode load, CFGNode early, CFGNode[] late, int[] anti) {
// We could skip final-field loads here.
// Walk LCA->early, flagging Load's block location choices
- for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
+ CFGNode marked = lca;
+ for( CFGNode cfg = marked; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
anti[cfg._nid] = load._nid;
// Walk load->mem uses, looking for Stores causing an anti-dep
for( Node mem : load.antiDeps() ) {
@@ -287,6 +314,9 @@ public abstract class GlobalCodeMotion {
default: throw Utils.TODO();
}
}
+ // Release this load's marks before another compilation reuses IDs.
+ for( CFGNode cfg = marked; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
+ anti[cfg._nid] = 0;
return lca;
}
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/CFGNode.java
index 6805c1ef..95aa3dc0 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/CFGNode.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/CFGNode.java
@@ -147,7 +147,9 @@ public abstract class CFGNode extends Node {
}
_ltree = stop._ltree = Parser.XCTRL._ltree = new LoopTree((StartNode)this);
- _bltWalk(2,null,stop, new BitSet());
+ BitSet post = CodeGen.CODE.visit();
+ _bltWalk(2,null,stop, post);
+ post.clear();
}
int _bltWalk( int pre, FunNode fun, StopNode stop, BitSet post ) {
// Pre-walked?
diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/chapter23/src/test/java/com/seaofnodes/simple/Chapter21Test.java
index f25a881a..cfd09502 100644
--- a/chapter23/src/test/java/com/seaofnodes/simple/Chapter21Test.java
+++ b/chapter23/src/test/java/com/seaofnodes/simple/Chapter21Test.java
@@ -416,7 +416,7 @@ public class Chapter21Test {
String sprimes = sb.p("]").toString();
// Compile, link against native C; expect the above string of primes to be printed out by C
- TestC.run("sieve",sprimes, 186);
+ TestC.run("sieve",sprimes, 113);
// Evaluate on RISC5 emulator; expect return of an array of primes in
// the simulated heap.
diff --git a/chapter23/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java b/chapter23/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java
new file mode 100644
index 00000000..319c94b0
--- /dev/null
+++ b/chapter23/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java
@@ -0,0 +1,32 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+public class SchedulingScratchTest {
+ @Test public void testScratchAcrossCompilations() {
+ String src = """
+ int[] !xs = new int[2];
+ xs[0] = arg;
+ int sum = 0;
+ int i = 0;
+ while( i<5 ) {
+ int old = xs[arg&1];
+ xs[0] = i;
+ sum = sum + old;
+ i = i + 1;
+ }
+ return sum;
+ """;
+ // Each fresh compilation reuses node IDs and the scheduler's scratch.
+ for( int round=0; round<3; round++ ) {
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
+ // A small graph after many earlier node allocations.
+ if( round==0 ) code._uid += 100_000;
+ assertEquals("6",Eval2.eval(code,0));
+ assertEquals("0",Eval2.eval(code,1));
+ assertEquals("8",Eval2.eval(code,2));
+ }
+ }
+}
diff --git a/chapter24a/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter24a/src/main/java/com/seaofnodes/simple/IterPeeps.java
index a3b66e09..66afe451 100644
--- a/chapter24a/src/main/java/com/seaofnodes/simple/IterPeeps.java
+++ b/chapter24a/src/main/java/com/seaofnodes/simple/IterPeeps.java
@@ -206,7 +206,7 @@ public class IterPeeps {
* True if Node is on the WorkList
*/
public boolean on( E x ) { return _on.get(x._nid); }
- boolean isEmpty() { return _len==0; }
+ public boolean isEmpty() { return _len==0; }
/**
* Removes a random Node from the WorkList; null if WorkList is empty
@@ -216,11 +216,13 @@ public class IterPeeps {
int idx = _R.nextInt(_len);
E x = (E)_es[idx];
_es[idx] = _es[--_len]; // Compress array
+ _es[_len] = null;
_on.clear(x._nid);
return x;
}
public void clear() {
+ Arrays.fill(_es,0,_len,null);
_len = 0;
_on.clear();
_R.setSeed(_seed);
diff --git a/chapter24a/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java b/chapter24a/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
index da45db4f..205a5cb2 100644
--- a/chapter24a/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
+++ b/chapter24a/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
@@ -4,6 +4,7 @@ import com.seaofnodes.simple.IterPeeps.WorkList;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.AryInt;
import com.seaofnodes.simple.util.Utils;
import java.util.*;
@@ -136,24 +137,46 @@ public abstract class GlobalCodeMotion {
}
// ------------------------------------------------------------------------
+ // Scheduling is not recursive. Retain scratch capacity between schedules,
+ // but release graph references and reset only the entries we used.
+ private static CFGNode[] LATE = new CFGNode[1];
+ // Load NIDs at their CFG block choices, checked against Store blocks.
+ private static int[] ANTI = new int[1];
+ private static final AryInt TOUCHED = new AryInt();
+ private static final Ary<Node> PLACED = new Ary<>(Node.class);
+ private static final WorkList<Node> WORK = new WorkList<>();
+
private static void schedLate( CodeGen code ) {
- CFGNode[] late = new CFGNode[code.UID()];
- Node[] ns = new Node[code.UID()];
- // Record Load NIDs at all their CFG block choices, then check against
- // Store block choices to force a Load above an anti-dependent Store.
- int[] anti = new int[code.UID()];
+ assert TOUCHED.isEmpty() && PLACED.isEmpty() && WORK.isEmpty();
+ if( code.UID() > LATE.length ) {
+ int len = Math.max(code.UID(),LATE.length<<1);
+ LATE = new CFGNode[len];
+ ANTI = new int[len];
+ }
+ WORK.clear();
// Breadth-first scheduling
- breadth(code._stop,ns,late,anti);
+ // Early scheduling has released the shared visit bits.
+ breadth(code._stop,PLACED,LATE,ANTI,code.visit());
// Copy the best placement choice into the control slot
- for( int i=0; i<late.length; i++ )
- if( ns[i] != null && !(ns[i] instanceof ProjNode) )
- ns[i].setDef(0,late[i]);
+ for( Node n : PLACED )
+ if( !(n instanceof ProjNode) )
+ n.setDef(0,LATE[n._nid]);
+ for( int i=0; i<TOUCHED.len(); i++ )
+ LATE[TOUCHED.at(i)] = null;
+ TOUCHED.clear();
+ clear(PLACED);
}
- private static void breadth(Node stop, Node[] ns, CFGNode[] late, int[] anti) {
+ // Ary.clear retains references; static scratch must release them as well.
+ private static void clear(Ary<Node> nodes) {
+ Arrays.fill(nodes._es,0,nodes._len,null);
+ nodes.clear();
+ }
+
+ private static void breadth(Node stop, Ary<Node> placed, CFGNode[] late, int[] anti, BitSet visit) {
// Things on the worklist have some (but perhaps not all) uses done.
- WorkList<Node> work = new WorkList<>();
+ WorkList<Node> work = WORK;
work.push(stop);
Node n;
outer:
@@ -177,16 +200,19 @@ public abstract class GlobalCodeMotion {
continue outer;
// All uses done, schedule
- _doSchedLate(n,ns,late,anti);
+ _doSchedLate(n,placed,late,anti);
}
+ TOUCHED.push(n._nid);
+
// A use just finished; reconsider its inputs and waiting loads,
// even when the shared memory input was already scheduled.
for( Node def : n._inputs ) {
if( def==null ) continue;
if( late[def._nid]==null ) work.push(def);
- wakeLoads(def,late,work,new BitSet());
+ wakeLoads(def,late,work,visit);
}
+ visit.clear();
if( n instanceof LoopNode loop )
for( Node phi : loop._outputs )
if( phi instanceof PhiNode && late[phi._nid]==null )
@@ -194,7 +220,7 @@ public abstract class GlobalCodeMotion {
}
}
- private static void _doSchedLate(Node n, Node[] ns, CFGNode[] late, int[] anti) {
+ private static void _doSchedLate(Node n, Ary<Node> placed, CFGNode[] late, int[] anti) {
// Walk uses, gathering the LCA (Least Common Ancestor) of uses
CFGNode early = n.in(0) instanceof CFGNode cfg ? cfg : n.in(0).cfg0();
assert early != null;
@@ -217,7 +243,7 @@ public abstract class GlobalCodeMotion {
if( better(lca,best) )
best = lca;
assert !(best instanceof IfNode);
- ns [n._nid] = n;
+ placed.push(n);
late[n._nid] = best;
}
@@ -259,7 +285,8 @@ public abstract class GlobalCodeMotion {
private static CFGNode find_anti_dep(CFGNode lca, MemOpNode load, CFGNode early, CFGNode[] late, int[] anti) {
// We could skip final-field loads here.
// Walk LCA->early, flagging Load's block location choices
- for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
+ CFGNode marked = lca;
+ for( CFGNode cfg = marked; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
anti[cfg._nid] = load._nid;
// Walk load->mem uses, looking for Stores causing an anti-dep
for( Node mem : load.antiDeps() ) {
@@ -287,6 +314,9 @@ public abstract class GlobalCodeMotion {
default: throw Utils.TODO();
}
}
+ // Release this load's marks before another compilation reuses IDs.
+ for( CFGNode cfg = marked; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
+ anti[cfg._nid] = 0;
return lca;
}
diff --git a/chapter24a/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/chapter24a/src/main/java/com/seaofnodes/simple/node/CFGNode.java
index 6805c1ef..95aa3dc0 100644
--- a/chapter24a/src/main/java/com/seaofnodes/simple/node/CFGNode.java
+++ b/chapter24a/src/main/java/com/seaofnodes/simple/node/CFGNode.java
@@ -147,7 +147,9 @@ public abstract class CFGNode extends Node {
}
_ltree = stop._ltree = Parser.XCTRL._ltree = new LoopTree((StartNode)this);
- _bltWalk(2,null,stop, new BitSet());
+ BitSet post = CodeGen.CODE.visit();
+ _bltWalk(2,null,stop, post);
+ post.clear();
}
int _bltWalk( int pre, FunNode fun, StopNode stop, BitSet post ) {
// Pre-walked?
diff --git a/chapter24a/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java b/chapter24a/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java
new file mode 100644
index 00000000..319c94b0
--- /dev/null
+++ b/chapter24a/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java
@@ -0,0 +1,32 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+public class SchedulingScratchTest {
+ @Test public void testScratchAcrossCompilations() {
+ String src = """
+ int[] !xs = new int[2];
+ xs[0] = arg;
+ int sum = 0;
+ int i = 0;
+ while( i<5 ) {
+ int old = xs[arg&1];
+ xs[0] = i;
+ sum = sum + old;
+ i = i + 1;
+ }
+ return sum;
+ """;
+ // Each fresh compilation reuses node IDs and the scheduler's scratch.
+ for( int round=0; round<3; round++ ) {
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
+ // A small graph after many earlier node allocations.
+ if( round==0 ) code._uid += 100_000;
+ assertEquals("6",Eval2.eval(code,0));
+ assertEquals("0",Eval2.eval(code,1));
+ assertEquals("8",Eval2.eval(code,2));
+ }
+ }
+}
diff --git a/chapter24b/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter24b/src/main/java/com/seaofnodes/simple/IterPeeps.java
index a3b66e09..66afe451 100644
--- a/chapter24b/src/main/java/com/seaofnodes/simple/IterPeeps.java
+++ b/chapter24b/src/main/java/com/seaofnodes/simple/IterPeeps.java
@@ -206,7 +206,7 @@ public class IterPeeps {
* True if Node is on the WorkList
*/
public boolean on( E x ) { return _on.get(x._nid); }
- boolean isEmpty() { return _len==0; }
+ public boolean isEmpty() { return _len==0; }
/**
* Removes a random Node from the WorkList; null if WorkList is empty
@@ -216,11 +216,13 @@ public class IterPeeps {
int idx = _R.nextInt(_len);
E x = (E)_es[idx];
_es[idx] = _es[--_len]; // Compress array
+ _es[_len] = null;
_on.clear(x._nid);
return x;
}
public void clear() {
+ Arrays.fill(_es,0,_len,null);
_len = 0;
_on.clear();
_R.setSeed(_seed);
diff --git a/chapter24b/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java b/chapter24b/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
index da45db4f..205a5cb2 100644
--- a/chapter24b/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
+++ b/chapter24b/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
@@ -4,6 +4,7 @@ import com.seaofnodes.simple.IterPeeps.WorkList;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.AryInt;
import com.seaofnodes.simple.util.Utils;
import java.util.*;
@@ -136,24 +137,46 @@ public abstract class GlobalCodeMotion {
}
// ------------------------------------------------------------------------
+ // Scheduling is not recursive. Retain scratch capacity between schedules,
+ // but release graph references and reset only the entries we used.
+ private static CFGNode[] LATE = new CFGNode[1];
+ // Load NIDs at their CFG block choices, checked against Store blocks.
+ private static int[] ANTI = new int[1];
+ private static final AryInt TOUCHED = new AryInt();
+ private static final Ary<Node> PLACED = new Ary<>(Node.class);
+ private static final WorkList<Node> WORK = new WorkList<>();
+
private static void schedLate( CodeGen code ) {
- CFGNode[] late = new CFGNode[code.UID()];
- Node[] ns = new Node[code.UID()];
- // Record Load NIDs at all their CFG block choices, then check against
- // Store block choices to force a Load above an anti-dependent Store.
- int[] anti = new int[code.UID()];
+ assert TOUCHED.isEmpty() && PLACED.isEmpty() && WORK.isEmpty();
+ if( code.UID() > LATE.length ) {
+ int len = Math.max(code.UID(),LATE.length<<1);
+ LATE = new CFGNode[len];
+ ANTI = new int[len];
+ }
+ WORK.clear();
// Breadth-first scheduling
- breadth(code._stop,ns,late,anti);
+ // Early scheduling has released the shared visit bits.
+ breadth(code._stop,PLACED,LATE,ANTI,code.visit());
// Copy the best placement choice into the control slot
- for( int i=0; i<late.length; i++ )
- if( ns[i] != null && !(ns[i] instanceof ProjNode) )
- ns[i].setDef(0,late[i]);
+ for( Node n : PLACED )
+ if( !(n instanceof ProjNode) )
+ n.setDef(0,LATE[n._nid]);
+ for( int i=0; i<TOUCHED.len(); i++ )
+ LATE[TOUCHED.at(i)] = null;
+ TOUCHED.clear();
+ clear(PLACED);
}
- private static void breadth(Node stop, Node[] ns, CFGNode[] late, int[] anti) {
+ // Ary.clear retains references; static scratch must release them as well.
+ private static void clear(Ary<Node> nodes) {
+ Arrays.fill(nodes._es,0,nodes._len,null);
+ nodes.clear();
+ }
+
+ private static void breadth(Node stop, Ary<Node> placed, CFGNode[] late, int[] anti, BitSet visit) {
// Things on the worklist have some (but perhaps not all) uses done.
- WorkList<Node> work = new WorkList<>();
+ WorkList<Node> work = WORK;
work.push(stop);
Node n;
outer:
@@ -177,16 +200,19 @@ public abstract class GlobalCodeMotion {
continue outer;
// All uses done, schedule
- _doSchedLate(n,ns,late,anti);
+ _doSchedLate(n,placed,late,anti);
}
+ TOUCHED.push(n._nid);
+
// A use just finished; reconsider its inputs and waiting loads,
// even when the shared memory input was already scheduled.
for( Node def : n._inputs ) {
if( def==null ) continue;
if( late[def._nid]==null ) work.push(def);
- wakeLoads(def,late,work,new BitSet());
+ wakeLoads(def,late,work,visit);
}
+ visit.clear();
if( n instanceof LoopNode loop )
for( Node phi : loop._outputs )
if( phi instanceof PhiNode && late[phi._nid]==null )
@@ -194,7 +220,7 @@ public abstract class GlobalCodeMotion {
}
}
- private static void _doSchedLate(Node n, Node[] ns, CFGNode[] late, int[] anti) {
+ private static void _doSchedLate(Node n, Ary<Node> placed, CFGNode[] late, int[] anti) {
// Walk uses, gathering the LCA (Least Common Ancestor) of uses
CFGNode early = n.in(0) instanceof CFGNode cfg ? cfg : n.in(0).cfg0();
assert early != null;
@@ -217,7 +243,7 @@ public abstract class GlobalCodeMotion {
if( better(lca,best) )
best = lca;
assert !(best instanceof IfNode);
- ns [n._nid] = n;
+ placed.push(n);
late[n._nid] = best;
}
@@ -259,7 +285,8 @@ public abstract class GlobalCodeMotion {
private static CFGNode find_anti_dep(CFGNode lca, MemOpNode load, CFGNode early, CFGNode[] late, int[] anti) {
// We could skip final-field loads here.
// Walk LCA->early, flagging Load's block location choices
- for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
+ CFGNode marked = lca;
+ for( CFGNode cfg = marked; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
anti[cfg._nid] = load._nid;
// Walk load->mem uses, looking for Stores causing an anti-dep
for( Node mem : load.antiDeps() ) {
@@ -287,6 +314,9 @@ public abstract class GlobalCodeMotion {
default: throw Utils.TODO();
}
}
+ // Release this load's marks before another compilation reuses IDs.
+ for( CFGNode cfg = marked; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
+ anti[cfg._nid] = 0;
return lca;
}
diff --git a/chapter24b/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/chapter24b/src/main/java/com/seaofnodes/simple/node/CFGNode.java
index 6805c1ef..95aa3dc0 100644
--- a/chapter24b/src/main/java/com/seaofnodes/simple/node/CFGNode.java
+++ b/chapter24b/src/main/java/com/seaofnodes/simple/node/CFGNode.java
@@ -147,7 +147,9 @@ public abstract class CFGNode extends Node {
}
_ltree = stop._ltree = Parser.XCTRL._ltree = new LoopTree((StartNode)this);
- _bltWalk(2,null,stop, new BitSet());
+ BitSet post = CodeGen.CODE.visit();
+ _bltWalk(2,null,stop, post);
+ post.clear();
}
int _bltWalk( int pre, FunNode fun, StopNode stop, BitSet post ) {
// Pre-walked?
diff --git a/chapter24b/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java b/chapter24b/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java
new file mode 100644
index 00000000..319c94b0
--- /dev/null
+++ b/chapter24b/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java
@@ -0,0 +1,32 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+public class SchedulingScratchTest {
+ @Test public void testScratchAcrossCompilations() {
+ String src = """
+ int[] !xs = new int[2];
+ xs[0] = arg;
+ int sum = 0;
+ int i = 0;
+ while( i<5 ) {
+ int old = xs[arg&1];
+ xs[0] = i;
+ sum = sum + old;
+ i = i + 1;
+ }
+ return sum;
+ """;
+ // Each fresh compilation reuses node IDs and the scheduler's scratch.
+ for( int round=0; round<3; round++ ) {
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
+ // A small graph after many earlier node allocations.
+ if( round==0 ) code._uid += 100_000;
+ assertEquals("6",Eval2.eval(code,0));
+ assertEquals("0",Eval2.eval(code,1));
+ assertEquals("8",Eval2.eval(code,2));
+ }
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter25/src/main/java/com/seaofnodes/simple/IterPeeps.java
index 2fe4e172..66afe451 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/IterPeeps.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/IterPeeps.java
@@ -216,11 +216,13 @@ public class IterPeeps {
int idx = _R.nextInt(_len);
E x = (E)_es[idx];
_es[idx] = _es[--_len]; // Compress array
+ _es[_len] = null;
_on.clear(x._nid);
return x;
}
public void clear() {
+ Arrays.fill(_es,0,_len,null);
_len = 0;
_on.clear();
_R.setSeed(_seed);
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
index da45db4f..205a5cb2 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
@@ -4,6 +4,7 @@ import com.seaofnodes.simple.IterPeeps.WorkList;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.AryInt;
import com.seaofnodes.simple.util.Utils;
import java.util.*;
@@ -136,24 +137,46 @@ public abstract class GlobalCodeMotion {
}
// ------------------------------------------------------------------------
+ // Scheduling is not recursive. Retain scratch capacity between schedules,
+ // but release graph references and reset only the entries we used.
+ private static CFGNode[] LATE = new CFGNode[1];
+ // Load NIDs at their CFG block choices, checked against Store blocks.
+ private static int[] ANTI = new int[1];
+ private static final AryInt TOUCHED = new AryInt();
+ private static final Ary<Node> PLACED = new Ary<>(Node.class);
+ private static final WorkList<Node> WORK = new WorkList<>();
+
private static void schedLate( CodeGen code ) {
- CFGNode[] late = new CFGNode[code.UID()];
- Node[] ns = new Node[code.UID()];
- // Record Load NIDs at all their CFG block choices, then check against
- // Store block choices to force a Load above an anti-dependent Store.
- int[] anti = new int[code.UID()];
+ assert TOUCHED.isEmpty() && PLACED.isEmpty() && WORK.isEmpty();
+ if( code.UID() > LATE.length ) {
+ int len = Math.max(code.UID(),LATE.length<<1);
+ LATE = new CFGNode[len];
+ ANTI = new int[len];
+ }
+ WORK.clear();
// Breadth-first scheduling
- breadth(code._stop,ns,late,anti);
+ // Early scheduling has released the shared visit bits.
+ breadth(code._stop,PLACED,LATE,ANTI,code.visit());
// Copy the best placement choice into the control slot
- for( int i=0; i<late.length; i++ )
- if( ns[i] != null && !(ns[i] instanceof ProjNode) )
- ns[i].setDef(0,late[i]);
+ for( Node n : PLACED )
+ if( !(n instanceof ProjNode) )
+ n.setDef(0,LATE[n._nid]);
+ for( int i=0; i<TOUCHED.len(); i++ )
+ LATE[TOUCHED.at(i)] = null;
+ TOUCHED.clear();
+ clear(PLACED);
}
- private static void breadth(Node stop, Node[] ns, CFGNode[] late, int[] anti) {
+ // Ary.clear retains references; static scratch must release them as well.
+ private static void clear(Ary<Node> nodes) {
+ Arrays.fill(nodes._es,0,nodes._len,null);
+ nodes.clear();
+ }
+
+ private static void breadth(Node stop, Ary<Node> placed, CFGNode[] late, int[] anti, BitSet visit) {
// Things on the worklist have some (but perhaps not all) uses done.
- WorkList<Node> work = new WorkList<>();
+ WorkList<Node> work = WORK;
work.push(stop);
Node n;
outer:
@@ -177,16 +200,19 @@ public abstract class GlobalCodeMotion {
continue outer;
// All uses done, schedule
- _doSchedLate(n,ns,late,anti);
+ _doSchedLate(n,placed,late,anti);
}
+ TOUCHED.push(n._nid);
+
// A use just finished; reconsider its inputs and waiting loads,
// even when the shared memory input was already scheduled.
for( Node def : n._inputs ) {
if( def==null ) continue;
if( late[def._nid]==null ) work.push(def);
- wakeLoads(def,late,work,new BitSet());
+ wakeLoads(def,late,work,visit);
}
+ visit.clear();
if( n instanceof LoopNode loop )
for( Node phi : loop._outputs )
if( phi instanceof PhiNode && late[phi._nid]==null )
@@ -194,7 +220,7 @@ public abstract class GlobalCodeMotion {
}
}
- private static void _doSchedLate(Node n, Node[] ns, CFGNode[] late, int[] anti) {
+ private static void _doSchedLate(Node n, Ary<Node> placed, CFGNode[] late, int[] anti) {
// Walk uses, gathering the LCA (Least Common Ancestor) of uses
CFGNode early = n.in(0) instanceof CFGNode cfg ? cfg : n.in(0).cfg0();
assert early != null;
@@ -217,7 +243,7 @@ public abstract class GlobalCodeMotion {
if( better(lca,best) )
best = lca;
assert !(best instanceof IfNode);
- ns [n._nid] = n;
+ placed.push(n);
late[n._nid] = best;
}
@@ -259,7 +285,8 @@ public abstract class GlobalCodeMotion {
private static CFGNode find_anti_dep(CFGNode lca, MemOpNode load, CFGNode early, CFGNode[] late, int[] anti) {
// We could skip final-field loads here.
// Walk LCA->early, flagging Load's block location choices
- for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
+ CFGNode marked = lca;
+ for( CFGNode cfg = marked; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
anti[cfg._nid] = load._nid;
// Walk load->mem uses, looking for Stores causing an anti-dep
for( Node mem : load.antiDeps() ) {
@@ -287,6 +314,9 @@ public abstract class GlobalCodeMotion {
default: throw Utils.TODO();
}
}
+ // Release this load's marks before another compilation reuses IDs.
+ for( CFGNode cfg = marked; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
+ anti[cfg._nid] = 0;
return lca;
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CFGNode.java
index 6805c1ef..95aa3dc0 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/CFGNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CFGNode.java
@@ -147,7 +147,9 @@ public abstract class CFGNode extends Node {
}
_ltree = stop._ltree = Parser.XCTRL._ltree = new LoopTree((StartNode)this);
- _bltWalk(2,null,stop, new BitSet());
+ BitSet post = CodeGen.CODE.visit();
+ _bltWalk(2,null,stop, post);
+ post.clear();
}
int _bltWalk( int pre, FunNode fun, StopNode stop, BitSet post ) {
// Pre-walked?
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java b/chapter25/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java
new file mode 100644
index 00000000..319c94b0
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java
@@ -0,0 +1,32 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+public class SchedulingScratchTest {
+ @Test public void testScratchAcrossCompilations() {
+ String src = """
+ int[] !xs = new int[2];
+ xs[0] = arg;
+ int sum = 0;
+ int i = 0;
+ while( i<5 ) {
+ int old = xs[arg&1];
+ xs[0] = i;
+ sum = sum + old;
+ i = i + 1;
+ }
+ return sum;
+ """;
+ // Each fresh compilation reuses node IDs and the scheduler's scratch.
+ for( int round=0; round<3; round++ ) {
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
+ // A small graph after many earlier node allocations.
+ if( round==0 ) code._uid += 100_000;
+ assertEquals("6",Eval2.eval(code,0));
+ assertEquals("0",Eval2.eval(code,1));
+ assertEquals("8",Eval2.eval(code,2));
+ }
+ }
+}
diff --git a/chapter26/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter26/src/main/java/com/seaofnodes/simple/IterPeeps.java
index 2fe4e172..66afe451 100644
--- a/chapter26/src/main/java/com/seaofnodes/simple/IterPeeps.java
+++ b/chapter26/src/main/java/com/seaofnodes/simple/IterPeeps.java
@@ -216,11 +216,13 @@ public class IterPeeps {
int idx = _R.nextInt(_len);
E x = (E)_es[idx];
_es[idx] = _es[--_len]; // Compress array
+ _es[_len] = null;
_on.clear(x._nid);
return x;
}
public void clear() {
+ Arrays.fill(_es,0,_len,null);
_len = 0;
_on.clear();
_R.setSeed(_seed);
diff --git a/chapter26/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java b/chapter26/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
index 9be662e8..d4a1966d 100644
--- a/chapter26/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
+++ b/chapter26/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
@@ -4,6 +4,7 @@ import com.seaofnodes.simple.IterPeeps.WorkList;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.AryInt;
import com.seaofnodes.simple.util.Utils;
import java.util.*;
@@ -136,24 +137,46 @@ public abstract class GlobalCodeMotion {
}
// ------------------------------------------------------------------------
+ // Scheduling is not recursive. Retain scratch capacity between schedules,
+ // but release graph references and reset only the entries we used.
+ private static CFGNode[] LATE = new CFGNode[1];
+ // Load NIDs at their CFG block choices, checked against Store blocks.
+ private static int[] ANTI = new int[1];
+ private static final AryInt TOUCHED = new AryInt();
+ private static final Ary<Node> PLACED = new Ary<>(Node.class);
+ private static final WorkList<Node> WORK = new WorkList<>();
+
private static void schedLate( CodeGen code ) {
- CFGNode[] late = new CFGNode[code.UID()];
- Node[] ns = new Node[code.UID()];
- // Record Load NIDs at all their CFG block choices, then check against
- // Store block choices to force a Load above an anti-dependent Store.
- int[] anti = new int[code.UID()];
+ assert TOUCHED.isEmpty() && PLACED.isEmpty() && WORK.isEmpty();
+ if( code.UID() > LATE.length ) {
+ int len = Math.max(code.UID(),LATE.length<<1);
+ LATE = new CFGNode[len];
+ ANTI = new int[len];
+ }
+ WORK.clear();
// Breadth-first scheduling
- breadth(code._stop,ns,late,anti);
+ // Early scheduling has released the shared visit bits.
+ breadth(code._stop,PLACED,LATE,ANTI,code.visit());
// Copy the best placement choice into the control slot
- for( int i=0; i<late.length; i++ )
- if( ns[i] != null && !(ns[i] instanceof ProjNode) )
- ns[i].setDef(0,late[i]);
+ for( Node n : PLACED )
+ if( !(n instanceof ProjNode) )
+ n.setDef(0,LATE[n._nid]);
+ for( int i=0; i<TOUCHED.len(); i++ )
+ LATE[TOUCHED.at(i)] = null;
+ TOUCHED.clear();
+ clear(PLACED);
}
- private static void breadth(Node stop, Node[] ns, CFGNode[] late, int[] anti) {
+ // Ary.clear retains references; static scratch must release them as well.
+ private static void clear(Ary<Node> nodes) {
+ Arrays.fill(nodes._es,0,nodes._len,null);
+ nodes.clear();
+ }
+
+ private static void breadth(Node stop, Ary<Node> placed, CFGNode[] late, int[] anti, BitSet visit) {
// Things on the worklist have some (but perhaps not all) uses done.
- WorkList<Node> work = new WorkList<>();
+ WorkList<Node> work = WORK;
work.push(stop);
Node n;
outer:
@@ -177,16 +200,19 @@ public abstract class GlobalCodeMotion {
continue outer;
// All uses done, schedule
- _doSchedLate(n,ns,late,anti);
+ _doSchedLate(n,placed,late,anti);
}
+ TOUCHED.push(n._nid);
+
// A use just finished; reconsider its inputs and waiting loads,
// even when the shared memory input was already scheduled.
for( Node def : n._inputs ) {
if( def==null ) continue;
if( late[def._nid]==null ) work.push(def);
- wakeLoads(def,late,work,new BitSet());
+ wakeLoads(def,late,work,visit);
}
+ visit.clear();
if( n instanceof LoopNode loop )
for( Node phi : loop._outputs )
if( phi instanceof PhiNode && late[phi._nid]==null )
@@ -194,7 +220,7 @@ public abstract class GlobalCodeMotion {
}
}
- private static void _doSchedLate(Node n, Node[] ns, CFGNode[] late, int[] anti) {
+ private static void _doSchedLate(Node n, Ary<Node> placed, CFGNode[] late, int[] anti) {
// Walk uses, gathering the LCA (Least Common Ancestor) of uses
CFGNode early = n.in(0) instanceof CFGNode cfg ? cfg : n.in(0).cfg0();
assert early != null;
@@ -217,7 +243,7 @@ public abstract class GlobalCodeMotion {
if( better(lca,best) )
best = lca;
assert !(best instanceof IfNode);
- ns [n._nid] = n;
+ placed.push(n);
late[n._nid] = best;
}
@@ -259,7 +285,8 @@ public abstract class GlobalCodeMotion {
private static CFGNode find_anti_dep(CFGNode lca, MemOpNode load, CFGNode early, CFGNode[] late, int[] anti) {
// We could skip final-field loads here.
// Walk LCA->early, flagging Load's block location choices
- for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
+ CFGNode marked = lca;
+ for( CFGNode cfg = marked; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
anti[cfg._nid] = load._nid;
// Walk load->mem uses, looking for Stores causing an anti-dep
for( Node mem : load.antiDeps() ) {
@@ -287,6 +314,9 @@ public abstract class GlobalCodeMotion {
default: throw Utils.TODO();
}
}
+ // Release this load's marks before another compilation reuses IDs.
+ for( CFGNode cfg = marked; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
+ anti[cfg._nid] = 0;
return lca;
}
diff --git a/chapter26/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/chapter26/src/main/java/com/seaofnodes/simple/node/CFGNode.java
index 6805c1ef..95aa3dc0 100644
--- a/chapter26/src/main/java/com/seaofnodes/simple/node/CFGNode.java
+++ b/chapter26/src/main/java/com/seaofnodes/simple/node/CFGNode.java
@@ -147,7 +147,9 @@ public abstract class CFGNode extends Node {
}
_ltree = stop._ltree = Parser.XCTRL._ltree = new LoopTree((StartNode)this);
- _bltWalk(2,null,stop, new BitSet());
+ BitSet post = CodeGen.CODE.visit();
+ _bltWalk(2,null,stop, post);
+ post.clear();
}
int _bltWalk( int pre, FunNode fun, StopNode stop, BitSet post ) {
// Pre-walked?
diff --git a/chapter26/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java b/chapter26/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java
new file mode 100644
index 00000000..319c94b0
--- /dev/null
+++ b/chapter26/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java
@@ -0,0 +1,32 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+public class SchedulingScratchTest {
+ @Test public void testScratchAcrossCompilations() {
+ String src = """
+ int[] !xs = new int[2];
+ xs[0] = arg;
+ int sum = 0;
+ int i = 0;
+ while( i<5 ) {
+ int old = xs[arg&1];
+ xs[0] = i;
+ sum = sum + old;
+ i = i + 1;
+ }
+ return sum;
+ """;
+ // Each fresh compilation reuses node IDs and the scheduler's scratch.
+ for( int round=0; round<3; round++ ) {
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
+ // A small graph after many earlier node allocations.
+ if( round==0 ) code._uid += 100_000;
+ assertEquals("6",Eval2.eval(code,0));
+ assertEquals("0",Eval2.eval(code,1));
+ assertEquals("8",Eval2.eval(code,2));
+ }
+ }
+}
diff --git a/chapter27/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter27/src/main/java/com/seaofnodes/simple/IterPeeps.java
index 2fe4e172..66afe451 100644
--- a/chapter27/src/main/java/com/seaofnodes/simple/IterPeeps.java
+++ b/chapter27/src/main/java/com/seaofnodes/simple/IterPeeps.java
@@ -216,11 +216,13 @@ public class IterPeeps {
int idx = _R.nextInt(_len);
E x = (E)_es[idx];
_es[idx] = _es[--_len]; // Compress array
+ _es[_len] = null;
_on.clear(x._nid);
return x;
}
public void clear() {
+ Arrays.fill(_es,0,_len,null);
_len = 0;
_on.clear();
_R.setSeed(_seed);
diff --git a/chapter27/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java b/chapter27/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
index 9be662e8..d4a1966d 100644
--- a/chapter27/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
+++ b/chapter27/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
@@ -4,6 +4,7 @@ import com.seaofnodes.simple.IterPeeps.WorkList;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.AryInt;
import com.seaofnodes.simple.util.Utils;
import java.util.*;
@@ -136,24 +137,46 @@ public abstract class GlobalCodeMotion {
}
// ------------------------------------------------------------------------
+ // Scheduling is not recursive. Retain scratch capacity between schedules,
+ // but release graph references and reset only the entries we used.
+ private static CFGNode[] LATE = new CFGNode[1];
+ // Load NIDs at their CFG block choices, checked against Store blocks.
+ private static int[] ANTI = new int[1];
+ private static final AryInt TOUCHED = new AryInt();
+ private static final Ary<Node> PLACED = new Ary<>(Node.class);
+ private static final WorkList<Node> WORK = new WorkList<>();
+
private static void schedLate( CodeGen code ) {
- CFGNode[] late = new CFGNode[code.UID()];
- Node[] ns = new Node[code.UID()];
- // Record Load NIDs at all their CFG block choices, then check against
- // Store block choices to force a Load above an anti-dependent Store.
- int[] anti = new int[code.UID()];
+ assert TOUCHED.isEmpty() && PLACED.isEmpty() && WORK.isEmpty();
+ if( code.UID() > LATE.length ) {
+ int len = Math.max(code.UID(),LATE.length<<1);
+ LATE = new CFGNode[len];
+ ANTI = new int[len];
+ }
+ WORK.clear();
// Breadth-first scheduling
- breadth(code._stop,ns,late,anti);
+ // Early scheduling has released the shared visit bits.
+ breadth(code._stop,PLACED,LATE,ANTI,code.visit());
// Copy the best placement choice into the control slot
- for( int i=0; i<late.length; i++ )
- if( ns[i] != null && !(ns[i] instanceof ProjNode) )
- ns[i].setDef(0,late[i]);
+ for( Node n : PLACED )
+ if( !(n instanceof ProjNode) )
+ n.setDef(0,LATE[n._nid]);
+ for( int i=0; i<TOUCHED.len(); i++ )
+ LATE[TOUCHED.at(i)] = null;
+ TOUCHED.clear();
+ clear(PLACED);
}
- private static void breadth(Node stop, Node[] ns, CFGNode[] late, int[] anti) {
+ // Ary.clear retains references; static scratch must release them as well.
+ private static void clear(Ary<Node> nodes) {
+ Arrays.fill(nodes._es,0,nodes._len,null);
+ nodes.clear();
+ }
+
+ private static void breadth(Node stop, Ary<Node> placed, CFGNode[] late, int[] anti, BitSet visit) {
// Things on the worklist have some (but perhaps not all) uses done.
- WorkList<Node> work = new WorkList<>();
+ WorkList<Node> work = WORK;
work.push(stop);
Node n;
outer:
@@ -177,16 +200,19 @@ public abstract class GlobalCodeMotion {
continue outer;
// All uses done, schedule
- _doSchedLate(n,ns,late,anti);
+ _doSchedLate(n,placed,late,anti);
}
+ TOUCHED.push(n._nid);
+
// A use just finished; reconsider its inputs and waiting loads,
// even when the shared memory input was already scheduled.
for( Node def : n._inputs ) {
if( def==null ) continue;
if( late[def._nid]==null ) work.push(def);
- wakeLoads(def,late,work,new BitSet());
+ wakeLoads(def,late,work,visit);
}
+ visit.clear();
if( n instanceof LoopNode loop )
for( Node phi : loop._outputs )
if( phi instanceof PhiNode && late[phi._nid]==null )
@@ -194,7 +220,7 @@ public abstract class GlobalCodeMotion {
}
}
- private static void _doSchedLate(Node n, Node[] ns, CFGNode[] late, int[] anti) {
+ private static void _doSchedLate(Node n, Ary<Node> placed, CFGNode[] late, int[] anti) {
// Walk uses, gathering the LCA (Least Common Ancestor) of uses
CFGNode early = n.in(0) instanceof CFGNode cfg ? cfg : n.in(0).cfg0();
assert early != null;
@@ -217,7 +243,7 @@ public abstract class GlobalCodeMotion {
if( better(lca,best) )
best = lca;
assert !(best instanceof IfNode);
- ns [n._nid] = n;
+ placed.push(n);
late[n._nid] = best;
}
@@ -259,7 +285,8 @@ public abstract class GlobalCodeMotion {
private static CFGNode find_anti_dep(CFGNode lca, MemOpNode load, CFGNode early, CFGNode[] late, int[] anti) {
// We could skip final-field loads here.
// Walk LCA->early, flagging Load's block location choices
- for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
+ CFGNode marked = lca;
+ for( CFGNode cfg = marked; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
anti[cfg._nid] = load._nid;
// Walk load->mem uses, looking for Stores causing an anti-dep
for( Node mem : load.antiDeps() ) {
@@ -287,6 +314,9 @@ public abstract class GlobalCodeMotion {
default: throw Utils.TODO();
}
}
+ // Release this load's marks before another compilation reuses IDs.
+ for( CFGNode cfg = marked; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
+ anti[cfg._nid] = 0;
return lca;
}
diff --git a/chapter27/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/chapter27/src/main/java/com/seaofnodes/simple/node/CFGNode.java
index 6805c1ef..95aa3dc0 100644
--- a/chapter27/src/main/java/com/seaofnodes/simple/node/CFGNode.java
+++ b/chapter27/src/main/java/com/seaofnodes/simple/node/CFGNode.java
@@ -147,7 +147,9 @@ public abstract class CFGNode extends Node {
}
_ltree = stop._ltree = Parser.XCTRL._ltree = new LoopTree((StartNode)this);
- _bltWalk(2,null,stop, new BitSet());
+ BitSet post = CodeGen.CODE.visit();
+ _bltWalk(2,null,stop, post);
+ post.clear();
}
int _bltWalk( int pre, FunNode fun, StopNode stop, BitSet post ) {
// Pre-walked?
diff --git a/chapter27/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java b/chapter27/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java
new file mode 100644
index 00000000..319c94b0
--- /dev/null
+++ b/chapter27/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java
@@ -0,0 +1,32 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+public class SchedulingScratchTest {
+ @Test public void testScratchAcrossCompilations() {
+ String src = """
+ int[] !xs = new int[2];
+ xs[0] = arg;
+ int sum = 0;
+ int i = 0;
+ while( i<5 ) {
+ int old = xs[arg&1];
+ xs[0] = i;
+ sum = sum + old;
+ i = i + 1;
+ }
+ return sum;
+ """;
+ // Each fresh compilation reuses node IDs and the scheduler's scratch.
+ for( int round=0; round<3; round++ ) {
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
+ // A small graph after many earlier node allocations.
+ if( round==0 ) code._uid += 100_000;
+ assertEquals("6",Eval2.eval(code,0));
+ assertEquals("0",Eval2.eval(code,1));
+ assertEquals("8",Eval2.eval(code,2));
+ }
+ }
+}
diff --git a/chapter28a/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter28a/src/main/java/com/seaofnodes/simple/IterPeeps.java
index e65ef6d9..39230eb2 100644
--- a/chapter28a/src/main/java/com/seaofnodes/simple/IterPeeps.java
+++ b/chapter28a/src/main/java/com/seaofnodes/simple/IterPeeps.java
@@ -333,17 +333,18 @@ public class IterPeeps {
int idx = _R.nextInt(_len);
E x = (E)_es[idx];
_es[idx] = _es[--_len]; // Compress array
+ _es[_len] = null;
_on.clear(x._nid);
return x;
}
public boolean isEmpty() { return _len==0; }
- //Node[] asAry() { return Arrays.copyOf(_es,_len); }
- //public void clear() {
- // _len = 0;
- // _on.clear();
- // _R.setSeed(_seed);
- // _totalWork = 0;
- //}
+ public void clear() {
+ Arrays.fill(_es,0,_len,null);
+ _len = 0;
+ _on.clear();
+ _R.setSeed(_seed);
+ _totalWork = 0;
+ }
}
}
diff --git a/chapter28a/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java b/chapter28a/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
index 4477025b..ba3743e3 100644
--- a/chapter28a/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
+++ b/chapter28a/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
@@ -4,6 +4,7 @@ import com.seaofnodes.simple.IterPeeps.WorkList;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.AryInt;
import com.seaofnodes.simple.util.Utils;
import java.util.*;
@@ -136,25 +137,53 @@ public abstract class GlobalCodeMotion {
}
// ------------------------------------------------------------------------
+ // Scheduling is not recursive. Retain scratch capacity between schedules,
+ // but release graph references and reset only the entries we used.
+ private static CFGNode[] LATE = new CFGNode[1];
+ // Load NIDs at their CFG block choices, checked against Store blocks.
+ private static int[] ANTI = new int[1];
+ private static final AryInt TOUCHED = new AryInt();
+ private static final Ary<Node> PLACED = new Ary<>(Node.class);
+ private static final Ary<Node> ANTIDEPS = new Ary<>(Node.class);
+ private static final WorkList<Node> WORK = new WorkList<>();
+
private static void schedLate( CodeGen code ) {
- CFGNode[] late = new CFGNode[code.UID()];
- Node[] ns = new Node[code.UID()];
- // Record Load NIDs at all their CFG block choices, then check against
- // Store block choices to force a Load above an anti-dependent Store.
- int[] anti = new int[code.UID()];
- Ary<Node> antideps = new Ary<>(Node.class);
+ assert TOUCHED.isEmpty() && PLACED.isEmpty() && WORK.isEmpty();
+ if( code.UID() > LATE.length ) {
+ int len = Math.max(code.UID(),LATE.length<<1);
+ LATE = new CFGNode[len];
+ ANTI = new int[len];
+ }
+ WORK.clear();
// Breadth-first scheduling
- breadth(code._stop,ns,late,anti, antideps);
+ // Early scheduling has released the shared visit bits.
+ breadth(code._stop,PLACED,LATE,ANTI,ANTIDEPS,code.visit());
// Copy the best placement choice into the control slot
- for( int i=0; i<late.length; i++ )
- if( ns[i] != null && !(ns[i] instanceof ProjNode) )
- ns[i].setDef(0,late[i]);
+ for( Node n : PLACED )
+ if( !(n instanceof ProjNode) )
+ n.setDef(0,LATE[n._nid]);
+ for( int i=0; i<TOUCHED.len(); i++ )
+ LATE[TOUCHED.at(i)] = null;
+ TOUCHED.clear();
+ clear(PLACED);
+ clear(ANTIDEPS);
+ }
+
+ // Ary.clear retains references; static scratch must release them as well.
+ private static void clear(Ary<Node> nodes) {
+ Arrays.fill(nodes._es,0,nodes._len,null);
+ nodes.clear();
}
- private static void breadth(Node stop, Node[] ns, CFGNode[] late, int[] anti, Ary<Node> antideps) {
+ private static Ary<Node> antiDeps(MemOpNode load, Ary<Node> antideps) {
+ clear(antideps);
+ return load.antiDeps(antideps);
+ }
+
+ private static void breadth(Node stop, Ary<Node> placed, CFGNode[] late, int[] anti, Ary<Node> antideps, BitSet visit) {
// Things on the worklist have some (but perhaps not all) uses done.
- WorkList<Node> work = new WorkList<>();
+ WorkList<Node> work = WORK;
work.push(stop);
Node n;
outer:
@@ -173,21 +202,24 @@ public abstract class GlobalCodeMotion {
// Loads need their memory inputs' uses also done
if( n instanceof MemOpNode ld && ld._isLoad )
- for( Node memuse : ld.antiDeps(antideps) )
+ for( Node memuse : antiDeps(ld,antideps) )
if( late[memuse._nid]==null )
continue outer;
// All uses done, schedule
- _doSchedLate(n,ns,late,anti,antideps);
+ _doSchedLate(n,placed,late,anti,antideps);
}
+ TOUCHED.push(n._nid);
+
// A use just finished; reconsider its inputs and waiting loads,
// even when the shared memory input was already scheduled.
for( Node def : n._inputs ) {
if( def==null ) continue;
if( late[def._nid]==null ) work.push(def);
- wakeLoads(def,late,work,new BitSet());
+ wakeLoads(def,late,work,visit);
}
+ visit.clear();
if( n instanceof LoopNode loop )
for( Node phi : loop._outputs )
if( phi instanceof PhiNode && late[phi._nid]==null )
@@ -195,7 +227,7 @@ public abstract class GlobalCodeMotion {
}
}
- private static void _doSchedLate(Node n, Node[] ns, CFGNode[] late, int[] anti, Ary<Node> antideps) {
+ private static void _doSchedLate(Node n, Ary<Node> placed, CFGNode[] late, int[] anti, Ary<Node> antideps) {
// Walk uses, gathering the LCA (Least Common Ancestor) of uses
CFGNode early = n.in(0) instanceof CFGNode cfg ? cfg : n.in(0).cfg0();
assert early != null;
@@ -217,7 +249,7 @@ public abstract class GlobalCodeMotion {
if( better(lca,best) )
best = lca;
assert !(best instanceof IfNode);
- ns [n._nid] = n;
+ placed.push(n);
late[n._nid] = best;
}
@@ -260,10 +292,11 @@ public abstract class GlobalCodeMotion {
private static CFGNode find_anti_dep(CFGNode lca, MemOpNode load, CFGNode early, CFGNode[] late, int[] anti, Ary<Node> antideps) {
// We could skip final-field loads here.
// Walk LCA->early, flagging Load's block location choices
- for( CFGNode cfg=lca; cfg!=early.idom(); cfg = cfg.idom() )
+ CFGNode marked = lca;
+ for( CFGNode cfg = marked; cfg!=early.idom(); cfg = cfg.idom() )
anti[cfg._nid] = load._nid;
// Walk load->mem uses, looking for Stores causing an anti-dep
- for( Node mem : load.antiDeps(antideps) ) {
+ for( Node mem : antiDeps(load,antideps) ) {
if( mem instanceof PhiNode phi ) {
// Inputs carrying different memory might overwrite this alias.
// Raise the LCA for those paths; the unchanged-memory paths
@@ -279,6 +312,9 @@ public abstract class GlobalCodeMotion {
lca = anti_dep(load,late[mem._nid],mem.cfg0(),lca,mem,anti);
}
}
+ // Release this load's marks before another compilation reuses IDs.
+ for( CFGNode cfg = marked; cfg!=early.idom(); cfg = cfg.idom() )
+ anti[cfg._nid] = 0;
return lca;
}
diff --git a/chapter28a/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/chapter28a/src/main/java/com/seaofnodes/simple/node/CFGNode.java
index 4f225fbe..e07b241b 100644
--- a/chapter28a/src/main/java/com/seaofnodes/simple/node/CFGNode.java
+++ b/chapter28a/src/main/java/com/seaofnodes/simple/node/CFGNode.java
@@ -203,7 +203,7 @@ public abstract class CFGNode extends Node {
_ltree = stop._ltree = CodeGen.CODE.XCTRL._ltree = new LoopTree((StartNode)this);
Ary<FunNode> work = new Ary<>(FunNode.class);
- BitSet post = new BitSet();
+ BitSet post = CodeGen.CODE.visit();
int pre = 1;
_pre = pre++;
for( FunNode fun : funs ) {
@@ -219,6 +219,7 @@ public abstract class CFGNode extends Node {
for( int i=0; i<work._len; i++ )
pre = work.at(i)._bltWalk(pre,work.at(i),stop, post, work);
+ post.clear();
}
int _bltWalk( int pre, FunNode fun, StopNode stop, BitSet post, Ary<FunNode> work ) {
diff --git a/chapter28a/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java b/chapter28a/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java
new file mode 100644
index 00000000..319c94b0
--- /dev/null
+++ b/chapter28a/src/test/java/com/seaofnodes/simple/SchedulingScratchTest.java
@@ -0,0 +1,32 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+public class SchedulingScratchTest {
+ @Test public void testScratchAcrossCompilations() {
+ String src = """
+ int[] !xs = new int[2];
+ xs[0] = arg;
+ int sum = 0;
+ int i = 0;
+ while( i<5 ) {
+ int old = xs[arg&1];
+ xs[0] = i;
+ sum = sum + old;
+ i = i + 1;
+ }
+ return sum;
+ """;
+ // Each fresh compilation reuses node IDs and the scheduler's scratch.
+ for( int round=0; round<3; round++ ) {
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
+ // A small graph after many earlier node allocations.
+ if( round==0 ) code._uid += 100_000;
+ assertEquals("6",Eval2.eval(code,0));
+ assertEquals("0",Eval2.eval(code,1));
+ assertEquals("8",Eval2.eval(code,2));
+ }
+ }
+}
diff --git a/chapter28b/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter28b/src/main/java/com/seaofnodes/simple/IterPeeps.java
index 42e6695e..a018c3ee 100644
--- a/chapter28b/src/main/java/com/seaofnodes/simple/IterPeeps.java
+++ b/chapter28b/src/main/java/com/seaofnodes/simple/IterPeeps.java
@@ -334,17 +334,18 @@ public class IterPeeps {
int idx = _R.nextInt(_len);
E x = (E)_es[idx];
_es[idx] = _es[--_len]; // Compress array
+ _es[_len] = null;
_on.clear(x._nid);
return x;
}
public boolean isEmpty() { return _len==0; }
- //Node[] asAry() { return Arrays.copyOf(_es,_len); }
- //public void clear() {
- // _len = 0;
- // _on.clear();
- // _R.setSeed(_seed);
- // _totalWork = 0;
- //}
+ public void clear() {
+ Arrays.fill(_es,0,_len,null);
+ _len = 0;
+ _on.clear();
+ _R.setSeed(_seed);
+ _totalWork = 0;
+ }
}
}
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 42999894..a1ac4e67 100644
--- a/chapter28b/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
+++ b/chapter28b/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
@@ -4,6 +4,7 @@ import com.seaofnodes.simple.IterPeeps.WorkList;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.AryInt;
import com.seaofnodes.simple.util.Utils;
import java.util.*;
@@ -152,25 +153,53 @@ public abstract class GlobalCodeMotion {
}
// ------------------------------------------------------------------------
+ // Scheduling is not recursive. Retain scratch capacity between objects,
+ // but release graph references and reset only the entries we used.
+ private static CFGNode[] LATE = new CFGNode[1];
+ // Load NIDs at their CFG block choices, checked against Store blocks.
+ private static int[] ANTI = new int[1];
+ private static final AryInt TOUCHED = new AryInt();
+ private static final Ary<Node> PLACED = new Ary<>(Node.class);
+ private static final Ary<Node> ANTIDEPS = new Ary<>(Node.class);
+ private static final WorkList<Node> WORK = new WorkList<>();
+
private static void schedLate( CodeGen code, StopNode stop ) {
- CFGNode[] late = new CFGNode[code.UID()];
- Node[] ns = new Node[code.UID()];
- // Record Load NIDs at all their CFG block choices, then check against
- // Store block choices to force a Load above an anti-dependent Store.
- int[] anti = new int[code.UID()];
- Ary<Node> antideps = new Ary<>(Node.class);
+ assert TOUCHED.isEmpty() && PLACED.isEmpty() && WORK.isEmpty();
+ if( code.UID() > LATE.length ) {
+ int len = Math.max(code.UID(),LATE.length<<1);
+ LATE = new CFGNode[len];
+ ANTI = new int[len];
+ }
+ WORK.clear();
// Breadth-first scheduling
- breadth(stop,ns,late,anti, antideps);
+ // Early scheduling has released the shared visit bits.
+ breadth(stop,PLACED,LATE,ANTI,ANTIDEPS,code.visit());
// Copy the best placement choice into the control slot
- for( int i=0; i<late.length; i++ )
- if( ns[i] != null && !(ns[i] instanceof ProjNode) )
- ns[i].setDef(0,late[i]);
+ for( Node n : PLACED )
+ if( !(n instanceof ProjNode) )
+ n.setDef(0,LATE[n._nid]);
+ for( int i=0; i<TOUCHED.len(); i++ )
+ LATE[TOUCHED.at(i)] = null;
+ TOUCHED.clear();
+ clear(PLACED);
+ clear(ANTIDEPS);
+ }
+
+ // Ary.clear retains references; static scratch must release them as well.
+ private static void clear(Ary<Node> nodes) {
+ Arrays.fill(nodes._es,0,nodes._len,null);
+ nodes.clear();
+ }
+
+ private static Ary<Node> antiDeps(MemOpNode load, Ary<Node> antideps) {
+ clear(antideps);
+ return load.antiDeps(antideps);
}
- private static void breadth(Node stop, Node[] ns, CFGNode[] late, int[] anti, Ary<Node> antideps) {
+ private static void breadth(Node stop, Ary<Node> placed, CFGNode[] late, int[] anti, Ary<Node> antideps, BitSet visit) {
// Things on the worklist have some (but perhaps not all) uses done.
- WorkList<Node> work = new WorkList<>();
+ WorkList<Node> work = WORK;
work.push(stop);
Node n;
outer:
@@ -189,21 +218,23 @@ public abstract class GlobalCodeMotion {
// Loads need their memory inputs' uses also done
if( n instanceof MemOpNode ld && ld._isLoad )
- for( Node memuse : ld.antiDeps(antideps) )
+ for( Node memuse : antiDeps(ld,antideps) )
if( late[memuse._nid]==null )
continue outer;
// All uses done, schedule
- _doSchedLate(n,ns,late,anti,antideps);
+ _doSchedLate(n,placed,late,anti,antideps);
}
+ TOUCHED.push(n._nid);
// A use just finished; reconsider its inputs and waiting loads,
// even when the shared memory input was already scheduled.
for( Node def : n._inputs ) {
if( def==null ) continue;
if( late[def._nid]==null ) work.push(def);
- wakeLoads(def,late,work,new BitSet());
+ wakeLoads(def,late,work,visit);
}
+ visit.clear();
if( n instanceof LoopNode loop )
for( Node phi : loop._outputs )
if( phi instanceof PhiNode && late[phi._nid]==null )
@@ -211,7 +242,7 @@ public abstract class GlobalCodeMotion {
}
}
- private static void _doSchedLate(Node n, Node[] ns, CFGNode[] late, int[] anti, Ary<Node> antideps) {
+ private static void _doSchedLate(Node n, Ary<Node> placed, CFGNode[] late, int[] anti, Ary<Node> antideps) {
// Walk uses, gathering the LCA (Least Common Ancestor) of uses
CFGNode early = n.in(0) instanceof CFGNode cfg ? cfg : n.in(0).cfg0();
assert early != null;
@@ -233,7 +264,7 @@ public abstract class GlobalCodeMotion {
if( better(lca,best) )
best = lca;
assert !(best instanceof IfNode);
- ns [n._nid] = n;
+ placed.push(n);
late[n._nid] = best;
}
@@ -276,10 +307,11 @@ public abstract class GlobalCodeMotion {
private static CFGNode find_anti_dep(CFGNode lca, MemOpNode load, CFGNode early, CFGNode[] late, int[] anti, Ary<Node> antideps) {
// We could skip final-field loads here.
// Walk LCA->early, flagging Load's block location choices
- for( CFGNode cfg=lca; cfg!=early.idom(); cfg = cfg.idom() )
+ CFGNode marked = lca;
+ for( CFGNode cfg = marked; cfg!=early.idom(); cfg = cfg.idom() )
anti[cfg._nid] = load._nid;
// Walk load->mem uses, looking for Stores causing an anti-dep
- for( Node mem : load.antiDeps(antideps) ) {
+ for( Node mem : antiDeps(load,antideps) ) {
if( mem instanceof PhiNode phi ) {
// Inputs carrying different memory might overwrite this alias.
// Raise the LCA for those paths; the unchanged-memory paths
@@ -295,6 +327,10 @@ public abstract class GlobalCodeMotion {
lca = anti_dep(load,late[mem._nid],mem.cfg0(),lca,mem,anti);
}
}
+ // This load's marks are no longer needed; leave scratch clean even
+ // when the next compilation reuses these node IDs.
+ for( CFGNode cfg = marked; cfg!=early.idom(); cfg = cfg.idom() )
+ anti[cfg._nid] = 0;
return lca;
}
diff --git a/chapter28b/src/main/java/com/seaofnodes/simple/codegen/IFG.java b/chapter28b/src/main/java/com/seaofnodes/simple/codegen/IFG.java
index bcdb14bf..d15e66da 100644
--- a/chapter28b/src/main/java/com/seaofnodes/simple/codegen/IFG.java
+++ b/chapter28b/src/main/java/com/seaofnodes/simple/codegen/IFG.java
@@ -172,6 +172,16 @@ abstract public class IFG {
// Check use-side for self-conflict; if so we MUST split
selfConflict(alloc,def,lrg1);
+ // All operands are live together, regardless of their input order.
+ TMP.put(lrg1,def);
+ }
+
+ // Check fixed-register uses after gathering every operand. An indirect
+ // call's target comes after its arguments, but must also avoid their
+ // fixed registers (and may need to move from its incoming register).
+ for( int i=1; i<n.nIns(); i++ ) {
+ Node def = n.in(i);
+ if( def==null || alloc.lrg(def)==null ) continue;
// Look for a must-use single register conflicting with some other must-def.
if( n instanceof MachNode m ) {
RegMask ni_mask = m.regmap(i);
@@ -190,10 +200,6 @@ abstract public class IFG {
}
}
}
-
- // All inputs live - except in self conflicts, where we keep the prior def alive
- // until it goes.
- TMP.put(lrg1,def);
}
}
diff --git a/chapter28b/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/chapter28b/src/main/java/com/seaofnodes/simple/node/CFGNode.java
index 2e8c3ff9..cb406e5b 100644
--- a/chapter28b/src/main/java/com/seaofnodes/simple/node/CFGNode.java
+++ b/chapter28b/src/main/java/com/seaofnodes/simple/node/CFGNode.java
@@ -203,7 +203,7 @@ public abstract class CFGNode extends Node {
_ltree = stop._ltree = CodeGen.CODE.XCTRL._ltree = new LoopTree((StartNode)this);
Ary<FunNode> work = new Ary<>(FunNode.class);
- BitSet post = new BitSet();
+ BitSet post = CodeGen.CODE.visit();
int pre = 1;
_pre = pre++;
for( FunNode fun : funs ) {
@@ -221,6 +221,7 @@ public abstract class CFGNode extends Node {
for( int i=0; i<work._len; i++ )
pre = work.at(i)._bltWalk(pre,work.at(i),stop, post, work);
+ post.clear();
}
int _bltWalk( int pre, FunNode fun, StopNode stop, BitSet post, Ary<FunNode> work ) {
diff --git a/chapter28b/src/test/java/com/seaofnodes/simple/PhaseTest.java b/chapter28b/src/test/java/com/seaofnodes/simple/PhaseTest.java
index ee262d6c..f2d09c7c 100644
--- a/chapter28b/src/test/java/com/seaofnodes/simple/PhaseTest.java
+++ b/chapter28b/src/test/java/com/seaofnodes/simple/PhaseTest.java
@@ -42,6 +42,29 @@ public class PhaseTest {
}
}
+ @Test public void testSchedulingScratchAcrossCompilations() {
+ String src = """
+ int[] !xs = new int[2];
+ xs[0] = arg;
+ int sum = 0;
+ for( int i=0; i<5; i++ ) {
+ int old = xs[arg&1];
+ xs[0] = i;
+ sum += old;
+ }
+ return sum;
+ """;
+ for( int round=0; round<3; round++ ) {
+ CodeGen code = new CodeGen(src).driver(Phase.TypeCheck);
+ // A small graph after many earlier node allocations, then fresh
+ // compilations which reuse its IDs and scheduling scratch.
+ if( round==0 ) code._uid += 100_000;
+ assertEquals("6",Eval2.eval(code,0));
+ assertEquals("0",Eval2.eval(code,1));
+ assertEquals("8",Eval2.eval(code,2));
+ }
+ }
+
@Test public void testExplicitSerializationAndMemoryExport() {
CodeGen code = new CodeGen(SRC).driver(Phase.Serialize);
assertNotNull(code._serial);
diff --git a/chapter28b/src/test/java/com/seaofnodes/simple/SelectiveLoadingTest.java b/chapter28b/src/test/java/com/seaofnodes/simple/SelectiveLoadingTest.java
index d56d5c89..fa14fc2c 100644
--- a/chapter28b/src/test/java/com/seaofnodes/simple/SelectiveLoadingTest.java
+++ b/chapter28b/src/test/java/com/seaofnodes/simple/SelectiveLoadingTest.java
@@ -134,7 +134,10 @@ public class SelectiveLoadingTest {
@Test public void testUnknownCallbackEncoding() {
for( String cpu : new String[]{"x86_64_v2","arm","riscv"} )
- new CodeGen("val apply = { {int->int} f,int n -> f(n); };")
+ new CodeGen("""
+ val apply = { {int->int} f,int n -> f(n); };
+ val apply_noInline = { {int->int} f,int n -> f(n); };
+ """)
.driver(CodeGen.Phase.Encoding,cpu,"SystemV");
}
diff --git a/docs/roadmap.md b/docs/roadmap.md
index ae450d82..88f35654 100644
--- a/docs/roadmap.md
+++ b/docs/roadmap.md
@@ -13,9 +13,10 @@ design decisions and chapter boundaries.
Remaining work:
-- Remove Eval2 scheduling scratch costs proportional to all previously
- allocated node IDs. Scheduling a newly loaded object should cost only for
- that graph and any affected existing nodes.
+- Review the remaining node-ID bitset costs in Eval2 scheduling. Scratch
+ arrays now retain capacity and reset only touched entries, but bitsets
+ still span previously allocated node IDs. Scheduling a newly loaded
+ object should cost only for that graph and any affected existing nodes.
- Reduce repeated scans of all calls and unloaded function summaries in
`StopCUNode.compute()`. Review its phase-dependent escape normalization and
reliance on the previous result before changing propagation.