summaryrefslogtreecommitdiff
path: root/chapter25/src/main/java/com
diff options
context:
space:
mode:
Diffstat (limited to 'chapter25/src/main/java/com')
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/Parser.java11
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java176
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java7
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java38
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/Opto.java1
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java1
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/Serialize.java5
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/CLInitNode.java30
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java24
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/FunNode.java38
10 files changed, 294 insertions, 37 deletions
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/Parser.java b/chapter25/src/main/java/com/seaofnodes/simple/Parser.java
index cd80b3ff..a6afc91f 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/Parser.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/Parser.java
@@ -2297,6 +2297,15 @@ public class Parser {
fld = fieldOrOpen(ts,name,false);
if( fld==null && ts!=null && !latestStruct(ts)._open )
throw error("Accessing unknown field '"+name+"' from '*"+ts._name+"'");
+
+ // We have to record "active touches" of a new class, to track when we
+ // need to force class initialization. Basically, we insert an CLInit,
+ // and if it does not fold away it'll turn into a runtime check.
+ 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(loc(),name,ctrl(),expr).peephole());
+
// With no field, we will update the bulk memory
int alias = fld==null ? 1 : fld._alias;
Type decl = fld==null ? Type.BOTTOM : fld._t;
@@ -2694,6 +2703,8 @@ public class Parser {
return that != null && _input == that._input && _position < that._position;
}
+ public String position() { return _line_number+":"+(_position-_line_start+1); }
+
/**
* Record the source text for lexing
*/
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java
new file mode 100644
index 00000000..96bdbb33
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java
@@ -0,0 +1,176 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import java.util.*;
+
+/** Initialization requirements of the optimized, executable graph. */
+public final class ClassInitDependencies {
+ private final CodeGen code;
+ private ArrayList<CompUnit> order;
+
+ private static final class Effects {
+ final FunNode fun;
+ final TreeMap<String,String> touches = new TreeMap<>();
+ final LinkedHashMap<Effects,String> callers = new LinkedHashMap<>();
+ boolean complete;
+ Effects(FunNode fun, boolean complete) { this.fun=fun; this.complete=complete; }
+ }
+
+ public ClassInitDependencies(CodeGen code) { this.code=code; }
+ public void invalidate() { order=null; }
+
+ // Resolve named class objects, including static nested class objects in a file.
+ private CompUnit owner(String name) {
+ name=name.substring(Parser.CLZ.length());
+ CompUnit best=null;
+ for( CompUnit cu : code._compunits.values() )
+ if( cu._cname!=null && (name.equals(cu._cname) || name.startsWith(cu._cname+".")) &&
+ (best==null || cu._cname.length()>best._cname.length()) ) best=cu;
+ return best;
+ }
+
+ private static String reason(FunNode fun, String what, Parser.Lexer loc) {
+ return fun._compunit._cname+" ("+fun.label()+"): "+what+
+ (loc==null ? "" : " at "+loc.position());
+ }
+
+ // Walk control forwards, not backwards from Return: a live touch before an
+ // infinite loop or nonreturning call must still contribute. Never cross a
+ // function boundary through Call -> Fun or Return -> CallEnd graph edges.
+ private void scan(Effects fx, TreeMap<Integer,Effects> functions) {
+ ArrayDeque<CFGNode> work=new ArrayDeque<>();
+ BitSet seen=new BitSet();
+ work.add(fx.fun);
+ while( !work.isEmpty() ) {
+ CFGNode cfg=work.remove();
+ if( seen.get(cfg._nid) || cfg._type.isHigh() ) continue;
+ seen.set(cfg._nid);
+ if( cfg instanceof CLInitNode init ) {
+ if( init.ptr()._type instanceof TypeMemPtr ptr && ptr._obj!=TypeStruct.BOT && !ptr._obj.isHigh() ) {
+ if( Parser.startsClzPrefix(ptr._obj._name) ) {
+ CompUnit target=owner(ptr._obj._name);
+ if( target==null ) fx.complete=false;
+ else fx.touches.putIfAbsent(target._cname,reason(fx.fun,"field "+init._field,init._loc));
+ }
+ } else fx.complete=false;
+ }
+ if( cfg instanceof CallNode call ) {
+ String why=reason(fx.fun,"call",call._loc);
+ if( call.fptr()._type instanceof TypeFunPtr fp && !XInt.isHigh(fp.fidxs()) && fp.nfcns()>0 ) {
+ for( int id=XInt.next(fp.fidxs(),0); id>=0; id=XInt.next(fp.fidxs(),id) ) {
+ Effects callee=functions.get(id);
+ if( callee==null ) { fx.complete=false; continue; }
+ if( callee.fun.isClz() )
+ fx.touches.putIfAbsent(callee.fun._compunit._cname,why);
+ else callee.callers.putIfAbsent(fx,why);
+ }
+ } else fx.complete=false;
+ }
+ if( cfg instanceof ReturnNode ) continue;
+ for( Node use : cfg._outputs ) {
+ if( !(use instanceof CFGNode next) || next instanceof FunNode || next instanceof StopNode ) continue;
+ if( next instanceof RegionNode r ) {
+ if( r._inputs.find(cfg)>0 ) work.add(r);
+ } else if( next.in(0)==cfg ) work.add(next);
+ }
+ }
+ }
+
+ /** Reject post-Opto cycles; incomplete imports/native effects are NOT certified. */
+ public void check() {
+ if( order!=null ) return;
+ assert code._phase.ordinal() >= CodeGen.Phase.Opto.ordinal();
+ TreeMap<Integer,Effects> functions=new TreeMap<>();
+ code._start.walk(n -> {
+ if( n instanceof FunNode fun && !fun.isDead() && !fun._type.isHigh() )
+ functions.put(fun.sig().fidx(),new Effects(fun,code.owns(fun)));
+ return null;
+ });
+ for( Effects fx : functions.values() ) scan(fx,functions);
+
+ // Recursive ordinary functions converge; only class requirements form
+ // initialization edges. This uses final call targets, never old types.
+ ArrayDeque<Effects> work=new ArrayDeque<>(functions.values());
+ HashSet<Effects> queued=new HashSet<>(functions.values());
+ while( !work.isEmpty() ) {
+ Effects callee=work.remove();
+ queued.remove(callee);
+ for( var edge : callee.callers.entrySet() ) {
+ Effects caller=edge.getKey();
+ boolean changed=false;
+ for( var touch : callee.touches.entrySet() )
+ if( caller.touches.putIfAbsent(touch.getKey(),edge.getValue()+"; then "+touch.getValue())==null ) changed=true;
+ if( caller.complete && !callee.complete ) { caller.complete=false; changed=true; }
+ if( changed && queued.add(caller) ) work.add(caller);
+ }
+ }
+ TreeMap<String,CompUnit> units=new TreeMap<>();
+ // Reuse local initializer summaries for diagnostics; no witnesses are
+ // retained on CompUnit or on this checker after validation returns.
+ IdentityHashMap<CompUnit,TreeMap<String,String>> reasons=new IdentityHashMap<>();
+ for( CompUnit cu : code._compunits.values() )
+ if( cu._cname!=null ) {
+ units.put(cu._cname,cu);
+ cu._classInitDeps=null;
+ cu._initComplete=false; // Imported summaries arrive in checkpoint 2.
+ if( cu._par!=null ) cu.addClassInitDep(code,cu._par);
+ }
+ for( Effects fx : functions.values() )
+ if( fx.fun.isClz() ) {
+ CompUnit cu=fx.fun._compunit;
+ cu._initComplete=fx.complete;
+ reasons.put(cu,fx.touches);
+ for( String target : fx.touches.keySet() ) cu.addClassInitDep(code,units.get(target));
+ }
+ ArrayList<CompUnit> sorted=new ArrayList<>(), path=new ArrayList<>();
+ IdentityHashMap<CompUnit,Integer> colors=new IdentityHashMap<>();
+ for( CompUnit cu : units.values() ) visit(cu,colors,path,sorted,reasons);
+ for( CompUnit cu : sorted )
+ if( cu._classInitDeps!=null )
+ for( CompUnit dep : cu._classInitDeps ) cu._initComplete &= dep._initComplete;
+ order=sorted;
+ }
+
+ // Temporary checkpoint lowering: retain existing runtime behavior. Later
+ // this becomes flag-and-call lowering, and obligations survive serialization.
+ public void eraseChecks() {
+ ArrayList<CLInitNode> checks=new ArrayList<>();
+ code._start.walk(n -> { if( n instanceof CLInitNode init ) checks.add(init); return null; });
+ for( CLInitNode init : checks )
+ if( !init.isDead() ) init.subsume(init.in(0));
+ code.invalidateIDepthCaches();
+ }
+
+ private void visit(CompUnit cu, IdentityHashMap<CompUnit,Integer> colors,
+ ArrayList<CompUnit> path, ArrayList<CompUnit> sorted,
+ IdentityHashMap<CompUnit,TreeMap<String,String>> reasons) {
+ Integer color=colors.get(cu);
+ if( color!=null ) {
+ if( color==2 ) return;
+ int start=path.indexOf(cu);
+ StringBuilder message=new StringBuilder("Cyclic class initialization: ");
+ for( int i=start; i<path.size(); i++ ) message.append(path.get(i)._cname).append(" -> ");
+ message.append(cu._cname);
+ for( int i=start; i<path.size(); i++ ) {
+ CompUnit from=path.get(i), to=i+1<path.size() ? path.get(i+1) : cu;
+ message.append("\n ").append(from._cname).append(" -> ").append(to._cname)
+ .append(": ").append(from._par==to ? "parent of "+from._cname : reasons.get(from).get(to._cname));
+ }
+ throw Parser.error(message.toString(),null);
+ }
+ colors.put(cu,1);
+ path.add(cu);
+ ArrayList<CompUnit> deps=new ArrayList<>();
+ if( cu._classInitDeps!=null ) for( CompUnit dep : cu._classInitDeps ) deps.add(dep);
+ deps.sort(Comparator.comparing(dep -> dep._cname));
+ for( CompUnit dep : deps ) visit(dep,colors,path,sorted,reasons);
+ path.removeLast();
+ colors.put(cu,2);
+ sorted.add(cu);
+ }
+
+ /** Prerequisite-first order of the known graph, not a completeness certificate. */
+ public ArrayList<CompUnit> order() { check(); return new ArrayList<>(order); }
+}
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 6fc66e4f..bc86dc6d 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
@@ -82,6 +82,7 @@ public class CodeGen {
// Compilation Units in this compile; one per source/object file
public HashMap<String,CompUnit> _compunits;
+ public final ClassInitDependencies _classInitDeps = new ClassInitDependencies(this);
// Test shortcut for only one compilation unit
public CompUnit compunit() {
assert _compunits.size()==1;
@@ -534,6 +535,11 @@ public class CodeGen {
_phase = Phase.TypeCheck;
long t0 = System.currentTimeMillis();
+ // Check for class-init cycles first; such a cycle can prevent other
+ // things from resolving types. Get the classes ordered and init'd
+ // before reporting other errors.
+ _classInitDeps.check();
+
final Ary<Node> errs = new Ary<>(Node.class);
_stop.walk( n -> {
if( n.err() != null )
@@ -572,6 +578,7 @@ public class CodeGen {
if( _obs != null ) _obs.error(null, err.getMessage());
throw err;
}
+ _classInitDeps.eraseChecks();
if( _obs != null ) _obs.phase("TypeCheck");
return this;
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java
index d1326cee..6d84c955 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java
@@ -16,19 +16,38 @@ import static com.seaofnodes.simple.codegen.CodeGen.CODE;
// files. These are mirrored over a file system tree (so form a tree), both
// the source tree and build/object tree.
public class CompUnit {
- public final CompUnit _par; // Parent, e.g. A/B
+ public final CompUnit _par; // Parent, e.g. A/B
final String _fname; // Full slashed file name starting from module root, e.g. A/B/C
final public String _cname; // Full dotted class name starting from module root, e.g. A.B.C
final String _name; // Base name, e.g. C
final ExternNode _ext; // Found in external libs; e.g. libc
final File _smp; // Simple source file, e.g. module_root/A/B/C.smp
- public final File _obj; // ELF output file, e.g. build/A/B/C.o
+ public final File _obj; // ELF output file, e.g. build/A/B/C.o
final public String _src; // Source code, from file or test case
- //public Encoding _encoding; // Encoding for output
public TypeStruct _clz; // The one TypeStruct being published
- //BAOS _serial; // Serialized IR for this ELF file
- Ary<CompUnit> _deps; // CompUnits that this CompUnit depends on
+
+ // _deps records units required for types, code, or class references,
+ // without requiring their initializers to run. These discovery/rebuild
+ // dependencies may form a cycle.
+ Ary<CompUnit> _deps;
+
+ // _classInitDeps records units whose initialization this initializer may
+ // require after Opto, including its parent; these edges must form a DAG.
+ // Initialization prerequisites are a subset of the units that must be
+ // available, but not necessarily of the direct _deps list: parent links
+ // and helper-call effects can add initialization edges to units discovered
+ // indirectly.
+ public Ary<CompUnit> _classInitDeps;
+
+ // Set after completing the cycle analysis for the <clinit> (and
+ // prerequisites).
+ public boolean _initComplete;
+
+ // This unit's own initializer is proven fully represented by static data
+ // and needs no execution; its ancestors may still need initialization.
+ // Reserved for static classification; currently always false.
+ public boolean _initStatic;
// Per-Compilation-Unit Start/StopNodes, keeping alive all exported Nodes.
// Null means no code loaded (yet).
@@ -123,6 +142,15 @@ public class CompUnit {
}
+ public boolean addClassInitDep(CodeGen code, CompUnit target) {
+ if( _classInitDeps==null ) _classInitDeps=new Ary<>(CompUnit.class);
+ if( _classInitDeps.find(target)!=-1 ) return false;
+ _classInitDeps.add(target);
+ code._classInitDeps.invalidate();
+ return true;
+ }
+
+
@Override public String toString() {
String x = "{"+_cname;
if( _smp!=null && _smp.exists()) x += ",hasSMP";
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 132705aa..efdcff6c 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Opto.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Opto.java
@@ -59,7 +59,6 @@ abstract public class Opto {
// 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 )
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java
index 90309328..155066a4 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java
@@ -42,6 +42,7 @@ public abstract class ParseAll {
if( cunit != null ) return cunit;
String cname = par==null ? name : (par._cname + "." + name).intern();
code._compunits.put(fname, cunit = external ? new CompUnit(obj,par,fname,cname,name) : new CompUnit(par,fname,cname,name));
+ code._classInitDeps.invalidate();
return cunit;
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Serialize.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Serialize.java
index 27253c5e..ce546318 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Serialize.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Serialize.java
@@ -508,8 +508,9 @@ abstract public class Serialize {
// Whole CompUnits have no users, went dead
if( cu._start != null && !cu._start.isDead() ) {
nodes.add(cu._start);
- // Memory proj
- nodes.add(cu._start.proj(1));
+ // A memory-free, nonreturning unit can lose its memory projection.
+ ProjNode mem=cu._start.proj(1);
+ if( mem!=null ) nodes.add(mem);
// All the functions, including internal ones, in comp-unit order
for( FunNode fun : code._linker )
if( fun!=null && !fun.isDead() && fun._compunit == cu )
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/CLInitNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CLInitNode.java
new file mode 100644
index 00000000..f00d8076
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CLInitNode.java
@@ -0,0 +1,30 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.*;
+
+/** An active class touch, retained in control until initialization validation.
+ * This checkpoint records the obligation only; flag/call lowering comes later.
+ */
+public class CLInitNode extends CFGNode {
+ public final Parser.Lexer _loc;
+ public final String _field;
+
+ public CLInitNode(Parser.Lexer loc, String field, Node ctrl, Node ptr) {
+ super(ctrl,ptr);
+ _loc=loc;
+ _field=field;
+ }
+
+ public Node ptr() { return in(1); }
+ @Override public String label() { return "CLInit"; }
+ @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p("CLInit ").p(ptr()); }
+ @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);
+ return null;
+ }
+}
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 35180fea..13e7ac12 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
@@ -17,8 +17,8 @@ public class CallEndNode extends CFGNode implements MultiNode {
// When set true, this Call/CallEnd/Fun/Return is being trivially inlined
boolean _folding;
- // +2 must clone, so await all cleanup but OK
- // +1 trivial now,
+ // +2: must clone, so await all cleanup but OK
+ // +1: trivial now, so can inline without cloning and without waiting for cleanup
// 0: take a look,
// -1: somethings wrong, but might improve
// -2: never and stop asking,
@@ -122,9 +122,10 @@ public class CallEndNode extends CFGNode implements MultiNode {
state = (TypeTuple)state.meet(in(i)._type);
// A reachable call whose presently linked exits are all unreachable
// keeps a conservative continuation. In particular, do not let a
- // transient no-return result tear down the caller during SCCP.
- if( state.ctl()==Type.XCONTROL &&
- CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Opto.ordinal() )
+ // transient no-return result tear down the caller before/during SCCP.
+ // If the call remains out-of-line, its continuation can conservatively
+ // contribute class-init dependencies even though it never executes.
+ if( state.ctl()==Type.XCONTROL )
return TypeTuple.make(Type.CONTROL,TypeMem.TOP,Type.TOP);
// At least as good as the TFP
return state.makeFrom(2,state.ret().join(tfp.ret()));
@@ -158,7 +159,10 @@ public class CallEndNode extends CFGNode implements MultiNode {
ReturnNode ret = (ReturnNode)in(1);
FunNode fun = ret.fun();
- // Heuristic forced inlining off via name
+ // Preserve initializer boundaries until post-Opto cycle validation.
+ if( fun.isClz() ) return -2;
+
+ // Heuristic forced inlining turned off via name
if( fun._name != null &&
fun._name.endsWith("_noInline") )
return -2; // Never, stop asking
@@ -168,7 +172,7 @@ public class CallEndNode extends CFGNode implements MultiNode {
if( fun.cfg(i).fun()==fun ) // Check for linked call input inside "fun"
{ addDep(fun); return -1; } // No, but can lose the recursive edge
- // Things that might improve, so not now a candidate
+ // Things that might improve, but not now a candidate
Node fptr = call.fptr();
// Constant Function is being called, and its not-null
if( !(fptr._type instanceof TypeFunPtr tfp && tfp.notNull() && tfp.isConstant()) )
@@ -181,16 +185,16 @@ public class CallEndNode extends CFGNode implements MultiNode {
// Encouraged inlining because small size and constructor.
int maxSize = fun.inlineLimit();
// A large parse can leave only a tiny initializer. Recognize that
- // without repeatedly walking the whole body while it shrinks.
+ // and await cleanup to shrink the body.
if( fun._approxUIDs >= maxSize ) {
if( !smallBody(fun) ) return -1;
- } else if( fun.body().cardinality() >= maxSize )
+ } else if( fun.body().cardinality() >= maxSize ) // Expensive size check
return -1; // Try later (if cleanup makes the real body small enough)
// Can be cloned but not trivial
if( fun.nIns() > 2 ) { addDep(fun); return 2; }
- // A candidate right now
+ // A trivial candidate right now
return 1;
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/FunNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/FunNode.java
index b74f9e6b..1e2ee10a 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/FunNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/FunNode.java
@@ -252,15 +252,20 @@ public class FunNode extends RegionNode {
// Build the function body set
public BitSet body() {
- // Reverse up (stop to start) CFG only, collect bitmap.
+ // Iter drains ordinary peeps between inlines; body discovery precedes
+ // this inline's folding, so function boundaries are stable.
+ assert !_folding;
+ // Forward control includes paths that never reach Return.
BitSet cfgs = new BitSet();
- cfgs.set(_nid);
- walkUp(ret(),cfgs );
+ walkCFG(this,cfgs);
+ cfgs.set(ret()._nid);
// Top down (start to stop) all flavors. CFG limit to bitmap.
// If data use bottoms out in wrong CFG, returns false - but tries all outputs.
// If any output hits an in-CFG use (e.g. phi), then keep node.
BitSet body = new BitSet();
+ // The function owns its Return even when it has no executable exit.
+ body.set(ret()._nid);
BitSet visit = new BitSet();
int old;
do {
@@ -271,13 +276,15 @@ public class FunNode extends RegionNode {
return body;
}
- private static void walkUp(CFGNode n, BitSet cfgs) {
+ private static void walkCFG(CFGNode n, BitSet cfgs) {
if( cfgs.get(n._nid) ) return;
cfgs.set(n._nid);
- if( n instanceof RegionNode r )
- for( int i=1; i<n.nIns(); i++ )
- walkUp(n.cfg(i),cfgs);
- else walkUp(n.cfg0(),cfgs);
+ if( n instanceof ReturnNode ) return;
+ for( Node use : n._outputs ) {
+ if( !(use instanceof CFGNode cfg) || cfg instanceof FunNode ) continue;
+ if( cfg instanceof RegionNode ? cfg._inputs.find(n)>0 : cfg.in(0)==n )
+ walkCFG(cfg,cfgs);
+ }
}
private static boolean walkDown( Node n, BitSet cfgs, BitSet body, BitSet visit ) {
@@ -285,12 +292,12 @@ public class FunNode extends RegionNode {
if( visit.get(n._nid) ) return body.get(n._nid);
visit.set(n._nid);
// Visit self as CFG outside the function
- if( n instanceof CFGNode && !cfgs.get(n._nid) && unfolded( n ) )
+ if( n instanceof CFGNode && !cfgs.get(n._nid) )
return false;
// Pretend the NewNode is a CFG, so its projections stay with it in loops
if( n instanceof NewNode nnn ) cfgs.set(n._nid);
// Visit n.cfg() outside of function
- if( n.in(0)!=null && !cfgs.get(n.in(0)._nid) && unfolded( n.in( 0 ) ) )
+ if( n.in(0)!=null && !cfgs.get(n.in(0)._nid) )
return false;
// Phis inside the function must have their body flag set BEFORE
// recursion walks around a loop and finds them again.
@@ -308,15 +315,6 @@ public class FunNode extends RegionNode {
return in;
}
- // A CFG is folding, and so is basically Data
- private static boolean unfolded( Node cfg) {
- if( cfg instanceof CallNode call && call.cend().folding() ) return false;
- if( cfg instanceof CallEndNode cend && cend.folding() ) return false;
- if( cfg instanceof FunNode fun && fun._folding ) return false;
- if( cfg instanceof ReturnNode ret && ret._fun._folding ) return false;
- return true;
- }
-
// Clone function body. Give function a new FIDX.
FunNode copyBody() {
// Build the function body BitSet
@@ -326,8 +324,10 @@ public class FunNode extends RegionNode {
IdentityHashMap<Node,Node> map = new IdentityHashMap<>();
BitSet visit = CodeGen.CODE.visit();
bodyCopy( visit, body, map, this );
+ bodyCopy( visit, body, map, _ret ); // May be disconnected from executable control.
visit.clear();
bodyEdge( visit, body, map, this );
+ bodyEdge( visit, body, map, _ret );
visit.clear();
assert map.size()==body.cardinality();