From ce3b04603d7af995004291d9a7eb668f313e6772 Mon Sep 17 00:00:00 2001 From: Cliff Click Date: Fri, 6 Nov 2026 16:06:02 -0800 Subject: Cleanup public escapes Drop "frozen" public escapes, and recompute in Start/Stop so Opto gets a precise escape set. Rename XInt.free -> XInt.alloc Fix some String tests to not escape by default --- .../main/java/com/seaofnodes/simple/type/XInt.java | 13 +- .../main/java/com/seaofnodes/simple/type/XInt.java | 13 +- chapter28a/README.md | 27 +++- .../main/java/com/seaofnodes/simple/Parser.java | 9 +- .../simple/codegen/ClassInitDependencies.java | 27 +++- .../com/seaofnodes/simple/codegen/CodeGen.java | 74 --------- .../com/seaofnodes/simple/codegen/LocalIds.java | 14 ++ .../java/com/seaofnodes/simple/codegen/Opto.java | 49 +++--- .../com/seaofnodes/simple/codegen/ParseAll.java | 4 - .../com/seaofnodes/simple/node/CLInitNode.java | 24 ++- .../com/seaofnodes/simple/node/CallEndNode.java | 20 ++- .../java/com/seaofnodes/simple/node/CallNode.java | 16 +- .../com/seaofnodes/simple/node/ConvertNode.java | 7 +- .../com/seaofnodes/simple/node/EscapeNode.java | 8 +- .../java/com/seaofnodes/simple/node/FunNode.java | 11 ++ .../java/com/seaofnodes/simple/node/LoadNode.java | 67 ++++++-- .../java/com/seaofnodes/simple/node/ParmNode.java | 12 +- .../java/com/seaofnodes/simple/node/StartNode.java | 19 ++- .../com/seaofnodes/simple/node/StopCUNode.java | 58 ++----- .../java/com/seaofnodes/simple/node/StoreNode.java | 9 +- .../com/seaofnodes/simple/type/TypeFunPtr.java | 4 + .../java/com/seaofnodes/simple/type/TypeMem.java | 19 +++ .../com/seaofnodes/simple/type/TypeStruct.java | 12 +- .../main/java/com/seaofnodes/simple/type/XInt.java | 13 +- .../java/com/seaofnodes/simple/Chapter10Test.java | 20 +-- .../java/com/seaofnodes/simple/Chapter12Test.java | 2 +- .../java/com/seaofnodes/simple/Chapter18Test.java | 2 +- .../java/com/seaofnodes/simple/Chapter19Test.java | 2 +- .../java/com/seaofnodes/simple/Chapter20Test.java | 17 ++- .../java/com/seaofnodes/simple/Chapter21Test.java | 31 ++-- .../simple/ClassInitDependenciesTest.java | 22 +++ .../java/com/seaofnodes/simple/EscapeFlowTest.java | 112 ++++++++++++++ .../java/com/seaofnodes/simple/ModulesTest.java | 36 ++++- chapter28b/README.md | 8 + .../main/java/com/seaofnodes/simple/Parser.java | 9 +- .../com/seaofnodes/simple/codegen/CodeGen.java | 62 -------- .../com/seaofnodes/simple/codegen/GlobalBits.java | 13 ++ .../java/com/seaofnodes/simple/codegen/Iter.java | 3 +- .../java/com/seaofnodes/simple/codegen/Opto.java | 38 ++--- .../com/seaofnodes/simple/codegen/ParseAll.java | 4 - .../com/seaofnodes/simple/node/CLInitNode.java | 27 +++- .../com/seaofnodes/simple/node/CallEndNode.java | 15 +- .../java/com/seaofnodes/simple/node/CallNode.java | 20 +-- .../com/seaofnodes/simple/node/ConvertNode.java | 7 +- .../com/seaofnodes/simple/node/EscapeNode.java | 8 +- .../java/com/seaofnodes/simple/node/FunNode.java | 11 ++ .../seaofnodes/simple/node/GlobalEscapeNode.java | 11 +- .../java/com/seaofnodes/simple/node/LoadNode.java | 67 ++++++-- .../java/com/seaofnodes/simple/node/ParmNode.java | 12 +- .../java/com/seaofnodes/simple/node/StartNode.java | 13 ++ .../com/seaofnodes/simple/node/StopCUNode.java | 52 ++----- .../java/com/seaofnodes/simple/node/StoreNode.java | 9 +- .../com/seaofnodes/simple/node/SummaryNode.java | 6 +- .../main/java/com/seaofnodes/simple/type/Type.java | 5 +- .../com/seaofnodes/simple/type/TypeFunPtr.java | 4 + .../java/com/seaofnodes/simple/type/TypeMem.java | 19 +++ .../com/seaofnodes/simple/type/TypeStruct.java | 12 +- .../main/java/com/seaofnodes/simple/type/XInt.java | 17 ++- .../java/com/seaofnodes/simple/Chapter10Test.java | 20 +-- .../java/com/seaofnodes/simple/Chapter12Test.java | 2 +- .../java/com/seaofnodes/simple/Chapter18Test.java | 2 +- .../java/com/seaofnodes/simple/Chapter19Test.java | 2 +- .../java/com/seaofnodes/simple/Chapter20Test.java | 22 +-- .../java/com/seaofnodes/simple/Chapter21Test.java | 38 ++--- .../java/com/seaofnodes/simple/Chapter25Test.java | 65 ++++++-- .../simple/ClassInitDependenciesTest.java | 22 +++ .../java/com/seaofnodes/simple/EscapeFlowTest.java | 112 ++++++++++++++ chapter29/README.md | 6 +- chapter29/docs/deferred-construction.md | 5 + chapter29/docs/escape-flow.md | 147 ++++++++++++++++++ .../com/seaofnodes/simple/codegen/CodeGen.java | 69 --------- .../com/seaofnodes/simple/codegen/GlobalBits.java | 13 ++ .../java/com/seaofnodes/simple/codegen/Opto.java | 24 +-- .../com/seaofnodes/simple/codegen/ParseAll.java | 5 - .../com/seaofnodes/simple/node/CLInitNode.java | 20 ++- .../com/seaofnodes/simple/node/CallEndNode.java | 15 +- .../java/com/seaofnodes/simple/node/CallNode.java | 4 +- .../com/seaofnodes/simple/node/ConvertNode.java | 11 +- .../com/seaofnodes/simple/node/EscapeNode.java | 2 +- .../java/com/seaofnodes/simple/node/FunNode.java | 11 ++ .../java/com/seaofnodes/simple/node/LoadNode.java | 46 ++++-- .../java/com/seaofnodes/simple/node/ParmNode.java | 7 +- .../java/com/seaofnodes/simple/node/StartNode.java | 13 ++ .../com/seaofnodes/simple/node/StopCUNode.java | 2 +- .../java/com/seaofnodes/simple/node/StoreNode.java | 7 +- .../com/seaofnodes/simple/node/SummaryNode.java | 6 +- .../com/seaofnodes/simple/parser/Functions.java | 2 +- .../java/com/seaofnodes/simple/parser/Types.java | 4 +- .../main/java/com/seaofnodes/simple/type/Type.java | 5 +- .../com/seaofnodes/simple/type/TypeFunPtr.java | 4 + .../java/com/seaofnodes/simple/type/TypeMem.java | 13 +- .../com/seaofnodes/simple/type/TypeStruct.java | 2 +- .../main/java/com/seaofnodes/simple/type/XInt.java | 17 ++- .../java/com/seaofnodes/simple/Chapter20Test.java | 23 +-- .../java/com/seaofnodes/simple/Chapter21Test.java | 38 ++--- .../java/com/seaofnodes/simple/Chapter25Test.java | 66 ++++++-- .../simple/ClassInitDependenciesTest.java | 22 +++ .../java/com/seaofnodes/simple/EscapeFlowTest.java | 112 ++++++++++++++ docs/roadmap.md | 11 +- skills/chapter25-codex-notes.md | 168 ++++++++++++++++++--- 100 files changed, 1691 insertions(+), 719 deletions(-) create mode 100644 chapter28a/src/test/java/com/seaofnodes/simple/EscapeFlowTest.java create mode 100644 chapter28b/src/test/java/com/seaofnodes/simple/EscapeFlowTest.java create mode 100644 chapter29/docs/escape-flow.md create mode 100644 chapter29/src/test/java/com/seaofnodes/simple/EscapeFlowTest.java diff --git a/chapter26/src/main/java/com/seaofnodes/simple/type/XInt.java b/chapter26/src/main/java/com/seaofnodes/simple/type/XInt.java index 17fc4bd3..a69783be 100644 --- a/chapter26/src/main/java/com/seaofnodes/simple/type/XInt.java +++ b/chapter26/src/main/java/com/seaofnodes/simple/type/XInt.java @@ -92,7 +92,7 @@ public abstract class XInt { int len = Math.max(xs.length,idx+1); if( (x&31)==31 && idx+1==len ) len++; - int[] ys = free(len); + int[] ys = alloc(len); System.arraycopy(xs,1,ys,1,xs.length-1); ys[idx] |= mask(x); return intern(ys); @@ -112,7 +112,7 @@ public abstract class XInt { len--; // Replace with trimmed if( len < xs.length ) { - var ys = free(len); + var ys = alloc(len); System.arraycopy(xs,1,ys,1,len-1); xs = _free(xs,ys); } @@ -146,8 +146,9 @@ public abstract class XInt { FREES.setX(len, xss = new Ary<>(int[].class)); return xss; } - // Pull free array from free list - static int[] free(int len) { + // Allocate a zeroed mutable array, reusing the free list. + // Pass it to intern after filling; do not retain a mutable reference. + public static int[] alloc(int len) { int[] xs = _xss(len).pop(); if( xs==null ) { NEWS++; xs = new int[len]; } @@ -176,7 +177,7 @@ public abstract class XInt { if( isX ) return xs; if( isY ) return ys; // Get a free one - int[] ms = free(max); + int[] ms = alloc(max); for( int i=1; i(int[].class)); return xss; } - // Pull free array from free list - static int[] free(int len) { + // Allocate a zeroed mutable array, reusing the free list. + // Pass it to intern after filling; do not retain a mutable reference. + public static int[] alloc(int len) { int[] xs = _xss(len).pop(); if( xs==null ) { NEWS++; xs = new int[len]; } @@ -176,7 +177,7 @@ public abstract class XInt { if( isX ) return xs; if( isY ) return ys; // Get a free one - int[] ms = free(max); + int[] ms = alloc(max); for( int i=1; i +known code -> Stop -> Start, lets us model escaping local functions and aliases +into the outside, and then seeing them again coming in as callbacks, or +arguments or side-effects. Tracking this exactly lets us know when we have +"escaped" a otherwise private function to the World, which might then call it +directly, or pass it back to us as an argument, or store it for later. + +`LocalIds` tracks foreign identities, excluding the local IDs. Calls and +escaping values add local identities as SCCP converges. Unknown callback +parameters and results read this state. A class function's hidden receiver +defaults to its class singleton, while its signature keeps the generic hidden +slot used by the calling convention. + +Public constructor fields expose the allocator. Initializers and constructor +helpers that accept private memory stay internal. Class-init checks read their +initializer's memory effects, and unknown callbacks participate in class-init +cycle detection through the functions that have escaped. + + ## Emitting code and data After cleanup by `FinishCalls`, the live code and data from the discovered diff --git a/chapter28a/src/main/java/com/seaofnodes/simple/Parser.java b/chapter28a/src/main/java/com/seaofnodes/simple/Parser.java index 6bd02363..3b5a8ce7 100644 --- a/chapter28a/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter28a/src/main/java/com/seaofnodes/simple/Parser.java @@ -291,7 +291,7 @@ public class Parser { for( int i=0; i",alloc,_code.alias(_ref._cname),true)); TYPES.put(clzName,ts); // Insert a field in the containing class with the nested class type. - // This is basically sugar for "val CLZNAME = " + // This is basically sugar for "val CLZNAME = " // and is a nicer version of updateSelfAsFieldsDiscovered. - _scope.define(typeName, sig, true, new FunPtrNode(sig,_code._start,ret).peephole(), loc()); + _scope.define(typeName, sig, true, new FunPtrNode(sig,_code._start,_code.link(alloc).ret()).peephole(), loc()); require("}"); return require(_code.ZERO,";"); diff --git a/chapter28a/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java b/chapter28a/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java index 6c70b71d..3784b6b8 100644 --- a/chapter28a/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java +++ b/chapter28a/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java @@ -29,6 +29,7 @@ public class ClassInitDependencies { private final int[] _lows; private final int[] _stk ; private int _sp; + private int _pre; // Build once; tests can inspect the graph independently of cycle checking. @@ -50,6 +51,14 @@ public class ClassInitDependencies { if( fun!=null && !fun.isDead() ) // Visit function and collect direct class touches and callees _edgess[fun.sig().fidx()] = walk(code,fun,work,iset,visit,null); + // An unknown callback can call any escaped local function. Zero is + // this outside-world vertex, not an executable function identity. + int[] escaped = ((TypeTuple)code._stop._type).mem()._escFs; + AryInt callees = new AryInt(); + for( FunNode fun : code._linker ) + if( fun!=null && !fun.isDead() && !fun.isClz() && XInt.bit(escaped,fun.sig().fidx()) ) + callees.push(fun.sig().fidx()); + _edgess[0] = callees.isEmpty() ? null : callees.asAry(); visit.clear(); } @@ -84,9 +93,12 @@ public class ClassInitDependencies { // 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 + if( XInt.isHigh(fp.fidxs()) ) { + iset.put(0,1); + if( sites!=null && sites.atX(0)==null ) sites.setX(0,call._loc); + } // Union call targets into the callee set - for( int dstx = XInt.next(fp.fidxs(),0); dstx>=0; dstx=XInt.next(fp.fidxs(),dstx) ) { + else for( int dstx = XInt.next(fp.fidxs(),0); dstx>=0; dstx=XInt.next(fp.fidxs(),dstx) ) { iset.put(dstx,1); if( sites!=null && sites.atX(dstx)==null ) sites.setX(dstx,call._loc); } @@ -113,10 +125,10 @@ public class ClassInitDependencies { public String check() { Arrays.fill(_pres,0); _sp = 0; - _pres[0] = 1; // Preorders stored in pres[0] and start at 1 + _pre = 1; int idx = -1; - for( int fidx = 1; fidx < _edgess.length; fidx++ ) + for( int fidx = 0; fidx < _edgess.length; fidx++ ) if( _pres[fidx] == 0 && // Already visited (idx = scc(fidx)) != -1 ) // And found a cycle return report(idx); // Report cycle @@ -143,7 +155,7 @@ public class ClassInitDependencies { FunNode fun = _code.link(src); Arrays.fill(sites._es,null); sites.clear(); - walk(_code,fun,work,iset,visit,sites); + if( fun!=null ) walk(_code,fun,work,iset,visit,sites); if( _edgess[src]!=null ) for( int dst : _edgess[src] ) if( members.get(dst) ) { @@ -151,7 +163,7 @@ public class ClassInitDependencies { Parser.Lexer loc = sites.atX(dst); if( loc!=null && loc._cu!=null ) sb.p('\n').p(SourceError.format(loc._cu,loc.pos(),"cyclic class initialization")); - else if( fun.isClz() && fun._compunit._par==_code.link(dst)._compunit ) + else if( fun!=null && fun.isClz() && dst!=0 && fun._compunit._par==_code.link(dst)._compunit ) sb.p("\n parent initialization requirement"); } } @@ -159,6 +171,7 @@ public class ClassInitDependencies { } private String name(int fidx) { + if( fidx==0 ) return "unknown call"; FunNode fun = _code.link(fidx); String owner=fun._compunit._cname; if( fun.isClz() ) return owner; @@ -169,7 +182,7 @@ public class ClassInitDependencies { // Tarjan SCC int scc( int fidx ) { // Assign pre-order number - _lows[fidx] = _pres[fidx] = _pres[0]++; + _lows[fidx] = _pres[fidx] = _pre++; _stk[_sp++] = fidx; // walk outbound edges if( _edgess[fidx]!=null ) { diff --git a/chapter28a/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java b/chapter28a/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java index 8b3857a1..a3de2ec8 100644 --- a/chapter28a/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java +++ b/chapter28a/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java @@ -86,85 +86,11 @@ public class CodeGen { return null; } - // True when fidx is published through a public class or instance field. - public boolean publicFIDX(int fidx) { - if( fidx < LocalIds.RESERVED ) return false; - // A public class initializer is itself an external root; unlike - // constructors and methods, it is not reached through a class field. - FunNode fun = _linker.atX(fidx); - if( fun != null && fun.isClz() && fun.isPublic() ) - return true; - // 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) ) { - 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 publicFieldFIDX(TypeStruct clz, int fidx) { - boolean hasCtor = clz.field("") != null; - for( Field fld : clz._fields ) - if( fld._fname.charAt(0)!='_' && fld._t instanceof TypeFunPtr tfp && - // With a user constructor, class:N.N is the private . - !(hasCtor && clz.hiddenInit(fld)) && - XInt.bit(tfp._fidxs,fidx) ) - return true; - return false; - } - public static boolean hasUserConstructor(TypeStruct self) { Type t = Parser.TYPES.get(Parser.addClzPrefix(self._name)); return t instanceof TypeStruct clz && clz.field("") != null; } - // A forward reference gets a class object and constructor signature so - // parsing can proceed, but it has no callable definition yet. - private static boolean resolvedClass(TypeStruct clz) { - Type t = Parser.TYPES.get(clz._name.substring(Parser.CLZ.length())); - return !(t instanceof TypeStruct ts) || !ts._fref; - } - - private int[] _publicFIDXs, _publicAliases; - - /** - * Freeze the externally visible portion of the program after ParseAll has - * loaded every compilation unit and closed/upgraded all types. Later - * optimization can remove linker entries and create inline-only fidxs; - * neither operation changes this interface. - */ - void freezePublicInterface() { - assert _publicFIDXs==null && _publicAliases==null; - int[] fidxs = XInt.EMPTY; - for( int fidx=LocalIds.RESERVED; fidx<_linker._len; fidx++ ) - if( publicFIDX(fidx) ) - fidxs = XInt.make(fidxs,fidx); - int[] aliases = XInt.EMPTY; - for( Type t : Parser.TYPES.values() ) - if( t instanceof TypeStruct clz && - Parser.startsClzPrefix(clz._name) && - Parser.isPublicClassName(clz._name) && - resolvedClass(clz) ) { - boolean hasCtor = clz.field("") != null; - for( Field fld : clz._fields ) - if( fld._fname.charAt(0)!='_' && fld._t!=Type.TOP && - !(hasCtor && clz.hiddenInit(fld)) ) - aliases = XInt.make(aliases,fld._alias); - } - _publicFIDXs = fidxs; - _publicAliases = aliases; - } - - public int[] publicFIDXs() { assert _publicFIDXs !=null; return _publicFIDXs; } - public int[] publicAliases(){ assert _publicAliases!=null; return _publicAliases; } - public boolean publicInterfaceFrozen() { return _publicFIDXs != null; } - // --------------------------- // Very common nodes, cached here diff --git a/chapter28a/src/main/java/com/seaofnodes/simple/codegen/LocalIds.java b/chapter28a/src/main/java/com/seaofnodes/simple/codegen/LocalIds.java index 462248dd..29157631 100644 --- a/chapter28a/src/main/java/com/seaofnodes/simple/codegen/LocalIds.java +++ b/chapter28a/src/main/java/com/seaofnodes/simple/codegen/LocalIds.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple.codegen; +import com.seaofnodes.simple.type.XInt; import java.util.HashMap; import java.util.Arrays; @@ -11,6 +12,19 @@ public class LocalIds { private final com.seaofnodes.simple.util.Ary _owners=new com.seaofnodes.simple.util.Ary<>(String.class); private final HashMap _slots=new HashMap<>(); + // All registered Simple identities are local; native storage and the + // unallocated tail belong to the outside world. Reserved IDs are excluded. + public int[] foreign() { + int[] foreign = XInt.alloc((_next>>>5)+2); + Arrays.fill(foreign,1,foreign.length,-1); + for( int i = 0; i<_next; i++ ) { + String owner = _owners.atX(i); + if( owner==null || !owner.startsWith("C:") ) + foreign[(i>>>5)+1] &= ~(1< oldTypes = new Ary<>(Type.class); WorkList deps = new WorkList<>(); WorkList changes = new WorkList<>(); Ary modes = new Ary<>(Node.class); - resetToTop(code,oldTypes,modes,deps); + Ary returns = new Ary<>(ReturnNode.class); + resetToTop(code,oldTypes,modes,deps,returns); unlinkStart(code); // OPTIMISTIC INTERPROCEDURAL SCCP @@ -48,17 +52,21 @@ abstract public class Opto { Node dep; while( (dep=deps.pop())!=null ) code.add(dep); moveChangesToWorklist(code, oldTypes, changes); + for( ReturnNode ret : returns ) code.add(ret.unkeep()); } // Reset Types before running the worklist algorithm - private static void resetToTop(CodeGen code, Ary oldTypes, Ary modes, WorkList deps ) { + private static void resetToTop(CodeGen code, Ary oldTypes, Ary modes, WorkList deps, Ary returns ) { code._start.walk( x -> { + // Keep bodies alive while SCCP rebuilds their callers. + if( x instanceof ReturnNode ret ) returns.add(ret.keep()); // Opto ends Parm.inProgress(), enabling constant folding even // when a parameter's type was already constant before SCCP. if( x instanceof ParmNode && x._type.isHighOrConst() ) deps.push(x); // SCCP adds call edges and default arguments, not numeric operations. if( x instanceof ModeNode mode && mode.mode()==0 ) modes.push(x); - oldTypes.setX(x._nid,x._type); // Compare the final SCCP type with Iter's type + // Change snapshot, not a bound: Opto adds foreign identities. + oldTypes.setX(x._nid,x._type); x._type = Type.TOP; // Reset all Nodes to TOP code._iter.add(x); // Visit everybody at least once; most Nodes produce better than TOP return null; @@ -66,9 +74,8 @@ abstract public class Opto { } - // If wholeWorld, remove the Start inputs to functions - they are not - // called unless they get linked (hence go dead and can be removed). If - // not wholeWorld, keep only public methods (and not the std lib). + // Rediscover ordinary functions through calls and escapes. This chapter + // retains initializer entries until opaque CLInit obligations are lowered. private static void unlinkStart(CodeGen code) { // Keep the Start & Start.ProjMem alive even as we remove all uses for( CompUnit cu : code._compunits.values() ) { @@ -81,20 +88,17 @@ abstract public class Opto { for( int i=0; i=0; fidx = XInt.next(fidxs,fidx) ) { // unlinkStart deliberately leaves uncalled functions in a @@ -136,10 +137,9 @@ abstract public class Opto { TypeMem tmem = (TypeMem)tt._types[1]; int[] fidxs = tmem._escFs; if( XInt.isHigh(fidxs) ) { - // A scalar BOTTOM can flow into memory while compiling an error - // program. It exposes all public functions, not private fields. + // Cofinite sets include foreign identities but can exclude locals. for( FunNode fun : code._linker ) - if( fun != null && code.publicFIDX(fun.sig().fidx()) ) + if( fun != null && !fun.isDead() && XInt.bit(fidxs,fun.sig().fidx()) ) linkStart(code,fun); return; } @@ -151,7 +151,7 @@ abstract public class Opto { } // Function is now reachable by unknown callers through Start. The - // default inputs must therefore use conservative declared types. + // memory and callback inputs read Start; hidden self uses its owning class. private static void linkStart( CodeGen code, FunNode fun ) { assert !fun.isDead(); StartCUNode start = fun._compunit._start; @@ -163,7 +163,9 @@ abstract public class Opto { code._iter.add(fun); for( Node p : fun._outputs ) if( p instanceof ParmNode parm ) { - Node defalt = parm._idx==1 ? start.proj(1) : ConstantNode.seed(parm._declaredType).peephole(); + Type t = parm._idx<2 ? parm._declaredType : fun.defaultArg(parm._idx-2); + Node defalt = parm._idx==1 || parm._declaredType instanceof TypeFunPtr + ? start.proj(1) : ConstantNode.seed(t).peephole(); parm.insertDef(1,defalt); code.add(parm); } @@ -185,8 +187,6 @@ abstract public class Opto { if( nval!=oldTypes.atX(n._nid) || n.isUnlocked() ) changes.push(n); if( oval == nval ) continue; assert oval.isa(nval) : "Non-monotonic SCCP: "+n.getClass().getSimpleName()+"#"+n._nid+" "+n+" old="+oval+" new="+nval; // Types start high and always fall - Type pesiVal = oldTypes.atX(n._nid); - assert pesiVal==null || nval.isa(pesiVal) : "SCCP below pessimistic type: "+n.getClass().getSimpleName()+"#"+n._nid+" "+n+" pessimistic="+pesiVal+" new="+nval; // Never fall worse than the pessimistic pass n._type = nval; // Now we have a series of stanzas where we lazily create the Call @@ -267,7 +267,6 @@ abstract public class Opto { if( n.isDead() ) continue; assert n.compute() == n._type; // Hit the fixed point. Type old = oldTypes.atX(n._nid); - assert old==null || n._type.isa(old); if( n._type!=old || n.isUnlocked() ) { code.add(n); code.addAll(n._inputs); diff --git a/chapter28a/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java b/chapter28a/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java index 6ae4e4bb..58983663 100644 --- a/chapter28a/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java +++ b/chapter28a/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java @@ -138,10 +138,6 @@ public abstract class ParseAll { return null; } ); - // The complete public interface is now known. Keep this independent - // of later graph/linker liveness so SCCP computations remain local and - // monotonic. - code.freezePublicInterface(); } // Parse one Simple source code file. Add all the FRefs produced to the worklist. diff --git a/chapter28a/src/main/java/com/seaofnodes/simple/node/CLInitNode.java b/chapter28a/src/main/java/com/seaofnodes/simple/node/CLInitNode.java index 0a0de3cf..0482484a 100644 --- a/chapter28a/src/main/java/com/seaofnodes/simple/node/CLInitNode.java +++ b/chapter28a/src/main/java/com/seaofnodes/simple/node/CLInitNode.java @@ -67,16 +67,27 @@ public class CLInitNode extends CFGNode implements MultiNode { public boolean bypass() { return instance() || ready()._type==TypeInteger.TRUE; } @Override public Type compute() { - if( in(0)._type.isHigh() || ptr()._type.isHigh() ) + if( in(0)._type.isHigh() || ptr()._type.isHigh() || + (ptr()._type instanceof TypeMemPtr tmp && tmp._obj.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 : + // Before Opto, the initializer has conservative effects. Once linked, + // its Return supplies them through the ordinary dependency worklist. + // Meet with incoming memory for the path that was already initialized. + Type bulk = ready()._type.isHigh() || mem()._type.isHigh() ? TypeMem.TOP : ready()._type==TypeInteger.TRUE ? mem()._type : TypeMem.BOT; + if( bulk==TypeMem.BOT && ready()._type!=TypeInteger.TRUE && + CodeGen.CODE._phase.ordinal()>=CodeGen.Phase.Opto.ordinal() ) { + FunNode target = CodeGen.CODE.link(cu()); + if( target!=null && target.ret()!=null ) { + Type result = addDepForwards(target.ret())._type; + Type effects = result instanceof TypeTuple tt ? tt.mem() : result.isHigh() ? TypeMem.TOP : TypeMem.BOT; + bulk = mem()._type.meet(effects); + } + } return TypeTuple.make(in(0)._type,bulk,slice); } @@ -132,9 +143,10 @@ public class CLInitNode extends CFGNode implements MultiNode { Node fptr = new FunPtrNode(target.sig(),CodeGen.CODE._start,target.ret()).peephole(); CallNode call = CodeGen.CODE.add(new CallNode(_loc,cold,memory,ptr(),arg,fptr).init()); CallEndNode end = new CallEndNode(call,TypeRPC.constant(CodeGen.CODE.rpc(_loc._cu._cname))); - // This is a lowered call, after interprocedural analysis. Do not add - // new parameter flows to a body already optimized by SCCP. CodeGen.CODE.add(end.init()); + call.link(target); + // Publish the linked effects before constructing memory projections. + end.setType(end.compute()); Node called = CodeGen.CODE.add(new CProjNode(end,0,ScopeNode.CTRL).init()); Node after = CodeGen.CODE.add(new ProjNode(end,1,ScopeNode.MEM0).init()); RegionNode region = CodeGen.CODE.add(new RegionNode(_loc,null,done,called).init()); diff --git a/chapter28a/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/chapter28a/src/main/java/com/seaofnodes/simple/node/CallEndNode.java index 4422265f..3be22c09 100644 --- a/chapter28a/src/main/java/com/seaofnodes/simple/node/CallEndNode.java +++ b/chapter28a/src/main/java/com/seaofnodes/simple/node/CallEndNode.java @@ -73,6 +73,16 @@ public class CallEndNode extends CFGNode implements MultiNode { return tt.makeFrom(2,tt.ret()); } + // The outside continuation reads Start directly: Call can remain + // CONTROL while its possible returned identities change. + if( XInt.isHigh(tfp.fidxs()) ) { + TypeMem outside = CodeGen.CODE._start.outside(this).escapes(call.exposed(this)); + TypeTuple state = TypeTuple.make(Type.CONTROL,outside,outside.outside(tfp.ret())); + for( int i = 1; i