summaryrefslogtreecommitdiff
path: root/chapter25/src/main
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-10-17 17:38:12 -0700
committerCliff Click <cliffc@acm.org>2026-10-17 17:38:12 -0700
commit0be52f8dd82f70d4c557fe916d47dc425401f848 (patch)
treeff2f2cc82744baed33e85971361a14abcdd2aa22 /chapter25/src/main
parent9b9fadcfa74b087a695156573ea28b89a166b18b (diff)
downloadSimple-0be52f8dd82f70d4c557fe916d47dc425401f848.tar.gz
Simple-0be52f8dd82f70d4c557fe916d47dc425401f848.zip
Phase cleanup heading into CLInit expansion
Diffstat (limited to 'chapter25/src/main')
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/Parser.java34
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java87
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java4
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/Opto.java57
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ArithNode.java3
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/CFGNode.java1
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/CLInitNode.java76
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/CProjNode.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ConstantNode.java4
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ConvertNode.java4
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java6
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/MemOpNode.java6
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java5
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/MultiNode.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ProjNode.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/StopCUNode.java6
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java1
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypeStruct.java2
25 files changed, 208 insertions, 108 deletions
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/Parser.java b/chapter25/src/main/java/com/seaofnodes/simple/Parser.java
index f0c4567f..eca2b9ce 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/Parser.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/Parser.java
@@ -290,6 +290,19 @@ public class Parser {
_scope.define(ids[i], t, (i==0 && ids[0]=="self") || (finals!=null && finals.get(i)), new ParmNode(ids[i],i+2,t,fun,ConstantNode.seed(t).peephole()).peephole(), loc);
}
+ // The initializer owns its run-once flag store. Parent initialization
+ // is a structural requirement, to be executed before this body when
+ // CLInit runtime lowering is added.
+ if( fun.isClz() ) {
+ Node self = fun.parm(2);
+ Field flag = ((TypeMemPtr)self._type)._obj.field("$init");
+ Node prior = mem().keep();
+ Node st = peep(new StoreNode(loc,"$init",flag._alias,flag._t,ctrl(),prior,self,
+ fldoff(self,"$init"),con(1),false));
+ storeMem(st,flag._alias,prior);
+ prior.unkeep();
+ }
+
// Parse the function body.
Node last=_code.ZERO; // Last statement as the default result
@@ -1236,9 +1249,12 @@ public class Parser {
? Field.make(initName,sig,_code.alias(_ref._cname),true)
: oldFld.makeFrom(sig);
Field ctorFld = ts==null ? null : ts.field("<ctor>");
+ // A same-named struct shares its file's class object and flag.
+ Field flag = ts==null ? null : ts.field("$init");
ts = ctorFld==null
? TypeStruct.make(clzName,false,fld)
: TypeStruct.make(clzName,false,fld,ctorFld);
+ if( flag!=null ) ts=ts.add(flag);
TypeFunPtr alloc = makeAllocator((TypeStruct)TYPES.get(fullName),ret,ctorFld);
ts = ts.add(Field.make("<new>",alloc,_code.alias(_ref._cname),true));
TYPES.put(clzName,ts);
@@ -2315,7 +2331,7 @@ public class Parser {
Kind.Func func = (Kind.Func)_scope._kinds.at(_scope.enclosingFuncOrDecl());
if( (ts==null || ts==TypeStruct.BOT || ts.isHigh() || startsClzPrefix(ts._name)) &&
!(ts!=null && (ts._name+".<clinit>").equals(func._name)) )
- ctrl(new CLInitNode(site,ctrl(),expr).peephole());
+ classInit(site,expr);
// With no field, we will update the bulk memory
int alias = fld==null ? 1 : fld._alias;
@@ -2578,6 +2594,22 @@ public class Parser {
private ConstantNode parseLiteral() { return con(_lexer.parseNumber()); }
ConstantNode con( long con ) { return _code.con(con); }
ConstantNode con( Type t ) { return _code.con(t ); }
+ private void classInit(Lexer site, Node ptr) {
+ // Ordinary load machinery resolves the flag alias and offset when
+ // the receiver is still a forward reference.
+ Field flag = ptr._type instanceof TypeMemPtr tmp ? tmp._obj.field("$init") : null;
+ int alias = flag==null ? 1 : flag._alias;
+ Node ready = new LoadNode(site,"$init",alias,null,mem(),ptr,fldoff(ptr,"$init")).peephole();
+ CLInitNode init = new CLInitNode(site,ctrl(),mem(),ptr,ready).peephole().keep();
+ ctrl(new CProjNode(init,0,ScopeNode.CTRL).peephole());
+ Node bulk = new ProjNode(init,1,ScopeNode.MEM0).peephole().keep();
+ flag = init.flag();
+ mem(flag==null ? bulk : mergeAlias(bulk,flag._alias,
+ new ProjNode(init,2,memName(flag._alias)).peephole()));
+ bulk.unkeep();
+ init.unkeep();
+ }
+
// Field offset for a known type (but unknown offset)
public static ConFldOffNode off( TypeStruct ts, String fname ) {
return (ConFldOffNode)(new ConFldOffNode(ts,fname).peephole());
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java
index df3be2d0..6c70b71d 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java
@@ -34,7 +34,7 @@ public class ClassInitDependencies {
// Build once; tests can inspect the graph independently of cycle checking.
public ClassInitDependencies( CodeGen code ) {
_code = code;
- assert code._phase.ordinal() >= CodeGen.Phase.Opto.ordinal();
+ assert code._phase.ordinal() >= CodeGen.Phase.IterPostOpto.ordinal();
_pres = new int[code._linker._len];
_lows = new int[code._linker._len];
_stk = new int[code._linker._len];
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 ff0be213..3909f881 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
@@ -28,7 +28,9 @@ public class CodeGen {
public enum Phase {
Parse, // Parse ASCII text into Sea-of-Nodes IR
Iter, // Pessimistic peepholes after parse
- Opto, // Run ideal optimizations
+ Opto, // SCCP, numeric modes, seed the peephole worklist
+ IterPostOpto, // Peepholes and inlining using SCCP results
+ FinalizeIR, // Field layout, offset cleanup, function/CU roots
TypeCheck, // Last check for bad programs
LoopTree, // Build a loop tree; break infinite loops
Serialize, // Serialize public IR for future compiles to link
@@ -97,8 +99,7 @@ public class CodeGen {
return null;
}
- // True when fidx is published through a public field of a public class.
- // TypeStruct.fidxs() already excludes private (underscore) fields.
+ // True when fidx is published through a public class or instance field.
public boolean publicFIDX(int fidx) {
if( fidx < GlobalBits.RESERVED ) return false;
// A public class initializer is itself an external root; unlike
@@ -106,19 +107,21 @@ public class CodeGen {
FunNode fun = _linker.atX(fidx);
if( fun != null && fun.isClz() && fun.isPublic() )
return true;
- // Constructors and methods live on their class objects, including
- // nested classes which do not have their own CompUnit.
+ // Include instance methods even while their constant function fields
+ // are stored in each instance rather than on the class object.
for( Type t : Parser.TYPES.values() )
if( t instanceof TypeStruct clz &&
Parser.startsClzPrefix(clz._name) &&
Parser.isPublicClassName(clz._name) &&
- resolvedClass(clz) &&
- publicClassFIDX(clz,fidx) )
- return true;
+ resolvedClass(clz) ) {
+ if( publicFieldFIDX(clz,fidx) ) return true;
+ Type self = Parser.TYPES.get(clz._name.substring(Parser.CLZ.length()));
+ if( self instanceof TypeStruct ts && publicFieldFIDX(ts,fidx) ) return true;
+ }
return false;
}
- private static boolean publicClassFIDX(TypeStruct clz, int fidx) {
+ private static boolean publicFieldFIDX(TypeStruct clz, int fidx) {
boolean hasCtor = clz.field("<ctor>") != null;
for( Field fld : clz._fields )
if( fld._fname.charAt(0)!='_' && fld._t instanceof TypeFunPtr tfp &&
@@ -182,7 +185,7 @@ public class CodeGen {
public final XCtrlNode XCTRL;
public Node constant( Type t ) {
- assert _phase==null || _phase.ordinal() <= Phase.Opto.ordinal();
+ assert _phase==null || _phase.ordinal() <= Phase.FinalizeIR.ordinal();
return ConstantNode.make(t).peephole();
}
public ConstantNode con( Type t ) { return (ConstantNode)constant(t); }
@@ -259,6 +262,8 @@ public class CodeGen {
if( p2 < p1 && p2 < Phase.Parse .ordinal() ) { parse(); p2 = dump(dump); }
if( p2 < p1 && p2 < Phase.Iter .ordinal() ) { iter(); p2 = dump(dump); }
if( p2 < p1 && p2 < Phase.Opto .ordinal() ) { opto(); p2 = dump(dump); }
+ if( p2 < p1 && p2 < Phase.IterPostOpto.ordinal() ) { iterPostOpto(); p2 = dump(dump); }
+ if( p2 < p1 && p2 < Phase.FinalizeIR.ordinal() ) { finalizeIR(); p2 = dump(dump); }
if( p2 < p1 && p2 < Phase.TypeCheck .ordinal() ) { typeCheck(); p2 = dump(dump); }
if( p2 < p1 && p2 < Phase.LoopTree .ordinal() ) { loopTree(); p2 = dump(dump); }
if( p2 < p1 && p1 >= Phase.Encoding .ordinal() ) { unlinkImports(); p2 = dump(dump); }
@@ -520,10 +525,8 @@ public class CodeGen {
}
- // Run ideal optimizations
+ // SCCP and numeric-mode resolution; leave cleanup work queued for Iter.
public CodeGen opto() {
- if( _phase == Phase.Parse )
- iter();
assert _phase == Phase.Iter;
_phase = Phase.Opto;
long t0 = System.currentTimeMillis();
@@ -534,6 +537,60 @@ public class CodeGen {
if( _obs != null ) _obs.phase(_phase.name());
return this;
}
+
+ public CodeGen iterPostOpto() {
+ assert _phase == Phase.Opto;
+ _phase = Phase.IterPostOpto;
+ long t0 = System.currentTimeMillis();
+ // Use SCCP types and the sharp call graph for peepholes and inlining.
+ _iter.iterate(this);
+ _times[Phase.IterPostOpto.ordinal()] = System.currentTimeMillis() - t0;
+ if( _obs != null ) _obs.phase(_phase.name());
+ return this;
+ }
+
+ public CodeGen finalizeIR() {
+ assert _phase == Phase.IterPostOpto;
+ _phase = Phase.FinalizeIR;
+ long t0 = System.currentTimeMillis();
+
+ // Freeze field sizes and replace symbolic field offsets by constants.
+ for( int i=0; i<_start.nOuts(); i++ ) {
+ Node use = _start.out(i);
+ if( use instanceof ConFldOffNode off ) {
+ Node con = off.asOffset();
+ assert con != null : off._ts._name+"."+off._fname;
+ off.subsume(con);
+ i--;
+ }
+ }
+ _iter.iterate(this); // Propagate field offsets and clean up.
+
+ // Retain surviving functions for emission without adding unknown callers.
+ for( FunNode fun : _linker )
+ if( fun != null && !fun.isDead() && !fun._type.isHigh() )
+ keepInCompUnit(fun);
+ for( CompUnit cu : _compunits.values() ) {
+ ProjNode mem = cu._start.proj(1);
+ if( mem != null ) mem.unkeep();
+ }
+ // Stable print order: initializers first, then other Returns by NID.
+ for( Node stop : _stop._inputs )
+ Arrays.sort(stop._inputs._es,0,stop.nIns(),
+ (x,y) -> ((((ReturnNode)x).fun().isClz() ? -1 : x._nid) -
+ (((ReturnNode)y).fun().isClz() ? -1 : y._nid) ));
+
+ _times[Phase.FinalizeIR.ordinal()] = System.currentTimeMillis() - t0;
+ if( _obs != null ) _obs.phase(_phase.name());
+ return this;
+ }
+
+ // Root a surviving function without claiming that it has unknown callers.
+ void keepInCompUnit(FunNode fun) {
+ StopCUNode stop = fun._compunit._stop;
+ if( stop._inputs.find(fun.ret()) == -1 )
+ add(stop).addDef(fun.ret());
+ }
public <N extends Node> N add( N n ) { return _iter.add(n); }
public void addAll( Ary<Node> ary ) { _iter.addAll(ary); }
public void addAll( Node n ) { _iter.add(n); _iter.addAll(n._inputs); }
@@ -541,9 +598,7 @@ public class CodeGen {
// ---------------------------
// Last check for bad programs
public CodeGen typeCheck() {
- // Demand phase Opto for cleaning up dead control flow at least,
- // required for the following GCM.
- assert _phase.ordinal() <= Phase.Opto.ordinal();
+ assert _phase == Phase.FinalizeIR;
_phase = Phase.TypeCheck;
long t0 = System.currentTimeMillis();
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 8eebc15b..33d971e9 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
@@ -276,6 +276,10 @@ public abstract class GlobalCodeMotion {
assert late[call._nid]!=null;
lca = anti_dep(load,late[call._nid],call.cfg0(),lca,call,anti);
break;
+ case CLInitNode init:
+ assert late[init._nid]!=null;
+ lca = anti_dep(load,late[init._nid],init.cfg0(),lca,init,anti);
+ break;
case PhiNode phi:
// Repeat anti-dep for matching Phi inputs.
// No anti-dep edges but may raise the LCA.
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Opto.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Opto.java
index efdcff6c..c41a29ac 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Opto.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Opto.java
@@ -3,9 +3,7 @@ package com.seaofnodes.simple.codegen;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.util.Ary;
-import com.seaofnodes.simple.util.Utils;
-import java.util.Arrays;
import java.util.BitSet;
// Optimistic Algorithm
@@ -45,36 +43,6 @@ abstract public class Opto {
// After SCCP add everything changed to the worklist and see if
// peepholes can make more progress.
moveChangesToWorklist(code, oldTypes);
-
- // Progress with improved types. E.g. since we have a full Call Graph
- // here, we might do better inlining.
- code._iter.iterate(code);
-
- // TODO:
- // loop unroll, peel, RCE, etc
-
- // Freeze field sizes; do struct layouts; convert field offsets into
- // constants.
- fieldSizes(code);
- // Propagate field size constants
- code._iter.iterate(code);
-
- // Retain surviving functions in their CompUnits for later code
- // emission. Start inputs are semantic: they mean unknown callers.
- for( FunNode fun : code._linker )
- if( fun != null && !fun.isDead() && !fun._type.isHigh() )
- keepInCompUnit(code,fun);
-
- for( CompUnit cu : code._compunits.values() ) {
- ProjNode mem = cu._start.proj(1);
- if( mem != null ) mem.unkeep();
- }
-
- // To help with testing, sort StopNode Returns by NID
- for( Node stop : code._stop._inputs )
- Arrays.sort(stop._inputs._es,0,stop.nIns(),
- (x,y) -> ((((ReturnNode)x).fun().isClz() ? -1 : x._nid) -
- (((ReturnNode)y).fun().isClz() ? -1 : y._nid) ));
}
// Reset Types before running the worklist algorithm
@@ -178,7 +146,7 @@ abstract public class Opto {
if( fun.nIns() < 2 ) {
assert fun.nIns() < 2 || fun.in(1) != start;
assert !start.isDead();
- keepInCompUnit(code,fun);
+ code.keepInCompUnit(fun);
// Function is reachable by any *remote* caller who gets the pointer!
fun.insertDef(1,start);
code._iter.add(fun);
@@ -191,14 +159,6 @@ abstract public class Opto {
}
}
- // Keep a surviving function rooted in its compilation unit without
- // claiming that it has unknown callers.
- private static void keepInCompUnit(CodeGen code, FunNode fun) {
- StopCUNode stop = fun._compunit._stop;
- if( stop._inputs.find(fun.ret()) == -1 )
- code.add(stop).addDef(fun.ret());
- }
-
// The core SCCP algorithm
private static void sccp( CodeGen code, Ary<Type> oldTypes ) {
@@ -353,19 +313,4 @@ abstract public class Opto {
return evidence;
}
-
-
- // Freeze field sizes; do struct layouts; convert field offsets into
- // constants.
- private static void fieldSizes( CodeGen code ) {
- for( int i=0; i<code._start.nOuts(); i++ ) {
- Node use = code._start.out(i);
- if( use instanceof ConFldOffNode off ) {
- Node con = off.asOffset();
- assert con != null; // Can be null for trying to reference missing field
- off.subsume( con );
- i--;
- }
- }
- }
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ArithNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ArithNode.java
index bc47ebfc..92ebd587 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/ArithNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ArithNode.java
@@ -55,6 +55,9 @@ public abstract class ArithNode extends Node implements ModeNode {
if( n instanceof ModeNode mode && mode.mode()==0 ) {
mode.setMode((byte)1);
n.setType(n.compute());
+ // Mode changes remove n from GVN even if the caller's remaining
+ // conditions prevent replacing the int-only operation by n.
+ CodeGen.CODE.add(n);
}
return true;
}
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 82c4af10..850a5711 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
@@ -160,7 +160,7 @@ public class BulkMemPhiNode extends PhiNode {
case MemPhiNode phi -> unsplit(phi._alias);
case EscapeNode esc -> esc.pub()==this ? unsplit(esc.fld()._alias) : 0;
default -> {
- assert use instanceof ReturnNode || use instanceof CallNode
+ assert use instanceof ReturnNode || use instanceof CallNode || use instanceof CLInitNode
: "Unexpected bulk-memory user "+use.getClass().getSimpleName()+"#"+use._nid+": "+use;
yield 0;
}
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 fe3382a6..4f225fbe 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/CFGNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CFGNode.java
@@ -109,6 +109,7 @@ public abstract class CFGNode extends Node {
}
private CFGNode _safeCFG() {
return switch(this) {
+ case CLInitNode init when init.bypass() -> init.cfg0()._safeCFG();
case CallNode call when call.cend()._folding -> call.cfg0()._safeCFG();
case CallEndNode cend when cend._folding -> cend.cfg(1)._safeCFG();
case FunNode fun when fun._folding -> fun.cfg(1)._safeCFG();
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/CLInitNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CLInitNode.java
index c9aeb874..9a41cc18 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/CLInitNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CLInitNode.java
@@ -6,23 +6,24 @@ import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.codegen.CompUnit;
import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.util.BAOS;
-import com.seaofnodes.simple.util.Utils;
import java.util.*;
// Track class initializers - the code just after this point expects the named
// class to be initialized. If this code path dies, then the dependency dies
// also - dead code does not force class init.
//
-public class CLInitNode extends CFGNode {
+public class CLInitNode extends CFGNode implements MultiNode {
public Parser.Lexer _loc; // For error reporting
+ // Inputs: control, whole memory, class pointer, loaded $init flag
+ // Results: control, whole memory, known-true flag alias memory.
// For field references, the ptr might be the dst clz ptr or not.
- public CLInitNode(Parser.Lexer loc, Node ctrl, Node ptr) {
- super(ctrl,ptr);
+ public CLInitNode(Parser.Lexer loc, Node ctrl, Node mem, Node ptr, Node ready) {
+ super(ctrl,mem,ptr,ready);
_loc = loc;
}
- CLInitNode(BAOS bais, String[] strs) { super(null,null); _loc=Parser.Lexer.unpack(bais,strs); }
+ CLInitNode(BAOS bais, String[] strs) { super(null,null,null,null); _loc=Parser.Lexer.unpack(bais,strs); }
@Override public Tag serialTag() { return Tag.CLInit; }
@Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
Parser.Lexer.packed(_loc,baos,strs);
@@ -34,7 +35,15 @@ public class CLInitNode extends CFGNode {
_loc = init._loc;
}
- Node ptr() { return in(1); }
+ public Node mem() { return in(1); }
+ public Node ptr() { return in(2); }
+ public Node ready() { return in(3); }
+
+ // The field/alias can be unresolved until forward references are loaded.
+ public Field flag() {
+ return ptr()._type instanceof TypeMemPtr tmp && Parser.startsClzPrefix(tmp._obj._name)
+ ? tmp._obj.field("$init") : null;
+ }
@Override public String label() { return "CLInit"; }
// Best effort dst compunit, based on ptr - or null
@@ -55,14 +64,51 @@ public class CLInitNode extends CFGNode {
// Block head is Start, Region, CProj, but not e.g. If, Return, Stop
public boolean blockHead() { return true; }
- @Override public Type compute() { return in(0)._type; }
- @Override public Node idealize() {
- // A receiver discovered to be an ordinary instance needs no class init.
- if( ptr()._type instanceof TypeMemPtr ptr && ptr._obj!=TypeStruct.BOT &&
- !ptr._obj.isHigh() && !Parser.startsClzPrefix(ptr._obj._name) )
- return in(0); // Normal instance, no class init
- if( ptr()._type == Type.NIL || ptr()._type.isHigh() )
- return in(0);
- return null;
+ private boolean instance() {
+ return ptr()._type==Type.NIL ||
+ (ptr()._type instanceof TypeMemPtr tmp && tmp._obj!=TypeStruct.BOT &&
+ !tmp._obj.isHigh() && !tmp._obj._open && !tmp._obj._fref &&
+ !Parser.startsClzPrefix(tmp._obj._name));
+ }
+
+ public boolean bypass() { return instance() || ready()._type==TypeInteger.TRUE; }
+
+ @Override public Type compute() {
+ if( in(0)._type.isHigh() || ptr()._type.isHigh() )
+ return TypeTuple.make(Type.XCONTROL,TypeMem.TOP,TypeMem.TOP);
+ if( instance() )
+ return TypeTuple.make(in(0)._type,mem()._type,mem()._type);
+ Field flag = flag();
+ Type slice = flag==null ? TypeMem.BOT : TypeMem.make(flag._alias,TypeInteger.TRUE);
+ // The initializer may write arbitrary public memory. Only its flag
+ // has a guaranteed value on the normal continuation.
+ Type bulk = ready()._type.isHigh() ? TypeMem.TOP :
+ ready()._type==TypeInteger.TRUE ? mem()._type : TypeMem.BOT;
+ return TypeTuple.make(in(0)._type,bulk,slice);
+ }
+
+ @Override public Node pcopy(int idx, Node dep) {
+ dep.addDep(ptr());
+ dep.addDep(ready());
+ if( instance() ) return idx==0 ? in(0) : mem();
+ if( ready()._type!=TypeInteger.TRUE ) return null;
+ if( idx==0 ) return in(0);
+ if( idx==1 ) return mem();
+ // Already established on the incoming path; this is a memory fact,
+ // not a new store to the class object.
+ return CodeGen.CODE.con(((TypeTuple)_type)._types[2]);
+ }
+
+ @Override public Node idealize() { return null; }
+
+ // Resolve a precise flag slice even when the parser did not yet know the
+ // class/alias and could only install the whole-memory projection.
+ static Node slice(Node mem, int alias, Node dep) {
+ if( !(mem instanceof ProjNode proj && proj._idx==1 && proj.in(0) instanceof CLInitNode init) )
+ return mem;
+ dep.addDep(init.ptr());
+ Field flag = init.flag();
+ if( flag==null || flag._alias!=alias ) return mem;
+ return new ProjNode(init,2,Parser.memName(alias)).peephole();
}
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/CProjNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CProjNode.java
index 12fde6c0..073e4658 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/CProjNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CProjNode.java
@@ -62,7 +62,7 @@ public class CProjNode extends CFGNode implements Proj {
return new CProjNode(new IfNode(iff.ctrl(),not.in(1)).peephole(),1-_idx,_idx==0 ? "False" : "True");
// Copy of some other input
- return ((MultiNode)ctrl()).pcopy(_idx);
+ return ((MultiNode)ctrl()).pcopy(_idx,this);
}
// Only called during basic-block layout, inverts a T/F CProj
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
index 13e7ac12..555d0c65 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
@@ -302,7 +302,7 @@ public class CallEndNode extends CFGNode implements MultiNode {
}
- @Override public Node pcopy(int idx) {
+ @Override public Node pcopy(int idx, Node dep) {
return _folding ? in(1).in(idx) : null;
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ConstantNode.java
index 47a7bbf6..4357570e 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/ConstantNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ConstantNode.java
@@ -37,13 +37,13 @@ public class ConstantNode extends TypeNode {
}
static Node make( BAOS bais, Type[] types) { return raw(types[bais.packed2()]); }
- // Semantic constant construction through Opto. A unique internal
+ // Semantic constant construction through FinalizeIR. A unique internal
// function identity carries a lifetime edge to the function's Return.
public static Node make( Type type ) {
if( type==Type. CONTROL ) return new CtrlNode();
if( type==Type.XCONTROL ) return new XCtrlNode();
if( CodeGen.CODE._phase != null &&
- CodeGen.CODE._phase.ordinal() > CodeGen.Phase.Opto.ordinal() )
+ CodeGen.CODE._phase.ordinal() > CodeGen.Phase.FinalizeIR.ordinal() )
return raw(type);
if( type instanceof TypeFunPtr tfp && tfp.isConstant() ) {
FunNode fun = CodeGen.CODE._link(tfp);
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ConvertNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ConvertNode.java
index b6c3b0c9..cdc890a7 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/ConvertNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ConvertNode.java
@@ -30,10 +30,10 @@ public class ConvertNode extends Node {
}
// The declaration fixes the result family during pessimistic parsing.
- // Optimistic SCCP must retain stronger facts already inside that family.
+ // From SCCP onward, preserve dead inputs and stronger facts in that family.
@Override public Type compute() {
Type src = val()._type;
- if( CodeGen.CODE._phase == CodeGen.Phase.Opto ) {
+ if( CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Opto.ordinal() ) {
if( src.isHigh() ) return Type.TOP;
if( (src.isa(_dst) && src.accessISA(_dst)) ) return src;
}
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 a557f97c..0a997083 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java
@@ -47,7 +47,8 @@ public class LoadNode extends MemOpNode {
dep.addDepForwards(use);
if( use instanceof StoreNode || use instanceof PhiNode ||
use instanceof MemMergeNode ||
- use instanceof NewNode || use instanceof EscapeNode || use instanceof CallNode ) return true;
+ use instanceof NewNode || use instanceof EscapeNode || use instanceof CallNode ||
+ use instanceof CLInitNode ) return true;
}
return false;
}
@@ -134,6 +135,9 @@ public class LoadNode extends MemOpNode {
if( _alias == 1 )
return null;
+ Node slice = CLInitNode.slice(mem,_alias,this);
+ if( slice!=mem ) { setDef(1,slice); return this; }
+
if( mem instanceof MemMergeNode merge ) {
setDef(1,merge.alias(_alias));
// In support of uplifting control to a prior dominating load, if
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
index 373b8825..b31e14d5 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
@@ -130,10 +130,10 @@ public abstract class MemOpNode extends TypeNode {
if( !(ptr instanceof TypeMemPtr tmp && tmp.notNull()) )
return Parser.error( "Might be null accessing '" + _name + "'",_loc);
if( tmp._obj.field(_name)==null &&
- CodeGen.CODE._phase.ordinal() > CodeGen.Phase.Opto.ordinal() )
+ CodeGen.CODE._phase.ordinal() > CodeGen.Phase.FinalizeIR.ordinal() )
return Parser.error("Accessing unknown field '"+_name+"' from '*"+tmp._obj._name+"'",_loc);
// Sane field
- if( off() instanceof ConFldOffNode && CodeGen.CODE._phase.ordinal() > CodeGen.Phase.Opto.ordinal() )
+ if( off() instanceof ConFldOffNode && CodeGen.CODE._phase.ordinal() > CodeGen.Phase.FinalizeIR.ordinal() )
return Parser.error("Accessing unknown field '"+_name+"' from '*"+tmp._obj._name+"'",_loc);
return null;
}
@@ -154,7 +154,7 @@ public abstract class MemOpNode extends TypeNode {
Node slice = _alias<merge.nIns() ? merge.in(_alias) : null;
if( (slice==mem) || (slice==null && merge.in(1)==mem) )
antiDeps(merge,deps,visit);
- } else if( use instanceof CallNode ||
+ } else if( use instanceof CallNode || use instanceof CLInitNode ||
(use instanceof MemOpNode st && !st._isLoad && st._alias==_alias) ||
(use instanceof EscapeNode esc && esc.fld()._alias==_alias) ||
(use instanceof MemPhiNode phi && phi._alias==_alias) ||
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java
index cdfaaf97..aaaedb89 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java
@@ -56,11 +56,14 @@ public class MemPhiNode extends PhiNode {
public Node idealize() {
// A precise memory Phi can always bypass a MemMerge and select its
// matching slice.
- for( int i=1; i<nIns(); i++ )
+ for( int i=1; i<nIns(); i++ ) {
if( in(i) instanceof MemMergeNode mem ) {
setDef(i,mem.alias(_alias));
return this;
}
+ Node slice = CLInitNode.slice(in(i),_alias,this);
+ if( slice!=in(i) ) { setDef(i,slice); return this; }
+ }
if( nIns()==3 && region() instanceof LoopNode loop && !loop.inProgress() &&
in(2) instanceof StoreNode st && st.mem()==this ) {
Node sink = st.sink(this);
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/MultiNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MultiNode.java
index b4d6d78e..152d525a 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/MultiNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/MultiNode.java
@@ -25,5 +25,5 @@ public interface MultiNode {
// Return not-null if this projection index is a ideal copy.
// Called by ProjNode ideal and used to collapse Multis.
- default Node pcopy(int idx) { return null; }
+ default Node pcopy(int idx, Node dep) { return null; }
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ProjNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ProjNode.java
index 4b29cc3c..fcc113a8 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/ProjNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ProjNode.java
@@ -55,7 +55,7 @@ public class ProjNode extends Node implements Proj {
return t instanceof TypeTuple tt ? tt._types[_idx] : t.oob();
}
- @Override public Node idealize() { return ((MultiNode)in(0)).pcopy(_idx); }
+ @Override public Node idealize() { return ((MultiNode)in(0)).pcopy(_idx,this); }
@Override
public boolean eq( Node n ) { return _idx == ((ProjNode)n)._idx; }
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/StopCUNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/StopCUNode.java
index ab0f5da6..f2bd5841 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/StopCUNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/StopCUNode.java
@@ -40,7 +40,8 @@ public class StopCUNode extends StopNode {
// can widen from a precise set to FULL; the public-only normalization
// below must not forget private bits already discovered precisely from
// that Return on an earlier SCCP step.
- if( CodeGen.CODE._phase == CodeGen.Phase.Opto &&
+ if( CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Opto.ordinal() &&
+ CodeGen.CODE._phase.ordinal() <= CodeGen.Phase.FinalizeIR.ordinal() &&
_type instanceof TypeTuple old && old.mem() instanceof TypeMem omem )
xmem = xmem.makeFrom(XInt.meet(xmem._escFs,omem._escFs),
XInt.meet(xmem._escAs,omem._escAs));
@@ -100,7 +101,8 @@ public class StopCUNode extends StopNode {
// precise finite sets. Replacing FULL early there would let a later
// input refinement add bits, reversing Iter's monotonic direction.
// This normalization is needed only for Opto's optimistic grand cycle.
- if( CodeGen.CODE._phase != CodeGen.Phase.Opto ) return mem;
+ if( CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal() ||
+ CodeGen.CODE._phase.ordinal() > CodeGen.Phase.FinalizeIR.ordinal() ) return mem;
int[] escFs = XInt.isHigh(mem._escFs)
? XInt.meet(prior._escFs,CodeGen.CODE.publicFIDXs())
: mem._escFs;
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java
index 4917e890..418b1e92 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java
@@ -304,7 +304,7 @@ public class StoreNode extends MemOpNode {
public Parser.ParseException err() {
Parser.ParseException err = super.err();
if( err != null ) return err;
- assert _size>0 || CodeGen.CODE._phase.ordinal() <= CodeGen.Phase.Opto.ordinal();
+ assert _size>0 || CodeGen.CODE._phase.ordinal() <= CodeGen.Phase.FinalizeIR.ordinal();
if( ptr()._type == Type.TOP ) // Dead store?
return null;
TypeMemPtr tmp = (TypeMemPtr)ptr()._type;
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
index 660f1559..8c9bed95 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
@@ -305,6 +305,7 @@ public class arm extends Machine {
case PtrToIntNode ptr -> new PtrToIntMach(ptr);
case GuardNode guard -> new GuardMach(guard);
case CallEndNode cend-> new CallEndMach(cend);
+ case CLInitNode init -> new CLInitNode(init);
case CProjNode c -> new CProjNode(c);
case ConstantNode con-> con(con);
case DivNode div -> div.mode()==2 ? new DivFARM(div) : new DivARM(div);
@@ -459,6 +460,7 @@ public class arm extends Machine {
}
private Node prj(ProjNode prj) {
+ if( prj.in(0) instanceof CLInitNode ) return new ProjNode(prj);
TypeInteger ret = cReturn(prj);
if( ret!=null ) return new ExtendARM(prj,ret);
return prj.in(0) instanceof StartNode ? new ProjNode(prj) : new ProjARM(prj);
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
index 19b91866..0195542c 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
@@ -218,6 +218,7 @@ public class riscv extends Machine {
case PtrToIntNode ptr -> new PtrToIntMach(ptr);
case GuardNode guard -> new GuardMach(guard);
case CallEndNode cend -> new CallEndMach(cend);
+ case CLInitNode init -> new CLInitNode(init);
case CProjNode c -> new CProjNode(c);
case ConstantNode con -> con(con);
case DivNode div -> div.mode()==2 ? new DivFRISC(div) : new DivRISC(div);
@@ -412,6 +413,7 @@ public class riscv extends Machine {
}
private Node prj(ProjNode prj) {
+ if( prj.in(0) instanceof CLInitNode ) return new ProjNode(prj);
TypeInteger ret = cReturn(prj);
if( ret!=null ) return new ExtendRISC(prj,ret);
return prj.in(0) instanceof StartNode ? new ProjNode(prj) : new ProjRISC(prj);
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
index 23c4d530..7b351533 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
@@ -491,6 +491,7 @@ public class x86_64_v2 extends Machine {
}
private Node prj( ProjNode prj ) {
+ if( prj.in(0) instanceof CLInitNode ) return new ProjNode(prj);
TypeInteger ret = cReturn(prj);
if( ret!=null ) return new ExtendX86(prj,ret);
return prj.in(0) instanceof StartNode ? new ProjNode(prj) : new ProjX86(prj);
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java
index bf8403e1..e9231d9a 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java
@@ -17,7 +17,7 @@ public class SimpleGraphObserver extends GraphCapture<Node> {
@Override protected void compile(String src) {
_code = new CodeGen(src);
_code._obs = this;
- _code.parse().opto().typeCheck();
+ _code.parse().iter().opto().iterPostOpto().finalizeIR().typeCheck();
}
@Override protected String phase() { return _code._phase.name(); }
@Override protected int pos() { return _code._phase == CodeGen.Phase.Parse ? _code.P.pos() : -1; }
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeStruct.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
index 82c37a12..4c5ae6ea 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
@@ -597,7 +597,7 @@ public class TypeStruct extends Type {
return _offs[idx];
}
private int[] offsets() { // Field byte offsets
- assert CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Opto.ordinal();
+ assert CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.FinalizeIR.ordinal();
// Compute a layout for a collection of fields
assert _fields != null; // No forward refs