summaryrefslogtreecommitdiff
path: root/chapter28b/src
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-10-28 09:30:15 -0700
committerCliff Click <cliffc@acm.org>2026-10-28 09:30:15 -0700
commit696adef058d43695ac1c47a7223d9e7a8b8ab6ea (patch)
tree956dc21e7c2bdb1386da7b5451dd4f252d829456 /chapter28b/src
parent76a7ee6eaf6963b044cb5d149c916c55ae4d24f7 (diff)
downloadSimple-696adef058d43695ac1c47a7223d9e7a8b8ab6ea.tar.gz
Simple-696adef058d43695ac1c47a7223d9e7a8b8ab6ea.zip
Another round of extra _phase changes removed
Also removes loading more code to resolve cyclic class dependencies, and instead rejects some of the more exciting cases
Diffstat (limited to 'chapter28b/src')
-rw-r--r--chapter28b/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java10
-rw-r--r--chapter28b/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java21
-rw-r--r--chapter28b/src/main/java/com/seaofnodes/simple/codegen/Serialize.java163
-rw-r--r--chapter28b/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java8
-rw-r--r--chapter28b/src/main/java/com/seaofnodes/simple/node/StopNode.java3
-rw-r--r--chapter28b/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java15
-rw-r--r--chapter28b/src/test/java/com/seaofnodes/simple/SelectiveLoadingTest.java30
7 files changed, 97 insertions, 153 deletions
diff --git a/chapter28b/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java b/chapter28b/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java
index d95f55f2..9a544685 100644
--- a/chapter28b/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java
+++ b/chapter28b/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java
@@ -150,16 +150,6 @@ public class ClassInitDependencies {
return null;
}
- // A condition-producing helper outside the SCC can eliminate a cycle.
- // Before reporting one, restore the full-IR precision of the original check.
- // Called only after check() has found a cycle.
- Ary<CompUnit> cycleImports() {
- Ary<CompUnit> units = new Ary<>(CompUnit.class);
- for( FunNode fun : _code._linker )
- if( fun!=null && fun.summary()!=null && units.find(fun._compunit)==-1 ) units.add(fun._compunit);
- return units;
- }
-
// Every internal edge of an SCC belongs to a cycle. Print all of them:
// stack order is not a path, and enumerating all cycle paths can explode.
// This bookkeeping and formatting are only needed on an error.
diff --git a/chapter28b/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java b/chapter28b/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
index ddcdba3c..e56068e7 100644
--- a/chapter28b/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
+++ b/chapter28b/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
@@ -523,28 +523,9 @@ public class CodeGen {
}
// TypeCheck saves its successful analysis for Serialize. Loading bodies
- // or entering another graph-changing phase discards it; no stale edges
- // survive a later Iter/FinalizeIR/TypeCheck sequence.
+ // or entering another graph-changing phase discards it.
ClassInitDependencies _classInitDependencies;
- ClassInitDependencies resolveInitCycles() {
- while( true ) {
- ClassInitDependencies deps = new ClassInitDependencies(this);
- String cycle = deps.check();
- if( cycle==null ) return deps;
- Ary<CompUnit> units = deps.cycleImports();
- if( units.isEmpty() ) throw Parser.error(cycle,null);
- for( CompUnit cu : units ) ParseAll.loadCompUnit(this,cu);
- Phase prior = _phase;
- try {
- _phase=Phase.IterPostOpto;
- _iter.iterate(this);
- _phase=Phase.FinalizeIR;
- FinalizeIR.run(this);
- } finally { _phase=prior; }
- }
- }
-
public CodeGen finalizeIR() { return phase(Phase.FinalizeIR, _phase==Phase.IterPostOpto, () -> FinalizeIR.run(this)); }
// Cross-phase `keep()`s belong here! We have a few nodes which we can
diff --git a/chapter28b/src/main/java/com/seaofnodes/simple/codegen/Serialize.java b/chapter28b/src/main/java/com/seaofnodes/simple/codegen/Serialize.java
index 3e4adf5f..32027cb5 100644
--- a/chapter28b/src/main/java/com/seaofnodes/simple/codegen/Serialize.java
+++ b/chapter28b/src/main/java/com/seaofnodes/simple/codegen/Serialize.java
@@ -450,113 +450,76 @@ public class Serialize {
String[] strs = tread.strs();
GlobalBits fileAliases = tread.fileAliases();
- CodeGen.Phase prior = code._phase;
- code._phase=CodeGen.Phase.Parse; // Constructors must tolerate an incomplete graph.
- try {
- // D - Read the nodes
-
- // Number of nodes
- int num = bais.packed4();
- // Packed array of nodes
- Ary<Node> nodes = new Ary<>(Node.class);
- CompUnit cu = null;
- for( int i = 0; i<num; i++ ) {
- Node.Tag tag = Node.Tag.VALS[bais.packed1()];
- Type t = types[bais.packed2()];
- Node n = tag.make(bais,strs,types,fileAliases,aliases);
- n._type = t;
- nodes.push(n);
- // A little extra for FunNodes
- if( n instanceof StartCUNode startcu ) {
- cu = code._compunits.get(startcu._fname==null ? (code._srcName==null ? "Test" : code._srcName) : startcu._fname);
- assert cu != null;
- }
- if( n instanceof StopCUNode stopcu ) {
- cu = null;
- }
- if( n instanceof FunNode fun ) {
- assert cu != null;
- fun._compunit = cu;
- if( fun._name==null || fun._name.isEmpty() ) fun._name=fidxs.symbol(fun.sig().fidx());
- }
- if( n instanceof ExternNode ext && ext._con instanceof TypeFunPtr tfp && tfp.isConstant() ) {
- String old = code.externFunc(tfp.fidx());
- if( old == null )
- code.externFunc(tfp.fidx(), ext._extern);
- else
- assert old.equals(ext._extern);
- }
+ // D - Read the nodes
+
+ // Number of nodes
+ int num = bais.packed4();
+ // Packed array of nodes
+ Ary<Node> nodes = new Ary<>(Node.class);
+ CompUnit cu = null;
+ for( int i = 0; i<num; i++ ) {
+ Node.Tag tag = Node.Tag.VALS[bais.packed1()];
+ Type t = types[bais.packed2()];
+ Node n = tag.make(bais,strs,types,fileAliases,aliases);
+ n._type = t;
+ nodes.push(n);
+ // A little extra for FunNodes
+ if( n instanceof StartCUNode startcu ) {
+ cu = code._compunits.get(startcu._fname==null ? (code._srcName==null ? "Test" : code._srcName) : startcu._fname);
+ assert cu != null;
+ }
+ if( n instanceof StopCUNode stopcu ) {
+ cu = null;
+ }
+ if( n instanceof FunNode fun ) {
+ assert cu != null;
+ fun._compunit = cu;
+ if( fun._name==null || fun._name.isEmpty() ) fun._name=fidxs.symbol(fun.sig().fidx());
+ }
+ if( n instanceof ExternNode ext && ext._con instanceof TypeFunPtr tfp && tfp.isConstant() ) {
+ String old = code.externFunc(tfp.fidx());
+ if( old == null )
+ code.externFunc(tfp.fidx(), ext._extern);
+ else
+ assert old.equals(ext._extern);
}
+ }
- // Node edges
- for( Node n : nodes ) {
- int len = n.nIns();
- for( int i = 0; i<len; i++ ) {
- int idx = bais.packed2();
- Node def = idx==0 ? null : nodes.at(idx-1);
- // MemMerge precise aliases are encoded by their input slot.
- // Remap populated file-local slots into this compilation's
- // local alias numbering; null holes carry no identity.
- int local = i;
- if( n instanceof MemMergeNode && i >= GlobalBits.RESERVED ) {
- if( def == null ) continue;
- local = aliases.map(fileAliases,i);
- }
- n.setDefX(local,def);
- // Call's output[0] is always the CallEnd
- if( n instanceof CallEndNode cend && i==0 ) {
- var outs = cend.call()._outputs;
- outs.swap(0,outs._len-1);
- }
+ // Node edges
+ for( Node n : nodes ) {
+ int len = n.nIns();
+ for( int i = 0; i<len; i++ ) {
+ int idx = bais.packed2();
+ Node def = idx==0 ? null : nodes.at(idx-1);
+ // MemMerge precise aliases are encoded by their input slot.
+ // Remap populated file-local slots into this compilation's
+ // local alias numbering; null holes carry no identity.
+ int local = i;
+ if( n instanceof MemMergeNode && i >= GlobalBits.RESERVED ) {
+ if( def == null ) continue;
+ local = aliases.map(fileAliases,i);
}
- // A little extra for FunNodes
- if( n instanceof FunNode fun ) {
- // Return is hooked like an edge
- ReturnNode ret = (ReturnNode)nodes.at(bais.packed2()-1);
- fun.setRet(ret);
- ret._fun = fun;
+ n.setDefX(local,def);
+ // Call's output[0] is always the CallEnd
+ if( n instanceof CallEndNode cend && i==0 ) {
+ var outs = cend.call()._outputs;
+ outs.swap(0,outs._len-1);
}
}
- // Add edge from Start to Stop
- StartNode start = (StartNode)nodes.at(0);
- StopNode stop = (StopNode)nodes.last();
- start.setDef(1,stop);
-
- // A body materialized after SCCP joins a different analysis context.
- // Its saved types are reset and checked by the ensuing fixed-point solve.
- assert prior!=CodeGen.Phase.Parse || check(code,nodes);
- elf._nodes = nodes;
- } finally { code._phase=prior; }
- }
-
- // Check loaded types and nodes are consistent
- private static boolean check( CodeGen code, Ary<Node> nodes ) {
- // Loaded nodes are complete, unlike nodes still being built by the
- // parser. Recompute them with post-parse semantics. The compilation-
- // unit Stop is the exception: its escape type depends on the complete
- // program's public interface, which ParseAll freezes only after every
- // compilation unit has been loaded.
- boolean good = true;
- CodeGen.Phase phase = code._phase;
- code._phase = CodeGen.Phase.Opto;
- try {
- for( Node n : nodes ) {
- if( n instanceof StopCUNode && !code.publicInterfaceFrozen() )
- continue;
- // Cross-object calls are relinked only after every body has
- // loaded. Their saved return types cannot be recomputed yet.
- if( n instanceof CallEndNode end && end.call().fptr()._type instanceof TypeFunPtr fp &&
- end.nIns()<=fp.nfcns() ) continue;
- Type nval = n.compute();
- if( nval != n._type ) {
- System.err.println("Node "+n+" read in as "+n._type+" but computes as "+nval);
- good = false;
- }
+ // A little extra for FunNodes
+ if( n instanceof FunNode fun ) {
+ // Return is hooked like an edge
+ ReturnNode ret = (ReturnNode)nodes.at(bais.packed2()-1);
+ fun.setRet(ret);
+ ret._fun = fun;
}
- } finally {
- code._phase = phase;
}
- return good;
+ // Add edge from Start to Stop
+ StartNode start = (StartNode)nodes.at(0);
+ StopNode stop = (StopNode)nodes.last();
+ start.setDef(1,stop);
+
+ elf._nodes = nodes;
}
// --------------------------------------------------
diff --git a/chapter28b/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java b/chapter28b/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java
index 78794096..a5eaa5ca 100644
--- a/chapter28b/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java
+++ b/chapter28b/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java
@@ -10,8 +10,12 @@ import com.seaofnodes.simple.util.Ary;
abstract class TypeCheck {
static void run(CodeGen code) {
// Check for class-init cycles first; such a cycle can prevent other
- // things from resolving types. Serialize can reuse the final graph.
- code._classInitDependencies = code.resolveInitCycles();
+ // things from resolving types. Use the graph and summaries left by
+ // optimization; do not load more bodies to disprove a possible cycle.
+ ClassInitDependencies deps = new ClassInitDependencies(code);
+ String cycle = deps.check();
+ if( cycle!=null ) throw Parser.error(cycle,null);
+ code._classInitDependencies = deps; // Serialize reuses this checked graph.
Ary<Node> errs = new Ary<>(Node.class);
code._stop.walk( n -> {
diff --git a/chapter28b/src/main/java/com/seaofnodes/simple/node/StopNode.java b/chapter28b/src/main/java/com/seaofnodes/simple/node/StopNode.java
index a7e52563..6d453324 100644
--- a/chapter28b/src/main/java/com/seaofnodes/simple/node/StopNode.java
+++ b/chapter28b/src/main/java/com/seaofnodes/simple/node/StopNode.java
@@ -15,7 +15,8 @@ public class StopNode extends CFGNode {
public StopNode() {
super();
- _type = compute();
+ // Edges may still be missing, including when loading a StopCU.
+ _type = TypeTuple.STATE;
}
public StopNode(StopNode stop) { super(stop); }
@Override public Tag serialTag() { return Tag.Stop; }
diff --git a/chapter28b/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java b/chapter28b/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java
index 08648187..38b4d8a2 100644
--- a/chapter28b/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java
+++ b/chapter28b/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java
@@ -248,25 +248,25 @@ public class ClassInitDependenciesTest {
Path dir=tempDir("import-"), stubs=tempDir("import-");
object(stubs,stubs,"A","int value=arg;");
object(stubs,stubs,"B","int value=arg;");
- // Each object is independently acyclic. Combining their real bodies
+ // Each object is independently acyclic. Combining their summaries
// must recheck the graph rather than trust either object's local order.
String asrc="int value=arg;\nvalue=B.value;";
CodeGen acode=module(dir,stubs,"A",Map.of("A",asrc));
String afile=acode._compunits.get("A").sourceFile();
module(dir,stubs,"B",Map.of("B","int value=arg; value=A.value;"));
for( String entry : new String[]{"A","B"} ) {
+ CodeGen code = new CodeGen(null,dir.toString(),new Ary<>(new String[]{dir.toString()}),
+ "Main","return "+entry+".value;",123,true,TypeInteger.BOT);
try {
- new CodeGen(null,dir.toString(),new Ary<>(new String[]{dir.toString()}),
- "Main","return "+entry+".value;",123,true,TypeInteger.BOT).driver(CodeGen.Phase.TypeCheck);
+ code.driver(CodeGen.Phase.TypeCheck);
fail("Expected imported initialization cycle");
} catch( Parser.ParseException ex ) {
assertCycle(ex.getMessage(),"A -> B -> A");
- assertTrue(ex.getMessage(),ex.getMessage().contains("value=B.value;\n -------^"));
- assertTrue(ex.getMessage(),ex.getMessage().contains("\n"+afile+":2:8: error: cyclic class initialization\n"));
+ assertNull(code.getElf(code._compunits.get("A")._obj)._nodes);
+ assertNull(code.getElf(code._compunits.get("B")._obj)._nodes);
}
}
- // The imported object must still report its saved path and offset
- // when the original source is no longer available.
+ // The summary reports the dependency chain even without source files.
Files.delete(Path.of(afile));
try {
new CodeGen(null,dir.toString(),new Ary<>(new String[]{dir.toString()}),
@@ -274,7 +274,6 @@ public class ClassInitDependenciesTest {
fail("Expected imported initialization cycle");
} catch( Parser.ParseException ex ) {
assertCycle(ex.getMessage(),"A -> B -> A");
- assertTrue(ex.getMessage(),ex.getMessage().contains("\n"+afile+": error: cyclic class initialization (source unavailable at byte offset "+asrc.indexOf('.')+")"));
}
}
diff --git a/chapter28b/src/test/java/com/seaofnodes/simple/SelectiveLoadingTest.java b/chapter28b/src/test/java/com/seaofnodes/simple/SelectiveLoadingTest.java
index d8fad3d5..d56d5c89 100644
--- a/chapter28b/src/test/java/com/seaofnodes/simple/SelectiveLoadingTest.java
+++ b/chapter28b/src/test/java/com/seaofnodes/simple/SelectiveLoadingTest.java
@@ -160,29 +160,35 @@ public class SelectiveLoadingTest {
}
}
- @Test public void testCycleRefinementLoadsConditionHelper() throws Exception {
+ @Test public void testPossibleCycleDoesNotLoadConditionHelper() throws Exception {
Path base = Path.of("build/objs/selective");
Files.createDirectories(base);
Path root = Files.createTempDirectory(base,"cycle-");
- Path stubs = Files.createDirectory(root.resolve("stubs"));
+ Path src = Files.createDirectory(root.resolve("src"));
Path lib = Files.createDirectory(root.resolve("lib"));
- object(stubs,null,"A","int value=arg;");
- object(stubs,null,"B","int value=arg;");
StringBuilder helper = new StringBuilder("val test={bool n -> ");
// Large parse, small final body: it will not request speculative loading.
for( int i = 0; i<80; i++ ) helper.append("val x").append(i).append("=n+0;");
helper.append("return n;};");
object(lib,null,"Predicate",helper.toString());
- Ary<String> inputs = new Ary<>(new String[]{stubs.toString(),lib.toString()});
- object(lib,inputs,"A","int value=1; if(Predicate.test(false)) value=B.value;");
- object(lib,inputs,"B","int value=A.value;");
- CodeGen code = new CodeGen(null,root.toString(),new Ary<>(new String[]{lib.toString()}),
- "Main","return A.value;",123,true,TypeInteger.BOT).driver(CodeGen.Phase.FinalizeIR);
+ // All sources and objects agree. The summary cannot express test(n)==n.
+ write(src,"Root","val entry=A;");
+ write(src,"Root/A","int value=1; if(Predicate.test(false)) value=B.value;");
+ write(src,"Root/B","int value=A.value;");
+ CodeGen code = new CodeGen(src.toString(),root.toString(),new Ary<>(new String[]{lib.toString()}),
+ "Root",null,123,true,TypeInteger.BOT).driver(CodeGen.Phase.FinalizeIR);
assertNotNull(function(code,"test").summary());
assertNotNull("Summaries alone report a potential cycle",new ClassInitDependencies(code).check());
- code.driver(CodeGen.Phase.TypeCheck);
- assertNull("The helper is outside A/B's apparent cycle",function(code,"test").summary());
- assertEquals("1",Eval2.eval(code,0));
+ try {
+ code.driver(CodeGen.Phase.TypeCheck);
+ fail("Reject the possible cycle without loading the condition helper");
+ } catch( Parser.ParseException ex ) {
+ assertTrue(ex.getMessage(),ex.getMessage().startsWith("Cyclic class initialization:"));
+ assertTrue(ex.getMessage(),ex.getMessage().contains("Root.A -> Root.B"));
+ assertTrue(ex.getMessage(),ex.getMessage().contains("Root.B -> Root.A"));
+ }
+ assertNotNull(function(code,"test").summary());
+ assertNull(code.getElf(code._compunits.get("Predicate")._obj)._nodes);
}
private static void object(Path dir, Ary<String> paths, String name, String source) {