diff options
Diffstat (limited to 'chapter25/src/main/java/com/seaofnodes')
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(); |
