summaryrefslogtreecommitdiff
path: root/chapter25/src
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-08-10 15:21:17 -0700
committerCliff Click <cliffc@acm.org>2026-08-10 15:21:17 -0700
commit628452bb9f6137d433395fae5a9f5d780fb0f1e1 (patch)
tree6fbf9916aa0b1bed55fd14a0613445048d89eb9a /chapter25/src
parent43dd950c4e3c9e17b619af56dd34394e0a4779b6 (diff)
downloadSimple-628452bb9f6137d433395fae5a9f5d780fb0f1e1.tar.gz
Simple-628452bb9f6137d433395fae5a9f5d780fb0f1e1.zip
More hardening
Allow setting random seeds to not fail tests due to e.g. spill-counts or exact graph shapes. A few missing addDeps or stalling-for-constants fixed.
Diffstat (limited to 'chapter25/src')
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/IterPeeps.java5
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java8
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java23
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java14
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/Node.java4
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ReturnNode.java9
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter05Test.java5
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter18Test.java4
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter20Test.java2
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java2
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter22Test.java2
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter23Test.java2
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter24Test.java2
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/TestArm64.java2
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/TestC.java2
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/TestRisc5.java2
16 files changed, 60 insertions, 28 deletions
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter25/src/main/java/com/seaofnodes/simple/IterPeeps.java
index 608f368d..9fac7a99 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/IterPeeps.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/IterPeeps.java
@@ -81,11 +81,14 @@ public class IterPeeps {
// Everybody gets a free "go again" in case they didn't get
// made in their final form.
_work.push(x);
+ // A self-returning peephole can have rewritten its input
+ // edges. The new defs gained a user and may have
+ // backwards, user-sensitive peepholes of their own.
+ for( Node z : x._inputs ) _work.push(z);
// If the result is not self, revisit all inputs (because
// there's a new user), and replace in the graph.
if( x != n ) {
for( Node z : n. _inputs ) _work.push(z);
- for( Node z : x. _inputs ) _work.push(z);
for( Node z : x._outputs ) _work.push(z);
n.subsume(x);
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
index 85407ddc..85d8143b 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
@@ -42,6 +42,9 @@ public class CodeGen {
}
public Phase _phase;
+ /** True when tests deliberately randomize Iter worklist order. */
+ public static boolean iterSeedOverridden() { return System.getProperty("simple.iter.seed") != null; }
+
// ---------------------------
// Module Source Root
public final String _modDir;
@@ -196,7 +199,10 @@ public class CodeGen {
_phase = null;
// Start GVN table
_gvn = new HashMap<>();
- _iter = new IterPeeps(workListSeed);
+ // Allow whole-suite worklist-order sweeps without rewriting the many
+ // explicit seeds used by tests and command-line compilation.
+ String iterSeed = System.getProperty("simple.iter.seed");
+ _iter = new IterPeeps(iterSeed == null ? workListSeed : Long.parseLong(iterSeed));
// End points of graph
_stop = new StopNode().init();
_start = new StartNode(null,_stop,arg).init();
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
index c4a13375..cab85e44 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
@@ -140,6 +140,7 @@ public class BulkMemPhiNode extends PhiNode {
return true;
}
+
// True if the MemMerge has no precise slice still covered by this Phi.
private boolean canPeek(MemMergeNode mmm) {
return missingAlias(mmm,false)==0;
@@ -165,7 +166,11 @@ public class BulkMemPhiNode extends PhiNode {
// Return an alias required by a user but still covered by this Phi, or 0.
private int outputAlias(Node use) {
- use = addDep(use); // User alias sharpening changes this decision
+ // This decision depends backwards on an immediate user's alias/set.
+ // Normal graph-neighbor enqueueing flows from defs to uses, so retain
+ // an explicit forward dependency to revisit this Phi when the user
+ // sharpens.
+ use = addDepForwards(use);
return switch(use) {
case ScopeNode scope -> 0;
case ParmNode parm -> 0;
@@ -217,6 +222,11 @@ public class BulkMemPhiNode extends PhiNode {
for( int i=0; i<nIns(); i++ ) mphi.addDef(in(i));
Node mem = mphi.peephole();
assert ((TypeMem)mem._type)._alias==alias;
+ // The precise Phi is a new alias-sensitive user of every memory
+ // input. Revisit those defs: a predecessor BulkMemPhi may now need
+ // to split this alias even though none of its inputs or types changed.
+ for( int i=1; i<mphi.nIns(); i++ )
+ CodeGen.CODE.add(mphi.in(i));
BitSet aliases = ((BitSet)_aliases.clone());
aliases.set(alias);
@@ -271,9 +281,20 @@ public class BulkMemPhiNode extends PhiNode {
if( found==null ) {
MemPhiNode mphi = new MemPhiNode("$"+alias,alias);
mphi.addDef(region());
+ // Publishing the Phi on the Region makes it visible to recursive
+ // precisePhi lookups before all loop inputs have been installed.
+ // Give that recursion a valid lattice value until peephole computes
+ // the completed precise-memory Phi.
+ mphi.setType(TypeMem.BOT.makeFrom(alias));
for( int i=1; i<nIns(); i++ )
mphi.addDef(preciseInput(in(i),alias));
found = mphi.peephole();
+ // A newly reconstructed precise Phi also creates new users of
+ // its memory inputs. In particular, a BulkMemPhi input may now
+ // need to split this alias, a backwards (user-to-def) change not
+ // covered by processing the new Phi itself.
+ for( int i=1; i<mphi.nIns(); i++ )
+ CodeGen.CODE.add(mphi.in(i));
CodeGen.CODE.add(found);
}
return found;
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java
index 8b8bcaac..f67f2b97 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java
@@ -317,12 +317,14 @@ public class LoadNode extends MemOpNode {
int shift = Long.numberOfLeadingZeros(ti._max)-1;
Node shf = con(shift);
if( shf._type==TypeInteger.ZERO ) {
- // Val can be stale and on worklist. Preserve momnotoncity and bring it up to date
- if( val._type.isa(_type) )
- return val;
- Node val2 = val.peephole();
- assert val._type.isa(_type);
- return val2;
+ // A freshly exposed Store value can still be globally unknown.
+ // Revisit this Load when it settles instead of replacing a typed
+ // Load with the weaker transient value.
+ if( !val._type.isa(_type) ) {
+ addDep(val);
+ return null;
+ }
+ return val;
}
Node shl = new ShlNode(null,val,shf.keep()).peephole();
return new SarNode(null,shl,shf.unkeep());
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter25/src/main/java/com/seaofnodes/simple/node/Node.java
index 986754f4..1ce8382a 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/Node.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/Node.java
@@ -501,6 +501,10 @@ public abstract class Node implements Cloneable {
// A high Load through a bad pointer still owns the source error;
// do not replace it with an unlocated constant before typeCheck.
!(this instanceof LoadNode && err()!=null) &&
+ // A CallEnd can be temporarily high while optimistic call-graph
+ // linking is still discovering its target Returns. Its result
+ // projections must remain attached so they can sharpen later.
+ !(this instanceof ProjNode proj && proj.in(0) instanceof CallEndNode && _type.isHigh()) &&
_type.isHighOrConst() )
return ConstantNode.make(_type).peephole();
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
index 93cf1b91..b60d27d7 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
@@ -60,15 +60,6 @@ public class ReturnNode extends CFGNode {
if( ret != fcn.ret() && ret.isa(fcn.ret()) )
_fun.setSig(fcn.makeFrom(ret));
- // If dead (cannot be reached; infinite loop), kill the exit values
- if( ctrl()._type==Type.XCONTROL &&
- !(mem() instanceof ConstantNode && expr() instanceof ConstantNode) ) {
- Node top = ConstantNode.make(Type.TOP).peephole();
- setDef(1,top);
- setDef(2,top);
- return this;
- }
-
return null;
}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter05Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter05Test.java
index 51ff20ba..6ac08fda 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter05Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter05Test.java
@@ -116,7 +116,10 @@ if( arg==0 )
return arg+a+b;
""");
code.parse().opto();
- assertEquals("return ((arg+Phi(Region,1,0))+Phi(Region,2,0));", code.print());
+ String ir = code.print();
+ assertTrue(ir,
+ ir.equals("return ((arg+Phi(Region,1,0))+Phi(Region,2,0));") ||
+ ir.equals("return (Phi(Region,((!=0)arg+1),0)+Phi(Region,2,0));"));
}
@Test
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter18Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter18Test.java
index de332d03..b65e101b 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter18Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter18Test.java
@@ -145,7 +145,9 @@ return fcn(3);
public void testFcn5() {
CodeGen code = new CodeGen("val _fact = { int x -> x <= 1 ? 1 : x*_fact(x-1); }; return _fact(arg);");
code.parse().opto().typeCheck();
- assertEquals("Stop[ return Phi(Region,1,(arg*#2)); return Phi(Region,1,(Parm_x(_fact,(arg-1),(x-1))*#2)); ]", code.print());
+ String ir = code.print();
+ assertTrue(ir.equals("Stop[ return Phi(Region,1,(arg*#2)); return Phi(Region,1,(Parm_x(_fact,(arg-1),(x-1))*#2)); ]") ||
+ ir.equals("Stop[ return Phi(Region,1,(arg*Phi(Region,1,((arg-1)*#2)))); return Phi(Region,1,(Parm_x(_fact,(Sub-1),(x-1))*#2)); ]"));
assertEquals( "1", Eval2.eval(code, 0));
assertEquals( "1", Eval2.eval(code, 1));
assertEquals( "2", Eval2.eval(code, 2));
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter20Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter20Test.java
index e4754213..17b0fd0a 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter20Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter20Test.java
@@ -19,7 +19,7 @@ public class Chapter20Test {
code.driver(CodeGen.Phase.RegAlloc,cpu,os);
int delta = spills>>3;
if( delta==0 ) delta = 1;
- if( spills != -1 )
+ if( spills != -1 && !CodeGen.iterSeedOverridden() )
assertEquals("Expect spills:",spills,code._regAlloc._spillScaled,delta);
if( stop != null )
assertEquals(stop, code.print());
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java
index a4621165..e4d49e7f 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java
@@ -40,7 +40,7 @@ return new _s0.v1;
CodeGen code = new CodeGen(src).driver(CodeGen.Phase.Encoding,cpu,os);
int delta = spills>>3;
if( delta==0 ) delta = 1;
- if( spills != -1 )
+ if( spills != -1 && !CodeGen.iterSeedOverridden() )
assertEquals("Expect spills:",spills,code._regAlloc._spillScaled,delta);
if( stop != null )
assertEquals(stop, code.print());
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter22Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter22Test.java
index 5a171659..91b8310d 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter22Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter22Test.java
@@ -40,7 +40,7 @@ return new s0.v1;
CodeGen code = new CodeGen(src).driver(CodeGen.Phase.Encoding,cpu,os);
int delta = spills>>3;
if( delta==0 ) delta = 1;
- if( spills != -1 )
+ if( spills != -1 && !CodeGen.iterSeedOverridden() )
assertEquals("Expect spills:",spills,code._regAlloc._spillScaled,delta);
if( stop != null )
assertEquals(stop, code.print());
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter23Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter23Test.java
index 7e64f226..08acfd32 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter23Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter23Test.java
@@ -25,7 +25,7 @@ public class Chapter23Test {
CodeGen code = new CodeGen(src).driver(CodeGen.Phase.Encoding,cpu,os);
int delta = spills>>3;
if( delta==0 ) delta = 1;
- if( spills != -1 )
+ if( spills != -1 && !CodeGen.iterSeedOverridden() )
assertEquals("Expect spills:",spills,code._regAlloc._spillScaled,delta);
if( stop != null )
assertEquals(stop, code.print());
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter24Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter24Test.java
index 0f2e1bed..fe8aaf2e 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter24Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter24Test.java
@@ -20,7 +20,7 @@ public class Chapter24Test {
CodeGen code = new CodeGen(src).driver(CodeGen.Phase.Encoding,cpu,os);
int delta = spills>>3;
if( delta==0 ) delta = 1;
- if( spills != -1 )
+ if( spills != -1 && !CodeGen.iterSeedOverridden() )
assertEquals("Expect spills:",spills,code._regAlloc._spillScaled,delta);
if( stop != null )
assertEquals(stop, code.print());
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/TestArm64.java b/chapter25/src/test/java/com/seaofnodes/simple/TestArm64.java
index 7394b578..14864d64 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/TestArm64.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/TestArm64.java
@@ -19,7 +19,7 @@ public class TestArm64 {
// Allocation quality not degraded
int delta = spills>>3;
if( delta == 0 ) delta =1;
- if( spills != -1 ) assertEquals("Expect spills:", spills, code._regAlloc._spillScaled, delta);
+ if( spills != -1 && !CodeGen.iterSeedOverridden() ) assertEquals("Expect spills:", spills, code._regAlloc._spillScaled, delta);
// Image
byte[] image = new byte[1<<20]; // A megabyte (1024*1024 bytes)
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/TestC.java b/chapter25/src/test/java/com/seaofnodes/simple/TestC.java
index 5c49d3a8..7177f765 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/TestC.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/TestC.java
@@ -99,7 +99,7 @@ public abstract class TestC {
// Allocation quality not degraded
int delta = spills>>3;
if( delta==0 ) delta = 1;
- if( spills != -1 )
+ if( spills != -1 && !CodeGen.iterSeedOverridden() )
assertEquals("Expect spills:",spills,code._regAlloc._spillScaled,delta);
}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/TestRisc5.java b/chapter25/src/test/java/com/seaofnodes/simple/TestRisc5.java
index aff95c38..658909ca 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/TestRisc5.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/TestRisc5.java
@@ -19,7 +19,7 @@ public abstract class TestRisc5 {
// Allocation quality not degraded
int delta = spills>>3;
if( delta==0 ) delta = 1;
- if( spills != -1 )
+ if( spills != -1 && !CodeGen.iterSeedOverridden() )
assertEquals("Expect spills:",spills,code._regAlloc._spillScaled,delta);
// Image