diff options
Diffstat (limited to 'chapter25/src/main/java/com/seaofnodes/simple/codegen')
6 files changed, 253 insertions, 232 deletions
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 96bdbb33..0150219e 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java @@ -1,176 +1,184 @@ package com.seaofnodes.simple.codegen; -import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.*; 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; } - } +// Search for Class Initialization Cycles. - 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; - } +// Done after Opto, so dead code does not make a cycle. + +// Build the inits-graph, a call-graph plus CLInits plus class parents. + +// (1) Scan all functions f, building _edges[f][]: the set of <cinit> fcns from +// CompUnits from CLInitNodes, and the set of fcn call targets in f and the +// class parent. These are all reported as fidxs (ints). +// +// (2) Walk the _edges graph using Tarjans SCC. Stop at cycles containing a +// <clint> (and continue with cycles NOT containing a <clinit>, which is +// ordinary recursive functions). +// +// (3) Report on <clinit> cycles +// +// +public class ClassInitDependencies { + private final CodeGen _code; + // Function FIDX -> callees, touched initializers, and parent initializer. + public final int[][] _edgess; + private final int[] _pres; + private final int[] _lows; + private final int[] _stk ; + private int _sp; + + + // 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(); + _pres = new int[code._linker._len]; + _lows = new int[code._linker._len]; + _stk = new int[code._linker._len]; + + // + BitSet visit = code.visit(); // Visit set per-fcn + Ary<CFGNode> work = new Ary<>(CFGNode.class); // Worklist per-fcn + IntHashMap iset = new IntHashMap(0); // dedup hash set per-fcn + _edgess = new int[code._linker._len][]; - private static String reason(FunNode fun, String what, Parser.Lexer loc) { - return fun._compunit._cname+" ("+fun.label()+"): "+what+ - (loc==null ? "" : " at "+loc.position()); + // For all functions, build the _edges array + for( FunNode fun : code._linker ) + if( fun!=null && !fun.isDead() ) + // Visit function and collect direct class touches and callees + _edgess[fun.sig().fidx()] = walk(code,fun,work,iset,visit); + visit.clear(); } - // 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); + // Return the outgoing init edge set for this function + private static int[] walk( CodeGen code, FunNode fun, Ary<CFGNode> work, IntHashMap iset, BitSet visit) { + // Walk function body, looking for outgoing init edges + assert iset .isEmpty(); // int hash set for de-dup + assert visit.isEmpty(); // needs a visit set, because loops + assert work .isEmpty(); // needs a worklist, because DAG, parallel explore paths + + // Children classes always dependent on parents + if( fun.isClz() && fun._compunit._par != null ) + iset.put(code.link(fun._compunit._par).sig().fidx(),1); + + work.push(fun); while( !work.isEmpty() ) { - CFGNode cfg=work.remove(); - if( seen.get(cfg._nid) || cfg._type.isHigh() ) continue; - seen.set(cfg._nid); + CFGNode cfg = work.pop(); + if( visit.get(cfg._nid) ) continue; // Been there, done that + visit.set(cfg._nid); + assert !cfg._type.isHigh(); + // Do not walk off end of function + if( cfg instanceof ReturnNode ) continue; + + // CLInit adds a class init requirement to this function + // (plus all parent classes) 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; + FunNode clz = code.link(init.cu()); // <clinit> referred to + iset.put(clz.sig().fidx(),1); // Add to edge set } - 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; + + // Calls union all their fptr fidxs into the callee set + if( cfg instanceof CallNode call && call.fptr()._type instanceof TypeFunPtr fp ) { + assert !XInt.isHigh(fp.fidxs()) && fp.nfcns()>0; // Call is alive with valid targets + // Union call targets into the callee set + for( int dstx = XInt.next(fp.fidxs(),0); dstx>=0; dstx=XInt.next(fp.fidxs(),dstx) ) + iset.put(dstx,1); } - if( cfg instanceof ReturnNode ) continue; + + // Forward recursion to walk the whole function 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); + // Do not walk into another function + if( !(use instanceof CFGNode cfg1) || use instanceof FunNode || use instanceof StopNode ) continue; + // Expect control flow edge is sensible + assert (use instanceof RegionNode r && r._inputs.find(cfg)>0) || use.in(0)==cfg; + if( !visit.get(cfg1._nid) ) // Small speed opt + work.add(cfg1); } } + + visit.clear(); + int[] edges = iset.isEmpty() ? null : iset.keySet(); + iset.clear(); + return edges; } - /** 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); - } + // Return an error string, or null for an acyclic class-init graph. + public String check() { + _pres[0] = 1; // Preorders stored in pres[0] and start at 1 + BitSet visit = _code.visit(); + + int idx = 0; + for( int fidx = 1; fidx < _edgess.length; fidx++ ) { + if( _pres[fidx] == 0 ) // Already visited + idx = scc(fidx); + if( idx != -1 ) break; // Found a clz cycle } - 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)); + visit.clear(); + + // Error report for a clz heading a cycle + if( idx != -1 ) { + SB sb = new SB().p("Cyclic class initialization: "); + String first = null; + for( int i=idx; i<_sp; i++ ) { + FunNode fun = _code.link(_stk[i]); + if( fun.isClz() ) { + if( first==null ) first = fun._compunit._cname; + sb.p(fun._compunit._cname).p(" -> "); + } } - 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; - } + sb.p(first); + visit.clear(); + return sb.toString(); + } - // 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(); + return null; // No errors } - 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)); + // Tarjan SCC + int scc( int fidx ) { + // Assign pre-order number + _lows[fidx] = _pres[fidx] = _pres[0]++; + _stk[_sp++] = fidx; + // walk outbound edges + if( _edgess[fidx]!=null ) { + for( int edge : _edgess[fidx] ) { + if( edge >= _pres.length ) continue; // Out of bounds happens when calling C fcns + if( _pres[edge] == 0 ) { // Not visited + int idx = scc(edge); + if( idx != -1 ) return idx; // Found a cycle + _lows[fidx] = Math.min(_lows[fidx],_lows[edge]); + } else if( _edgess[edge]!=null && onStk(edge) ) { + _lows[fidx] = Math.min(_lows[fidx],_pres[edge]); + } } - 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); + // Found a cycle + if( _lows[fidx] == _pres[fidx] ) { + int idx = find(fidx); + if( hasClzCycle(idx) ) + return idx; // Found a class cycle + // Pop stack, including fidx + _sp = idx; + } + return -1; // No cycle + } + + private int find(int e) { + for( int i=0; i<_sp; i++ ) if( _stk[i] == e ) return i; + return -1; + } + private boolean onStk(int e) { return find(e) != -1; } + + private boolean hasClzCycle(int idx) { + if( idx+1 == _sp ) return false; // Cycle of 1 does not count + for( int i=idx; i<_sp; i++ ) + if( _code.link(_stk[i]).isClz() ) + return true; + return false; } - /** 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 bc86dc6d..ff0be213 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java @@ -80,9 +80,14 @@ public class CodeGen { // Current Working Directory; default module base private final String _cwd; - // Compilation Units in this compile; one per source/object file + // Compilation Units used in this compile; typically one per source/object + // file, although you can nest structs and get more than one. These are all + // required to compile, and are expected to have their source timestamps + // ahead of their obj file. Some of these may entirely be dead or inlined + // in this CompUnit and thus not needed in any linking stage. All the + // alive ones (inlined away or not) participate in class init cycle checks. 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; @@ -376,6 +381,13 @@ public class CodeGen { _linker.setX(fidx,null); fun = null; } return fun; } + // Return the <clinit> for a CompUnit + public FunNode link( CompUnit cu ) { + for( FunNode fun : _linker ) + if( fun!=null && fun._compunit == cu && fun.isClz() ) + return fun; + return null; + } // Insert linker mapping from constant function signature to the function // being called. @@ -536,28 +548,27 @@ public class CodeGen { 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(); + // things from resolving types. The dependency graph is temporary. + String clzInitCycle = new ClassInitDependencies(this).check(); + if( clzInitCycle != null ) + throw Parser.error(clzInitCycle,null); - final Ary<Node> errs = new Ary<>(Node.class); + Ary<Node> errs = new Ary<>(Node.class); _stop.walk( n -> { - if( n.err() != null ) - errs.add(n); - return null; + if( n.err() != null ) + errs.add(n); + return null; }); - String unresolved = unresolvedTypeName(); _times[Phase.TypeCheck.ordinal()] = System.currentTimeMillis() - t0; + if( !errs.isEmpty() ) { // Prefer an originating error over a secondary error caused by a // BOTTOM input. If all errors are downstream-poisoned, retain // the old first-error behavior. Node min = null; for( Node n : errs ) - if( noBottomInputs(n) ) { - min = n; - break; - } + if( noBottomInputs(n) ) + { min = n; break; } if( min==null ) min = errs.at(0); for( Node n : errs ) @@ -573,12 +584,6 @@ public class CodeGen { if( _obs != null ) _obs.error(min, min.err().getMessage()); throw min.err(); } - if( unresolved != null ) { - var err = Parser.error("Unknown struct type '"+unresolved+"'",null); - if( _obs != null ) _obs.error(null, err.getMessage()); - throw err; - } - _classInitDeps.eraseChecks(); if( _obs != null ) _obs.phase("TypeCheck"); return this; } @@ -590,21 +595,9 @@ public class CodeGen { return true; } - // Forward-ref structs are open while parsing. They must all be resolved - // to closed structs before leaving type checking. - private String unresolvedTypeName() { - String[] unresolved = new String[1]; - _stop.walk(n -> { - TypeStruct ts = requiredStruct(n); - if( unresolved[0]==null && ts!=null && ts._fref ) - unresolved[0] = ts._name; - return null; - }); - return unresolved[0]; - } - public static TypeStruct requiredStruct(Node n) { - if( n instanceof NewNode alloc ) return alloc._ts; + // CNC: remove? cleanup? + if( n instanceof NewNode alloc ) return alloc._ts; if( n instanceof ConFldOffNode off ) return off._ts; if( n instanceof FldOffNode off && off.in(0)._type instanceof TypeMemPtr ptr ) return ptr._obj; return null; @@ -644,6 +637,13 @@ public class CodeGen { // turns them into external relocations. private void unlinkImports() { assert _phase.ordinal() <= Phase.Serialize.ordinal(); + // Imported function constants keep their symbolic identity, not an + // embedded copy of the other object's Return and body. + for( Node n : _start._outputs.asAry() ) + if( n instanceof FunPtrNode ptr && ptr.ret()!=null && !owns(ptr.fun()) ) { + ptr.liftType(ptr._type); + ptr.setDef(1,null); + } for( FunNode fun : _linker ) { if( fun==null || fun.isDead() || owns(fun) ) continue; @@ -651,6 +651,14 @@ public class CodeGen { if( fun.in(i) instanceof CallNode call ) call.unlink(fun,i--); } + for( int i=0; i<_stop.nIns(); i++ ) { + StopCUNode stop=(StopCUNode)_stop.in(i); + CompUnit cu=_compunits.get(stop.start()._fname); + if( cu._src==null ) _stop.delDef(i--); + } + // The whole-object wrappers now summarize only the emitted units. + _stop._type=_stop.compute(); + _start._type=_start.compute(); //_iter.iterate(this); } @@ -1028,9 +1036,10 @@ public class CodeGen { // Map from symbol string to ELFReader; most lookups // will miss and no need to unpack ElfReader types _externSymbols.putIfAbsent(elf._strs[1],elf); - for( String str : elf._strs ) - if( str.startsWith(Parser.CLZ) ) - _externSymbols.putIfAbsent(str,elf); + + // Publish definitions only; referenced classes belong to other objects. + for( String fname : elf._units ) + _externSymbols.putIfAbsent(Parser.addClzPrefix(fname.replace('/','.')),elf); break; default: throw Utils.TODO("should not reach here"); 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 6d84c955..544ca27e 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java @@ -32,23 +32,6 @@ public class CompUnit { // 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). public StartCUNode _start; @@ -141,16 +124,6 @@ public class CompUnit { code._stop.addDef(_stop); } - - 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/ElfReader.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfReader.java index 534bb10f..10720300 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfReader.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfReader.java @@ -30,6 +30,8 @@ public class ElfReader { public String[] _strs; // Dependent file names public String[] _deps; + // Compilation units defined by this object, independent of init analysis. + public String[] _units; // Only published symbol this file public TypeStruct _clz; // Canonical named types, distinct from optimized same-named graph states. 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 155066a4..bb123634 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java @@ -42,7 +42,6 @@ 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; } @@ -88,12 +87,6 @@ public abstract class ParseAll { // Load all pre-compiled CompUnits for( CompUnit cu : NEEDS_LOAD ) loadCompUnit(code,cu); - if( !NEEDS_LOAD.isEmpty() ) - code._stop.walk(n -> { - if( n instanceof CallNode ) - code.add(n); - return null; - }); // Many types will have been declared cyclically either internally or // across multiple compilation units. @@ -146,6 +139,9 @@ public abstract class ParseAll { // Adds sort by NIDs, which just got shuffled if( n instanceof AddNode add ) code.add(add); + // All imported bodies are loaded; link them before Iter checks types. + if( n instanceof CallNode call && !NEEDS_LOAD.isEmpty() ) + code.add(call).idealize(); return null; } ); @@ -196,6 +192,8 @@ public abstract class ParseAll { CompUnit xcunit = findRequiredType(code,cunit,typeName); if( xcunit != null ) cunit.addDep(xcunit); + else + assert false : "expected to find required type"; } catch( IOException ioe ) { throw new RuntimeException(ioe); } @@ -219,16 +217,22 @@ public abstract class ParseAll { // Load IR after parsing if( NEEDS_LOAD.find(cunit) != -1 ) return; // Already on the to-be-loaded list? + for( CompUnit loaded : NEEDS_LOAD ) + if( loaded._obj.equals(cunit._obj) ) return; // Another class in the same object. try { NEEDS_LOAD.add(cunit); // Load dependent classes ElfReader elf = ElfReader.load(cunit._obj,cunit); elf.loadPublicSymbols(); + // Register bundled definitions before resolving dependencies or IR. + for( String fname : elf._units ) + makeCUnit(code,null,fname,cunit._obj); for( String fname : elf._deps ) { // Check CompUnit for being up to date - CompUnit sub = makeCUnit(code,null,fname,null/*depObj(code,cunit,fname)*/); + CompUnit sub = code._compunits.get(fname); + if( sub==null ) sub=makeCUnit(code,null,fname,depObj(code,cunit,fname)); // If not seen before or out of date, parse the new nested cunit - if( sub._clz == null && sub._par == null ) + if( sub._clz == null && WORK.find(sub)==-1 && NEEDS_LOAD.find(sub)==-1 ) WORK.add(sub); } } catch( IOException ioe ) { @@ -257,7 +261,8 @@ public abstract class ParseAll { // Extract published symbols and put them in the internal tables; // cross-references will pick them up there. ElfReader elf = ElfReader.load(cunit._obj,cunit); - cunit._clz = elf.loadSimple(code); + elf.loadSimple(code); + cunit._clz = (TypeStruct)Parser.TYPES.get(Parser.addClzPrefix(cunit._cname)); StartNode lstart = (StartNode)elf._nodes.at(0); StopNode lstop = ( StopNode)elf._nodes.last(); @@ -283,8 +288,8 @@ public abstract class ParseAll { // Add the new top-level loaded class Parser.TYPES.put(cunit._clz._name,cunit._clz); Field inst = cunit._clz.field(cunit._name); - if( inst != null ) // Also the instance type, if a constructor exists - Parser.TYPES.put(cunit._cname, ((TypeMem)((TypeFunPtr)inst._t)._ret)._t); + if( inst != null && inst._t instanceof TypeFunPtr tfp && tfp._ret instanceof TypeMem mem ) + Parser.TYPES.put(cunit._cname,mem._t); } // Symbol not found in source code (or we would not be here). @@ -382,11 +387,8 @@ public abstract class ParseAll { throw new RuntimeException(ioe); } ElfReader typedElf = ElfReader.load(cunit._obj,cunit); - TypeStruct clz = typedElf == null ? null : typedElf.loadPublicTypes(code); - if( clz != null ) - Parser.TYPES.put(clz._name,clz); - else - clz = (TypeStruct)Parser.TYPES.get(clzName); + if( typedElf != null ) typedElf.loadPublicTypes(code); + TypeStruct clz = (TypeStruct)Parser.TYPES.get(clzName); if( clz == null ) Parser.TYPES.put(clzName,clz = TypeStruct.make(clzName,true)); cunit._clz = clz; 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 ce546318..1806be24 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Serialize.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Serialize.java @@ -21,6 +21,12 @@ abstract public class Serialize { xdeps.add(cudep._fname); } String[] deps = xdeps.toArray(new String[xdeps.size()]); + Arrays.sort(deps); + Ary<String> owned = new Ary<>(String.class); + for( CompUnit cu : code._compunits.values() ) + if( cu._src!=null ) owned.add(cu._fname); + String[] units = owned.asAry(); + Arrays.sort(units); // Top-level clazz being reported String topName = code._srcName == null ? "Test" : code._srcName; @@ -37,14 +43,14 @@ abstract public class Serialize { TypeStruct[] pubTypes = named.values().toArray(new TypeStruct[0]); // Compress into bytes - BAOS baos = write(nodes, clz, pubTypes, deps, code._aliases, code._fidxs, code._rpcs, code._externFunc); + BAOS baos = write(nodes, clz, pubTypes, deps, units, code._aliases, code._fidxs, code._rpcs, code._externFunc); // --- Expensive bijection assert if( false ) { // Inflate into POJOs; renumbers everything ElfReader elf = new ElfReader(new BAOS(baos.toByteArray())); readAll(code,elf, code._aliases, code._fidxs, code._rpcs); - BAOS baos2 = write(elf._nodes,elf._clz,elf._pubTypes,elf._deps, code._aliases, code._fidxs, code._rpcs, code._externFunc); + BAOS baos2 = write(elf._nodes,elf._clz,elf._pubTypes,elf._deps,elf._units, code._aliases, code._fidxs, code._rpcs, code._externFunc); // Bi-jection for( int i=0; i<baos.size(); i++ ) @@ -58,7 +64,7 @@ abstract public class Serialize { } // -------------------------------------------------- - static BAOS write(Ary<Node> nodes, TypeStruct clz, TypeStruct[] pubTypes, String[] depobjs, GlobalBits aliases, GlobalBits fidxs, GlobalBits rpcs, HashMap<Integer,String> externFunc) { + static BAOS write(Ary<Node> nodes, TypeStruct clz, TypeStruct[] pubTypes, String[] depobjs, String[] units, GlobalBits aliases, GlobalBits fidxs, GlobalBits rpcs, HashMap<Integer,String> externFunc) { // Initialize the mapping from bits/tags to types Type.TAGOFFS(); @@ -85,6 +91,12 @@ abstract public class Serialize { if( n instanceof ParmNode parm ) parm._declaredType.gather(types); if( n instanceof TypeNode con ) con._con .gather(types); } + // A defined class remains importable even when its initializer body dies. + for( TypeStruct ts : pubTypes ) + if( Parser.startsClzPrefix(ts._name) && + Arrays.binarySearch(units,ts._name.substring(Parser.CLZ.length()).replace('.','/'))>=0 ) + ts.gather(types); + // Only record canonical declarations already reachable from this // object's graph. Pulling unrelated parser types into this closure // can include open or partially built types from other units. @@ -106,6 +118,7 @@ abstract public class Serialize { if( depobjs != null ) for( String fname : depobjs ) gather(strs,fname); + for( String fname : units ) gather(strs,fname); // Count strings from all types for( Type t : atypes ) { if( t instanceof Field fld ) gather(strs,fld._fname); @@ -139,6 +152,10 @@ abstract public class Serialize { for( String s : astrs ) baos.packedS(s); + // Defined units are loader metadata, not initialization dependencies. + baos.packed4(units.length); + for( String fname : units ) baos.packed4(strs.get(fname)); + // B.2 - Write the local->global mappings aliases.packed(baos,strs); fidxs .packed(baos,strs); @@ -232,6 +249,9 @@ abstract public class Serialize { elf._deps = new String[ndependents]; for( int i=0; i<ndependents; i++ ) elf._deps[i] = strs[1+i+1]; + elf._units = new String[bais.packed4()]; + for( int i=0; i<elf._units.length; i++ ) + elf._units[i] = strs[bais.packed4()]; } // -------------------------------------------------- @@ -260,6 +280,9 @@ abstract public class Serialize { elf._deps = new String[ndependents]; for( int i = 0; i < ndependents; i++ ) elf._deps[i] = strs[1 + i + 1]; + elf._units = new String[bais.packed4()]; + for( int i=0; i<elf._units.length; i++ ) + elf._units[i] = strs[bais.packed4()]; } // B.2 - Read the local->global mappings. Further down we will map @@ -409,6 +432,10 @@ abstract public class Serialize { 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); @@ -506,7 +533,7 @@ abstract public class Serialize { // All the CompUnits for( CompUnit cu : code._compunits.values() ) { // Whole CompUnits have no users, went dead - if( cu._start != null && !cu._start.isDead() ) { + if( cu._src!=null && cu._start != null && !cu._start.isDead() ) { nodes.add(cu._start); // A memory-free, nonreturning unit can lose its memory projection. ProjNode mem=cu._start.proj(1); |
