diff options
Diffstat (limited to 'chapter28a/src')
30 files changed, 473 insertions, 247 deletions
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<ids.length; i++ ) { Type t = sig.arg(i); // Args take a default input if called from Start/external-world - _scope.define(ids[i], t, (i==0 && ids[0]=="self") || (finals!=null && finals.get(i)), new ParmNode(ids[i],i+2,t,fun,ConstantNode.seed(t).peephole()).peephole(), loc); + _scope.define(ids[i], t, (i==0 && ids[0]=="self") || (finals!=null && finals.get(i)), new ParmNode(ids[i],i+2,t,fun,ConstantNode.seed(fun.defaultArg(i)).peephole()).peephole(), loc); } if( isCtor && !isInit ) { @@ -1333,13 +1333,16 @@ public class Parser { : TypeStruct.make(clzName,false,fld,ctorFld); if( flag!=null ) ts=ts.add(flag); TypeFunPtr alloc = makeAllocator((TypeStruct)TYPES.get(fullName),ret,ctorFld); + // Publish the source-callable allocator, not the private-memory helper. + sig = alloc; + ts = ts.replace(fld.makeFrom(alloc)); ts = ts.add(Field.make("<new>",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 = <init>" + // This is basically sugar for "val CLZNAME = <new>" // 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("<ctor>") != 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 <init>. - !(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("<ctor>") != 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("<ctor>") != 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<String> _owners=new com.seaofnodes.simple.util.Ary<>(String.class); private final HashMap<String,int[]> _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<<i); + } + return XInt.intern(foreign); + } + public int next(String owner) { return next(owner,-1); } public int next(String owner, int slot) { int[] ids=_slots.get(owner); diff --git a/chapter28a/src/main/java/com/seaofnodes/simple/codegen/Opto.java b/chapter28a/src/main/java/com/seaofnodes/simple/codegen/Opto.java index 6370bf5b..e1282883 100644 --- a/chapter28a/src/main/java/com/seaofnodes/simple/codegen/Opto.java +++ b/chapter28a/src/main/java/com/seaofnodes/simple/codegen/Opto.java @@ -25,12 +25,16 @@ abstract public class Opto { // get a fairly precise set of Call Graph edges between CallNodes and // FunNodes. public static void opto(CodeGen code) { + int[] foreign = code._fidxs.foreign(); + for( int fidx : code._externFunc.keySet() ) foreign = XInt.make(foreign,fidx); + code._start._foreign = TypeMem.START.makeFrom(foreign,code._aliases.foreign()); // Reset Types before running the worklist algorithm Ary<Type> oldTypes = new Ary<>(Type.class); WorkList<Node> deps = new WorkList<>(); WorkList<Node> changes = new WorkList<>(); Ary<Node> modes = new Ary<>(Node.class); - resetToTop(code,oldTypes,modes,deps); + Ary<ReturnNode> 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<Type> oldTypes, Ary<Node> modes, WorkList<Node> deps ) { + private static void resetToTop(CodeGen code, Ary<Type> oldTypes, Ary<Node> modes, WorkList<Node> deps, Ary<ReturnNode> 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<stop.nIns(); i++ ) { ReturnNode ret = (ReturnNode)stop.in(i); FunNode fun = ret.fun(); - // Start is a semantic unknown-caller edge. Public functions - // keep it; closed-world functions must be reached by a - // concrete Call edge discovered during SCCP. Initializers, + // Start is a semantic unknown-caller edge. Ordinary functions + // are discovered through calls and escaped values. Initializers, // including private ones, are called by opaque CLInit // obligations whose Call edges are introduced only in Lower. if( !fun.isClz() && - !code.publicFIDX(fun.sig().fidx()) && fun.in(1) instanceof StartNode ) { fun.removeDeadPath(1); // Unhook from Stop without treating the ReturnNode as // DEAD. It stays in limbo until Opto links when a caller // is discovered (or not, and the function is really dead). - stop._inputs.del(i--); - ret.delUse(stop); + stop.delDef(i--); } // Unlink the existing conservative call linkages. for( int j=1; j<fun.nIns(); j++ ) @@ -109,11 +113,8 @@ abstract public class Opto { private static void linkCG(CodeGen code, TypeFunPtr tfp, CallNode call) { if( tfp.nargs() != call.nargs() ) return; // Error calls hit this int[] fidxs = tfp.fidxs(); - // An unresolved forward call can still carry the infinite, inferred - // function set into Opto. There is no finite call graph to link yet; - // type checking will report the unresolved reference afterwards. This - // can yet be valid if the call is eventually dead. - // `if( false ) frefCall(args);` + // Infinite targets use the outside continuation. Escaped local + // callbacks are reached through Start, and their effects feed Stop. if( XInt.isHigh(fidxs) ) return; for( int fidx = XInt.next(fidxs,0); fidx >=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<nIns(); i++ ) + state = (TypeTuple)state.meet(in(i)._type); + return state; + } + // Here, if I can figure out I've linked *all* callers, then I can meet // across the linked returns and join with the function return type. @@ -93,7 +103,11 @@ public class CallEndNode extends CFGNode implements MultiNode { bound = bound.meet(fun==null ? tfp.ret() : addDep(fun).sig().ret()); } } - boolean hasExtern = externs != 0; + if( externs!=0 ) { + assert externs==tfp.nfcns(); + TypeMem outside = CodeGen.CODE._start.outside(this).escapes(call.exposed(this)); + return TypeTuple.make(Type.CONTROL,outside,outside.outside(tfp.ret())); + } // If we have an error-call, e.g. wrong args, then we never link. // Pre-Opto this looks like a missing target fcn, and we assume it will @@ -111,9 +125,7 @@ public class CallEndNode extends CFGNode implements MultiNode { } // A linked function for every concrete function - TypeTuple state = hasExtern - ? TypeTuple.make(Type.CONTROL,call.exposed(this),tfp.ret()) - : TypeTuple.STATE.dual(); + TypeTuple state = TypeTuple.STATE.dual(); for( int i=1; i<nIns(); i++ ) state = (TypeTuple)state.meet(in(i)._type); // A reachable call whose presently linked exits are all unreachable diff --git a/chapter28a/src/main/java/com/seaofnodes/simple/node/CallNode.java b/chapter28a/src/main/java/com/seaofnodes/simple/node/CallNode.java index f791225a..1a8da215 100644 --- a/chapter28a/src/main/java/com/seaofnodes/simple/node/CallNode.java +++ b/chapter28a/src/main/java/com/seaofnodes/simple/node/CallNode.java @@ -69,10 +69,18 @@ public class CallNode extends CFGNode { TypeMem exposed(Node dep) { dep.addDepForwards(this); dep.addDepForwards(fptr()); - if( _type!=Type.CONTROL || !external() ) return TypeMem.START; - Type input=dep.addDepForwards(mem())._type; - TypeMem escaped=input instanceof TypeMem m ? m : input.isHigh() ? TypeMem.TOP : TypeMem.BOT; - for( int i=2; i<nIns()-1; i++ ) escaped=escaped.escapes(dep.addDepForwards(arg(i))._type); + if( _type.isHigh() ) return TypeMem.START; + // A broad scalar target can refine to a function pointer during Iter. + // It cannot yet promise that the call exposes nothing. + if( _type!=Type.CONTROL ) return TypeMem.BOT; + if( !(fptr()._type instanceof TypeFunPtr) ) + return fptr()._type.isHigh() ? TypeMem.START : TypeMem.BOT; + if( !external() ) return TypeMem.START; + Type input = dep.addDepForwards(mem())._type; + TypeMem escaped = input instanceof TypeMem m ? m : input.isHigh() ? TypeMem.TOP : TypeMem.BOT; + TypeMem outside = CodeGen.CODE._start.outside(dep); + for( int i = 2; i<nIns()-1; i++ ) + escaped = escaped.escapesFrom(dep.addDepForwards(arg(i))._type,outside); return TypeMem.make(1,Type.BOTTOM,false,false,false,escaped._escFs,escaped._escAs); } diff --git a/chapter28a/src/main/java/com/seaofnodes/simple/node/ConvertNode.java b/chapter28a/src/main/java/com/seaofnodes/simple/node/ConvertNode.java index 078c50ec..f5803fe7 100644 --- a/chapter28a/src/main/java/com/seaofnodes/simple/node/ConvertNode.java +++ b/chapter28a/src/main/java/com/seaofnodes/simple/node/ConvertNode.java @@ -36,7 +36,12 @@ public class ConvertNode extends Node { @Override public Type compute() { Type src = val()._type; if( src.isHigh() ) return Type.TOP; - if( !(_dst instanceof TypeMemPtr) && src.isa(_dst) ) return src; + if( _dst instanceof TypeMemPtr ptr ) { + // Keep non-nullness before the identity rewrite; shape and access + // still follow the declaration so compute stays monotone. + return src instanceof TypeMemPtr actual && actual.notNull() ? ptr.nonZero() : ptr; + } + if( src.isa(_dst) ) return src; return _dst; } diff --git a/chapter28a/src/main/java/com/seaofnodes/simple/node/EscapeNode.java b/chapter28a/src/main/java/com/seaofnodes/simple/node/EscapeNode.java index f7518184..9561eba8 100644 --- a/chapter28a/src/main/java/com/seaofnodes/simple/node/EscapeNode.java +++ b/chapter28a/src/main/java/com/seaofnodes/simple/node/EscapeNode.java @@ -1,6 +1,7 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.*; import com.seaofnodes.simple.util.*; @@ -68,7 +69,12 @@ public class EscapeNode extends TypeNode { Type t = tpriv.meet(tpub).join(fld._t); int[] fidxes = XInt.meet(mpriv._escFs, mpub._escFs); int[] aliases = XInt.meet(mpriv._escAs, mpub._escAs); - return TypeMem.make(fld._alias, t, false, false, fld._final, fidxes, aliases).escapesFrom(t); + // Keep escapes from the actual private value before narrowing it to + // the declaration. Selecting its Store from a MemMerge must preserve + // this result, including Scalar.BOT's unknown pointer escapes. + TypeMem defaults = CodeGen.CODE._phase.ordinal()<CodeGen.Phase.Opto.ordinal() ? TypeMem.BOT : mpub; + return TypeMem.make(fld._alias, t, false, false, fld._final, fidxes, aliases) + .escapesFrom(tpriv,defaults).escapesFrom(t,defaults); } @Override public Node idealize() { diff --git a/chapter28a/src/main/java/com/seaofnodes/simple/node/FunNode.java b/chapter28a/src/main/java/com/seaofnodes/simple/node/FunNode.java index 15626a07..c5954f23 100644 --- a/chapter28a/src/main/java/com/seaofnodes/simple/node/FunNode.java +++ b/chapter28a/src/main/java/com/seaofnodes/simple/node/FunNode.java @@ -71,6 +71,17 @@ public class FunNode extends RegionNode { // Signature can improve over time public TypeFunPtr sig() { return _sig; } + // A class function's hidden receiver defaults to its owning singleton. + // Keep PTR in the signature: it marks the hidden ABI slot and permits + // uniform call syntax, including callbacks forwarded by another class. + public Type defaultArg(int idx) { + Type t = sig().arg(idx); + if( idx!=0 || t!=TypePtr.PTR ) return t; + TypeStruct clz = (TypeStruct)Parser.TYPES.get(Parser.addClzPrefix(_compunit._cname)); + assert clz!=null; + return TypeMemPtr.make((byte)2,clz,true); + } + public void setSig( TypeFunPtr sig ) { assert sig.isa(_sig) || resolvesForwardSelf(sig,_sig); if( _sig != sig ) { diff --git a/chapter28a/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter28a/src/main/java/com/seaofnodes/simple/node/LoadNode.java index e1a495ad..14ac8c37 100644 --- a/chapter28a/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter28a/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -87,6 +87,14 @@ public class LoadNode extends MemOpNode { if( mem._alias==_alias ) t = t.join(mem._t); + if( !mem._one ) t = mem.outside(t); + // Store forwarding is also a value fact during SCCP. Waiting for the + // later rewrite can turn a known non-null stored pointer into a + // nullable declaration, then BOTTOM at the next dereference. + if( t instanceof TypeMemPtr && pfld._alias>1 && + find(mem(),ptr(),null,null,t) instanceof StoreNode st ) + t = addDepForwards(st.val())._type; + // A deeply read-only base produces a deeply read-only value. The // generic declared field type must not cast this information away. if( ptr0.isFinal() || readonly ) @@ -205,30 +213,50 @@ public class LoadNode extends MemOpNode { // the unchanged loop memory, or a merge whose every arm folds. Null fails. // Register dependencies so a later pointer/offset rewrite retries the query. private Node find(Node mem, Node ptr, Node stop, BitSet visit) { - if( ptr instanceof ReadOnlyNode ro ) ptr=ro.in(1); - Field fld; - boolean external = ptr._type instanceof TypeMemPtr tmp && - (fld=tmp._obj.field(_name))!=null && fld._extern; + return find(mem,ptr,stop,visit,_type); + } + + private Node find(Node mem, Node ptr, Node stop, BitSet visit, Type expected) { + ptr = address(ptr); + Field fld = ptr._type instanceof TypeMemPtr owner ? owner._obj.field(_name) : null; + int alias = fld==null ? _alias : fld._alias; + boolean external = fld!=null && fld._extern; while( mem!=null ) { addDep(mem); if( mem==stop ) return mem; switch( mem ) { - case MemMergeNode merge: mem=merge.alias(_alias); break; + case MemMergeNode merge: mem=merge.alias(alias); break; case StoreNode st: // Alias 1 is still unresolved, not a proven different field. if( st._alias==1 ) return null; addDep(st.ptr()); - if( _alias==st._alias && (ptr==st.ptr() || ptr==st.nnptr()) && off()==st.off() ) { + if( alias==st._alias && (ptr==address(st.ptr()) || ptr==address(st.nnptr())) && + ((!_name.equals("[]") && _name.equals(st._name)) || off()==st.off() || + (addDep(off())._type.isConstant() && off()._type==addDep(st.off())._type)) ) { + // Forwarding must preserve the pointer type already seen + // by users: completion, read-only view, and recursive shape. + // Open declarations can disagree until Opto upgrades them. + if( expected instanceof TypeMemPtr ) { + Type value = addDep(st.val())._type; + if( ptr()._type.isFinal() || declaredType().isFinal() ) value = value.makeRO(); + if( !value.isa(expected) ) return null; + } // A full-width load cannot narrow the forwarded value. if( declaredType() instanceof TypeInteger ti && ti.log_size()==3 && - !addDep(st.val())._type.isa(_type) ) return null; + !addDep(st.val())._type.isa(expected) ) return null; return st; } - if( (external || _alias==st._alias) && (external || !neverAlias(ptr,st.ptr())) && + if( (external || alias==st._alias) && (external || !neverAlias(ptr,address(st.ptr()))) && !addDep(off())._type.join(addDep(st.off())._type).isHigh() ) return null; mem=st.mem(); break; case ProjNode proj: + if( proj._idx==1 && proj.in(0) instanceof CLInitNode init ) { + Node prior = init.pcopy(1,this); + if( prior==null ) return null; + mem = prior; + break; + } // New's memory is private; public memory is joined at Escape. if( external || !(proj.in(0) instanceof NewNode nnn) ) return null; if( declaredType()==Type.BOTTOM ) return null; @@ -236,8 +264,8 @@ public class LoadNode extends MemOpNode { case EscapeNode esc: if( external ) return null; addDep(esc.self()); - if( esc.self()==ptr ) { mem=esc.priv(); break; } - if( neverAlias(ptr,esc.self()) ) { mem=esc.pub(); break; } + if( address(esc.self())==ptr ) { mem=esc.priv(); break; } + if( neverAlias(ptr,address(esc.self())) ) { mem=esc.pub(); break; } return null; case MemPhiNode phi: addDep(phi.region()); @@ -278,6 +306,25 @@ public class LoadNode extends MemOpNode { return ld.peephole(); } + // Type views preserve the address. Keep the actual Load pointer qualified + // for compute/err; only alias comparisons see through these views. + private Node address(Node ptr) { + // A pointer Load with a proven reaching Store denotes the stored + // address even before the later peephole replaces the Load itself. + if( ptr instanceof LoadNode ld && addDepForwards(ld)._type instanceof TypeMemPtr && + ld.ptr()._type instanceof TypeMemPtr base && base.notNull() && ld.err()==null && + ld.find(ld.mem(),ld.ptr(),null,null) instanceof StoreNode st ) + ptr = addDepForwards(st.val()); + if( ptr instanceof ConvertNode conv && addDep(conv.val())._type instanceof TypeMemPtr src && + src.isa(addDep(conv)._type) && src.accessISA(conv._type) ) + ptr = conv.val(); + while( ptr instanceof ReadOnlyNode ) { + addDep(ptr); + ptr = ptr.in(1); + } + return ptr; + } + private static boolean neverAlias( Node ptr1, Node ptr2 ) { return ptr1.in(0) != ptr2.in(0) && // Unrelated allocations diff --git a/chapter28a/src/main/java/com/seaofnodes/simple/node/ParmNode.java b/chapter28a/src/main/java/com/seaofnodes/simple/node/ParmNode.java index 316efb93..b92c93bb 100644 --- a/chapter28a/src/main/java/com/seaofnodes/simple/node/ParmNode.java +++ b/chapter28a/src/main/java/com/seaofnodes/simple/node/ParmNode.java @@ -50,7 +50,7 @@ public class ParmNode extends PhiNode { @Override public Type compute() { if( !(region() instanceof RegionNode r) ) - return region()._type==Type.XCONTROL || region()._type==Type.TOP ? (_type instanceof TypeMem ? TypeMem.TOP : Type.TOP) : _type; + return region()._type==Type.XCONTROL || region()._type==Type.TOP ? (_declaredType instanceof TypeMem ? TypeMem.TOP : Type.TOP) : _type; // During parsing Phis have to be computed type pessimistically. if( r.inProgress() || in(nIns()-1)==null ) return _declaredType; @@ -63,7 +63,12 @@ public class ParmNode extends PhiNode { if( ctrl != Type.XCONTROL && ctrl != Type.TOP ) { if( in(i)._type==Type.BOTTOM ) return Type.BOTTOM; - t = t.meet(in(i)._type); + Type arg = in(i)._type; + // The unknown-caller callback input is Start's memory + // projection, interpreted using this parameter's signature. + if( i==1 && r.in(i) instanceof StartNode && _idx>1 && arg instanceof TypeMem outside ) + arg = outside.outside(_declaredType); + t = t.meet(arg); } } // Recursion is a loop in disguise. A Parm merges the first argument @@ -73,7 +78,8 @@ public class ParmNode extends PhiNode { // widening this Parm alone cannot stop f(x-1)+1 from growing its result. if( t instanceof TypeInteger ti && !ti.isHigh() && !ti.isConstant() ) t = ti.same_but_slightly_wider_than(); - return t.join(_declaredType); + t = t.join(_declaredType); + return t==Type.TOP && _declaredType instanceof TypeMem ? TypeMem.TOP : t; } @Override diff --git a/chapter28a/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter28a/src/main/java/com/seaofnodes/simple/node/StartNode.java index 3c605e30..283da7df 100644 --- a/chapter28a/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ b/chapter28a/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -26,6 +26,9 @@ import java.util.HashSet; public class StartNode extends LoopNode implements MultiNode { final Type _arg; + // Immutable foreign identity seed, installed with Opto's reset. Actual + // local escapes arrive through Stop, not through a frozen public list. + public TypeMem _foreign = TypeMem.START; public StartNode(StartNode start, StopNode stop, Type arg) { super(null,start,stop); _arg = arg; } public StartNode(StartNode start) { super(start); _arg = start==null ? null : start._arg; } @@ -55,16 +58,10 @@ public class StartNode extends LoopNode implements MultiNode { } boolean escapedFIDX(int fidx) { - // Public class fields are callable by the unknown external world - // regardless of the current SCCP approximation of Start memory. - if( CodeGen.CODE.publicFIDX(fidx) ) return true; if( !(_type instanceof TypeTuple tt) ) return !_type.isHigh(); // TOP never escapes, BOTTOM always escapes TypeMem tmem = (TypeMem)tt._types[1]; - if( tmem == TypeMem.BOT ) return false; if( tmem == TypeMem.TOP ) return false; - if( XInt.isHigh(tmem._escFs) ) - return false; return XInt.bit(tmem._escFs,fidx); } @@ -73,12 +70,22 @@ public class StartNode extends LoopNode implements MultiNode { TypeMem tmem = stop._type instanceof TypeTuple tt ? tt.mem() : stop._type.isHigh() ? TypeMem.START : TypeMem.BOT; + if( !(this instanceof StartCUNode) ) tmem = tmem.escapes(_foreign); + else if( in(0) instanceof StartNode start ) tmem = tmem.escapes(start.outside(this)); assert tmem._alias==1 && !tmem._one; return TypeTuple.make(Type.CONTROL,tmem,_arg); } @Override public Node idealize() { return null; } + // Consumers register against the value they read, even when their Call's + // control remains unchanged as more identities escape. + public TypeMem outside(Node dep) { + if( CodeGen.CODE._phase==null || CodeGen.CODE._phase.ordinal()<CodeGen.Phase.Opto.ordinal() ) return TypeMem.BOT; + Type t = dep.addDepForwards(this)._type; + return t instanceof TypeTuple tt ? tt.mem() : t.isHigh() ? _foreign : TypeMem.BOT; + } + // No immediate dominator, and idepth==0 @Override public int idepth() { return 0; } @Override public CFGNode idom(Node dep) { return null; } diff --git a/chapter28a/src/main/java/com/seaofnodes/simple/node/StopCUNode.java b/chapter28a/src/main/java/com/seaofnodes/simple/node/StopCUNode.java index 53c2325c..ae7abbb4 100644 --- a/chapter28a/src/main/java/com/seaofnodes/simple/node/StopCUNode.java +++ b/chapter28a/src/main/java/com/seaofnodes/simple/node/StopCUNode.java @@ -12,6 +12,9 @@ public class StopCUNode extends StopNode { public StopCUNode(StopNode stop) { super(stop); } + // Each compilation unit owns its Stop by identity. + @Override public boolean eq(Node n) { return false; } + public StartCUNode start() { for( Node use : _outputs ) if( use instanceof StartCUNode start ) @@ -27,17 +30,10 @@ public class StopCUNode extends StopNode { return TypeTuple.STATE; StartCUNode start = start(); - // Memory start from all returns + // Recompute from inputs, not our previous result. Unknown values carry + // FULL escape sets: restricting them to public names would lose private + // objects/functions which a later load-forwarding rewrite can reveal. TypeMem xmem = TypeMem.START; - // Escape summaries are monotone discovery facts. In Opto, a Return - // can widen from a precise set to FULL; the public-only normalization - // below must not forget private bits already discovered precisely from - // that Return on an earlier SCCP step. - if( CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Opto.ordinal() && - CodeGen.CODE._phase.ordinal() <= CodeGen.Phase.Lower.ordinal() && - _type instanceof TypeTuple old && old.mem() instanceof TypeMem omem ) - xmem = xmem.makeFrom(XInt.meet(xmem._escFs,omem._escFs), - XInt.meet(xmem._escAs,omem._escAs)); for( Node def : _inputs ) { ReturnNode ret = (ReturnNode)def; @@ -48,10 +44,9 @@ public class StopCUNode extends StopNode { FunNode fun = ret.fun(); boolean noUserCtor = fun.isInstance() && !CodeGen.hasUserConstructor(((TypeMemPtr)fun.sig().arg(0))._obj); - // Instance <init> is an allocation helper, never an externally - // callable constructor when a user constructor exists. Otherwise - // the same no-arg <init> is the public constructor. - boolean pub = fun.isPublic() && (fun.isClz() || noUserCtor); + // Only entry initializers seed escape discovery. Constructors + // and methods are discovered through escaping class/instance fields. + boolean pub = fun.isClz() && (CodeGen.CODE.entryClinitName().equals(fun._name) || fun.isPublic()); TypeFunPtr sig = fun.sig(); // If public init, the function escapes and the related class escapes if( pub ) { @@ -72,48 +67,21 @@ public class StopCUNode extends StopNode { // here as its effects are not visible to the outside world. addDepForwards(start); // Check again if function escapes if( pub || start.escapedFIDX(sig.fidx()) ) { - // External callers already hold their object arguments, so - // writes through those arguments expose addresses too. - for( Node use : fun._outputs ) - if( use instanceof ParmNode p && p._idx>=2 ) - xmem=publicBottom(xmem.escapes(addDepForwards(p)._type)); if( tret.ctl().isHigh() ) continue; // Return not reachable // Capture (precisely?) all escaping pointer aliases and fidxs. // Escaped fidxs means the linked world can call that function; // escaped aliases means the linked world can R/W those aliases. - xmem = xmem.escapes(tret.ret()); - xmem = publicBottom(xmem); - xmem = (TypeMem)xmem.meet(publicBottom(tret.mem(),xmem)); + xmem = xmem.escapesFrom(tret.ret(),start.outside(this)); + xmem = (TypeMem)xmem.meet(tret.mem()); } } - // Native retention is shared by the whole program, including calls - // on paths with no Return. A class boundary must not hide it. + // Calls can retain arguments even on paths with no Return. for( CallNode call : CodeGen.CODE._calls ) - if( !call.isDead() ) xmem=publicBottom(xmem.escapes(call.exposed(this))); + if( !call.isDead() ) xmem = xmem.escapes(call.exposed(this)); assert xmem._alias==1 && !xmem._one; return TypeTuple.make(Type.CONTROL,xmem,Type.BOTTOM); } - // FULL escape sets arise from global BOTTOM. Across a compilation-unit - // boundary global BOTTOM exposes every public field, while retaining any - // private escapes already discovered precisely. - private TypeMem publicBottom(TypeMem mem) { return publicBottom(mem,mem); } - private TypeMem publicBottom(TypeMem mem, TypeMem prior) { - // The pessimistic Iter pass naturally lifts transient FULL inputs to - // precise finite sets. Replacing FULL early there would let a later - // input refinement add bits, reversing Iter's monotonic direction. - // This normalization is needed only for Opto's optimistic grand cycle. - if( CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal() || - CodeGen.CODE._phase.ordinal() > CodeGen.Phase.Lower.ordinal() ) return mem; - int[] escFs = XInt.isHigh(mem._escFs) - ? XInt.meet(prior._escFs,CodeGen.CODE.publicFIDXs()) - : mem._escFs; - int[] escAs = XInt.isHigh(mem._escAs) - ? XInt.meet(prior._escAs,CodeGen.CODE.publicAliases()) - : mem._escAs; - return escFs==mem._escFs && escAs==mem._escAs ? mem : mem.makeFrom(escFs,escAs); - } - @Override public Node idealize() { int len = nIns(); diff --git a/chapter28a/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter28a/src/main/java/com/seaofnodes/simple/node/StoreNode.java index bdd07c8d..8cad3259 100644 --- a/chapter28a/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter28a/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -76,6 +76,7 @@ public class StoreNode extends MemOpNode { Type decl = declaredType(); if( decl != Type.BOTTOM ) { assert decl != Type.TOP; val = val.join(decl); } + TypeMem outside = TypeMem.opaque(val) ? CodeGen.CODE._start.outside(this) : TypeMem.BOT; // A singleton class's final constant field lives in the static image. // Its precise memory slice is a constant too; ordinary peepholes can // replace this Store before scheduling or instruction selection. @@ -84,14 +85,16 @@ public class StoreNode extends MemOpNode { Field fld = ptr._obj.field(_name); if( _alias!=1 && ptr._one && Parser.startsClzPrefix(ptr._obj._name) && fld!=null && fld._final && decl.isConstant() ) - return TypeMem.make(_alias,val,true,true,true,mem._escFs,mem._escAs).escapesFrom(val); + // A constant slice contains only this value's escapes. Copying + // bulk escapes here would freeze a transient FULL into it. + return TypeMem.make(_alias,val,true,true,true,null,null).escapesFrom(val,outside); // Allocation uses a private TypeMem and nothing else does. This // memory is truly private; a temporary singleton until it escapes - // which is never does in a constructor. if( !_name.equals("[]") && mem._one ) // Just track the stored value - return TypeMem.make(_alias,val,true,false,_init,null,null).escapesFrom(val); + return TypeMem.make(_alias,val,true,false,_init,null,null).escapesFrom(val,outside); // Normal aliasing Store. assert mem._alias==1 || mem._alias==_alias; // Perfect aliasing @@ -106,7 +109,7 @@ public class StoreNode extends MemOpNode { // Bulk memory's final bit describes its conservative remainder, not // this newly precise slice. Slice finality comes from the field. boolean xfinal = fld != null && fld._final; - return TypeMem.make(_alias,t,mem._one,mem._clz,xfinal,mem._escFs,mem._escAs).escapesFrom(val); + return TypeMem.make(_alias,t,mem._one,mem._clz,xfinal,mem._escFs,mem._escAs).escapesFrom(val,outside); } @Override diff --git a/chapter28a/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java b/chapter28a/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java index 301fcc8b..2303af49 100644 --- a/chapter28a/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java +++ b/chapter28a/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java @@ -78,6 +78,10 @@ public class TypeFunPtr extends TypeNil { @Override TypeFunPtr makeFrom( byte nil ) { return nil ==_nil ? this : make( nil,_open,_sig,_ret, _fidxs); } public TypeFunPtr makeFrom( Type ret ) { return ret ==_ret ? this : make( _nil,_open,_sig, ret, _fidxs); } public TypeFunPtr makeFrom( int fidx ) { return make1(_nil,_open,_sig,_ret,fidx); } + public TypeFunPtr restrictFIDXs(int[] fidxs) { + int[] ids = XInt.dual(XInt.meet(XInt.dual(_fidxs),XInt.dual(fidxs))); + return ids==_fidxs ? this : make(_nil,_open,_sig,_ret,ids); + } public TypeFunPtr makeFrom( Type arg, int idx ) { // Alter named arg Type[] sig = _sig.clone(); diff --git a/chapter28a/src/main/java/com/seaofnodes/simple/type/TypeMem.java b/chapter28a/src/main/java/com/seaofnodes/simple/type/TypeMem.java index a0079eae..bd0585c8 100644 --- a/chapter28a/src/main/java/com/seaofnodes/simple/type/TypeMem.java +++ b/chapter28a/src/main/java/com/seaofnodes/simple/type/TypeMem.java @@ -90,10 +90,29 @@ public class TypeMem extends Type { public TypeMem escapesFrom(Type t) { return make(_alias,_t,_one,_clz,_final, escapeFIDX(_escFs,t), escapeAlias(_escAs,t)); } + // An opaque external object can expose the public interface and whatever + // private values have already escaped. It cannot invent access to other + // private definitions here. The caller supplies FULL until local forward + // types are resolved. Ordinary unknown values still require FULL. + public TypeMem escapesFrom(Type t, TypeMem defaults) { + if( opaque(t) ) + return makeFrom(XInt.meet(_escFs,defaults._escFs),XInt.meet(_escAs,defaults._escAs)); + return escapesFrom(t); + } + public static boolean opaque(Type t) { + TypeStruct ts = t instanceof TypeMemPtr ptr ? ptr._obj : t instanceof TypeStruct obj ? obj : null; + return ts!=null && ts._open && ts._fref && !ts.isAry(); + } public TypeMem escapesAliases(Type t) { return make(_alias,_t,_one,_clz,_final, _escFs, escapeAlias(_escAs,t)); } + // A value supplied by the outside world, bounded by its declared family. + // Foreign identities remain possible even when no local function escapes. + public Type outside(Type declared) { + return declared instanceof TypeFunPtr fun ? fun.restrictFIDXs(_escFs) : declared; + } + public static final TypeMem TOP = make(1, Type.TOP ,true ,true, true , XInt.EMPTY, XInt.EMPTY); public static final TypeMem BOT = make(1, Type.BOTTOM,false,false,true , XInt.FULL , XInt.FULL ); public static final TypeMem START= make(1,Type.BOTTOM,false,false,false, XInt.EMPTY, XInt.EMPTY); diff --git a/chapter28a/src/main/java/com/seaofnodes/simple/type/TypeStruct.java b/chapter28a/src/main/java/com/seaofnodes/simple/type/TypeStruct.java index 67169009..d6531be2 100644 --- a/chapter28a/src/main/java/com/seaofnodes/simple/type/TypeStruct.java +++ b/chapter28a/src/main/java/com/seaofnodes/simple/type/TypeStruct.java @@ -98,7 +98,7 @@ public class TypeStruct extends Type { maxAlias = Math.max(maxAlias,f._alias); int len = XInt.idx(maxAlias)+1; if( (maxAlias&31)==31 ) len++; - int[] xs = XInt.free(len); + int[] xs = XInt.alloc(len); for( Field f : _fields ) if( f._fname.charAt(0) != '_' && // Skip private fields. !hiddenInit(f) && // Skip private allocation helper. @@ -122,13 +122,11 @@ public class TypeStruct extends Type { return (_fidxs = xs); } - // With a user constructor, the same-named function field is the hidden - // default <init> allocation helper, not part of the public class surface. + // Initializer/constructor helpers receive private memory. Only the + // allocator has a source-callable signature. public boolean hiddenInit(Field fld) { - if( field("<ctor>") == null || !_name.startsWith("class:") ) return false; - int idx = _name.lastIndexOf('.'); - String name = _name.substring(idx<0 ? "class:".length() : idx+1); - return fld._fname.equals(name); + return fld._t instanceof TypeFunPtr tfp && tfp.nargs()>=2 && + tfp.arg(1) instanceof TypeMem; } // New open struct with no fields diff --git a/chapter28a/src/main/java/com/seaofnodes/simple/type/XInt.java b/chapter28a/src/main/java/com/seaofnodes/simple/type/XInt.java index 126a4e64..898ed97a 100644 --- a/chapter28a/src/main/java/com/seaofnodes/simple/type/XInt.java +++ b/chapter28a/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<min; i++ ) ms[i] = xs[i] | ys[i]; for( int i=min; i<ms.length; i++ ) { @@ -189,7 +190,7 @@ public abstract class XInt { // Dual: flip all the bits public static int[] dual(int[] xs) { - int[] ds = free(xs.length); + int[] ds = alloc(xs.length); for( int i=1; i<xs.length; i++ ) ds[i] = ~xs[i]; return intern(ds); diff --git a/chapter28a/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter28a/src/test/java/com/seaofnodes/simple/Chapter10Test.java index 39c7040e..04e66a6f 100644 --- a/chapter28a/src/test/java/com/seaofnodes/simple/Chapter10Test.java +++ b/chapter28a/src/test/java/com/seaofnodes/simple/Chapter10Test.java @@ -156,7 +156,7 @@ else return v; """); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("Stop[ return Test.Vector2D; return MEM[ 2:___ 3:.x=0; 4:.y=0;]; return Test.Vector2D; ]", code.print()); + assertEquals("Stop[ return Test.Vector2D; return Test.Vector2D; ]", code.print()); } @Test @@ -194,7 +194,7 @@ while (arg) { return bar.a; """); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("Stop[ return Phi(Loop,0,(Phi_a+2)); return MEM[ 2:___ 3:.a=0;]; return Test.Bar; ]", code.print()); + assertEquals("Stop[ return Phi(Loop,0,(Phi_a+2)); return Test.Bar; ]", code.print()); } @Test @@ -243,10 +243,10 @@ struct _Bar { int a; }; !_Bar? !bar = new _Bar; if (arg) bar = null; if( bar ) bar.a = 1; -return bar.a; +return bar; """); - code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("return Phi(Region,1,.a);", code.print()); + code.driver(CodeGen.Phase.TypeCheck); + assertEquals("return Phi(Region,null,Test._Bar);", code.print()); } @Test @@ -316,7 +316,7 @@ while( i.x < i.len ) { return sum; """); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("Stop[ return Phi(Loop,0,(Phi(Loop,0,(Phi_x+1))+Phi_sum)); return MEM[ 2:___ 3:.x=0; 4:.len=0;]; return Test.Iter; ]", code.print()); + assertEquals("Stop[ return Phi(Loop,0,(Phi(Loop,0,(Phi_x+1))+Phi_sum)); return Test.Iter; ]", code.print()); } @@ -335,7 +335,7 @@ while(arg) { return ret; """); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("Stop[ return Phi(Loop,Test.s0,Phi(Region,Test.s0,Phi_ret)); return MEM[ 2:___ 3:.v0=0;]; return Test.s0; ]", code.print()); + assertEquals("Stop[ return Phi(Loop,Test.s0,Phi(Region,Test.s0,Phi_ret)); return Test.s0; ]", code.print()); } @Test @@ -387,7 +387,7 @@ s0 b = new s0(); return a.x * 100 + b.x * 10 + cnt; """); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("Stop[ return 112; return MEM[ 2:___ 3:___ 4:.x=1;]; return Test.s0; ]", code.print()); + assertEquals("Stop[ return 112; return Test.s0; ]", code.print()); } @@ -430,7 +430,7 @@ else return new s0; if(new s0.f0) return 0; """); code.driver(CodeGen.Phase.TypeCheck); - assertEquals("Stop[ return Test.s0; return MEM[ 2:___ 3:.f0=0;]; return Test.s0; ]", code.print()); + assertEquals("Stop[ return Test.s0; return Test.s0; ]", code.print()); } @Test @@ -458,7 +458,7 @@ s0 v1 = v0; return v1; """); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("Stop[ return (const)Test.s0; return MEM[ 2:___ 3:.f0=0;]; return Test.s0; ]", code.print()); + assertEquals("Stop[ return (const)Test.s0; return Test.s0; ]", code.print()); } diff --git a/chapter28a/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter28a/src/test/java/com/seaofnodes/simple/Chapter12Test.java index fae7611d..973b15d9 100644 --- a/chapter28a/src/test/java/com/seaofnodes/simple/Chapter12Test.java +++ b/chapter28a/src/test/java/com/seaofnodes/simple/Chapter12Test.java @@ -101,7 +101,7 @@ N n = new N(new M); return n.next; """); code.driver(CodeGen.Phase.FinalizeIR); - assertEquals("Stop[ return (const)Test.M; return MEM[ 2:___ 3:.m=0;]; return Test.M; return MEM[ 2:___ 3:___ 4:___ 5:___ 6:___ 7:.next=Parm_m(Test.N.N,*Test.M {i64 !m; }); 8:.i=0;]; return MEM[ 2:___ 3:___ 4:___ 5:___ 6:___ 7:___ 8:.i=0;]; return Test.N; ]", code.print()); + assertEquals("Stop[ return (const)Test.M; return Test.M; return Test.N; ]", code.print()); } @Test diff --git a/chapter28a/src/test/java/com/seaofnodes/simple/Chapter18Test.java b/chapter28a/src/test/java/com/seaofnodes/simple/Chapter18Test.java index 632b9539..8e109991 100644 --- a/chapter28a/src/test/java/com/seaofnodes/simple/Chapter18Test.java +++ b/chapter28a/src/test/java/com/seaofnodes/simple/Chapter18Test.java @@ -345,7 +345,7 @@ for(;;) { } """); code.driver(Phase.LocalSched); - assertEquals("Stop[ return Top; return Parm_i(x,i64); ]", code.print()); + assertEquals("return Top;", code.print()); assertNull( Eval2.eval( code, 0 ) ); // Infinite loop, <clinit> never exits } diff --git a/chapter28a/src/test/java/com/seaofnodes/simple/Chapter19Test.java b/chapter28a/src/test/java/com/seaofnodes/simple/Chapter19Test.java index d5fe207a..3cc00922 100644 --- a/chapter28a/src/test/java/com/seaofnodes/simple/Chapter19Test.java +++ b/chapter28a/src/test/java/com/seaofnodes/simple/Chapter19Test.java @@ -118,7 +118,7 @@ val hashCode = { !String self -> hashCode(new String("Hello, World!")); """; CodeGen code = new CodeGen(src).driver(Phase.LocalSched); - assertEquals("Stop[ return Phi(Region,123456789,(!=0)Phi(Loop,0,(.[]+((Phi_hash<<5)-Phi_hash)))); return MEM[ 2:___ 3:___ 4:___ 5:.cs=Parm_s(Test.String.String,*[]u8[final]); 6:._hashCode=0;]; return MEM[ 2:___ 3:___ 4:___ 5:___ 6:._hashCode=0;]; return Test.String; return Phi(Region,0,1,0,1); return Phi(Region,(!=0)._hashCode,Phi(Region,123456789,(!=0)Phi(Loop,0,(.[]+((Phi_hash<<5)-Phi_hash))))); ]", code.print()); + assertEquals("Stop[ return Phi(Region,123456789,(!=0)Phi(Loop,0,(.[]+((Phi_hash<<5)-Phi_hash)))); return Test.String; return Phi(Region,0,1,0,1); return Phi(Region,(!=0)._hashCode,Phi(Region,123456789,(!=0)Phi(Loop,0,(.[]+((Phi_hash<<5)-Phi_hash))))); ]", code.print()); assertEquals("4029215624828139541", Eval2.eval(code, 2)); } diff --git a/chapter28a/src/test/java/com/seaofnodes/simple/Chapter20Test.java b/chapter28a/src/test/java/com/seaofnodes/simple/Chapter20Test.java index cdfd16a7..7eb1f255 100644 --- a/chapter28a/src/test/java/com/seaofnodes/simple/Chapter20Test.java +++ b/chapter28a/src/test/java/com/seaofnodes/simple/Chapter20Test.java @@ -241,13 +241,6 @@ val equals = { !String !self, !String s -> return true; }; -// Return the String hashCode (cached, and never 0) -val hashCode = { !String self -> - self._hashCode - ? self._hashCode - : (self._hashCode = _hashCodeString(self)); -}; - val _hashCodeString = { !String self -> int hash=0; for( int i=0; i< self.cs#; i++ ) @@ -255,9 +248,17 @@ val _hashCodeString = { !String self -> if( !hash ) hash = 123456789; return hash; }; + +// Return the String hashCode (cached, and never 0) +val hashCode = { !String self -> + self._hashCode + ? self._hashCode + : (self._hashCode = _hashCodeString(self)); +}; """; + // Return the public hashCode wrapper; keep its helper private. testCPU(src,"x86_64_v2", "SystemV",12,null); - testCPU(src,"riscv" , "SystemV",6,null); + testCPU(src,"riscv" , "SystemV",8,null); testCPU(src,"arm" , "SystemV",6,null); } diff --git a/chapter28a/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/chapter28a/src/test/java/com/seaofnodes/simple/Chapter21Test.java index 2fcf65e9..e15c2581 100644 --- a/chapter28a/src/test/java/com/seaofnodes/simple/Chapter21Test.java +++ b/chapter28a/src/test/java/com/seaofnodes/simple/Chapter21Test.java @@ -368,13 +368,6 @@ val equals = { !String !self, !String s -> return true; }; -// Return the String hashCode (cached, and never 0) -val hashCode = { !String self -> - self._hashCode - ? self._hashCode - : (self._hashCode = _hashCodeString(self)); -}; - val _hashCodeString = { !String self -> int hash=0; for( int i=0; i< self.cs#; i++ ) @@ -382,9 +375,17 @@ val _hashCodeString = { !String self -> if( !hash ) hash = 123456789; return hash; }; + +// Return the String hashCode (cached, and never 0) +val hashCode = { !String self -> + self._hashCode + ? self._hashCode + : (self._hashCode = _hashCodeString(self)); +}; """; + // Return the public hashCode wrapper; keep its helper private. testCPU(src,"x86_64_v2", "SystemV",12,null); - testCPU(src,"riscv" , "SystemV",6,null); + testCPU(src,"riscv" , "SystemV",8,null); testCPU(src,"arm" , "SystemV",6,null); } @@ -407,13 +408,6 @@ val equals = { !String !self, !String s -> return true; }; -// Return the String hashCode (cached, and never 0) -val hashCode = { !String self -> - self._hashCode - ? self._hashCode - : (self._hashCode = _hashCodeString(self)); -}; - val _hashCodeString = { !String self -> int hash=0; for( int i=0; i< self.cs#; i++ ) @@ -421,6 +415,13 @@ val _hashCodeString = { !String self -> if( !hash ) hash = 123456789; return hash; }; + +// Return the String hashCode (cached, and never 0) +val hashCode = { !String self -> + self._hashCode + ? self._hashCode + : (self._hashCode = _hashCodeString(self)); +}; """; TestC.runC(src, "stringHash", "",12); } diff --git a/chapter28a/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java b/chapter28a/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java index 6b4a987d..c8a00d19 100644 --- a/chapter28a/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java +++ b/chapter28a/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java @@ -132,6 +132,28 @@ public class ClassInitDependenciesTest { assertTrue(touches(code,deps,"Root/B","Root")); } + @Test public void testUnknownCallbackCycle() throws Exception { + for( long seed : new long[]{1,123,987654321} ) { + try { + compile("A",Map.of( + "A", """ + { {int->int} -> {int->int} } bounce="C"; + val callback_noInline={int n -> B.value;}; + val f=bounce(callback_noInline); + int value=f(1); + """, + "B", "int value=A.value;"),seed); + fail("Expected callback initialization cycle"); + } catch( Parser.ParseException ex ) { + String message=ex.getMessage(); + assertTrue(message,message.startsWith("Cyclic class initialization:")); + assertTrue(message,message.contains(" -> unknown call")); + assertTrue(message,message.contains("unknown call -> ")); + assertTrue(message,message.contains("Root.B -> Root.A")); + } + } + } + @Test public void testCycles() throws Exception { cycle("A",Map.of("A","val get_noInline={ ->\n B.value;\n};\nint value=get_noInline();", "B","int value=A.value;"),"A -> B -> A"); diff --git a/chapter28a/src/test/java/com/seaofnodes/simple/EscapeFlowTest.java b/chapter28a/src/test/java/com/seaofnodes/simple/EscapeFlowTest.java new file mode 100644 index 00000000..71a281b5 --- /dev/null +++ b/chapter28a/src/test/java/com/seaofnodes/simple/EscapeFlowTest.java @@ -0,0 +1,112 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.*; +import org.junit.Test; +import static org.junit.Assert.*; + +public class EscapeFlowTest { + // A cofinite outside set must not revive private allocation helpers. + @Test public void testOpaqueRoundTrip() { + for( long seed : new long[]{1,123,987654321} ) { + CodeGen code = new CodeGen(""" + struct _A { B? !b; new _A = { B? p -> b=p; }; }; + val wrap = { B? b -> return new _A(b); }; + return wrap; + """,seed,true).driver(CodeGen.Phase.TypeCheck); + TypeMem outside = ((TypeTuple)code._start._type).mem(); + assertTrue(XInt.isHigh(outside._escFs)); + assertNotSame(XInt.FULL,outside._escFs); + for( FunNode fun : code._linker ) + if( fun!=null && fun._name!=null && fun._name.contains("_A.") ) + assertFalse(fun._name,fun.unknownCallers()); + } + } + + // Foreign callbacks remain possible. The default callback argument reads + // Start memory rather than a frozen declaration constant. + @Test public void testUnknownCallerCallback() { + CodeGen code = new CodeGen(""" + val _hidden_noInline = { int n -> return n+2; }; + val apply_noInline = { {int->int} f,int n -> return f(n); }; + return apply_noInline; + """).parse(); + TypeMemPtr self = (TypeMemPtr)named(code,"apply_noInline").parm(2).in(1)._type; + assertTrue(self._one); + assertEquals("class:Test",self._obj._name); + code.driver(CodeGen.Phase.TypeCheck); + FunNode apply = named(code,"apply_noInline"); + assertTrue(apply.unknownCallers()); + ParmNode callback = apply.parm(3); + assertTrue(callback.in(1) instanceof ProjNode); + TypeFunPtr type = (TypeFunPtr)callback._type; + assertTrue(XInt.isHigh(type.fidxs())); + assertSame(TypeInteger.BOT,apply.ret().expr()._type); + // Only <clinit> and apply escape; forwarding #self must not expose _hidden. + assertEquals("Stop[ return { apply_noInline}; return #2; ]",code.print()); + } + + @Test public void testNativeCallbackRoundTrip() { + CodeGen code = new CodeGen(""" + val _callback_noInline = { int n -> return n+1; }; + { {int->int} -> {int->int} } bounce = "C"; + return bounce(_callback_noInline)(arg); + """).driver(CodeGen.Phase.TypeCheck); + FunNode callback = named(code,"_callback_noInline"); + assertTrue(callback.unknownCallers()); + assertTrue(XInt.bit(((TypeTuple)code._start._type).mem()._escFs,callback.sig().fidx())); + } + + // Call remains CONTROL. Changing only the outside set must wake CallEnd + // and extend the possible callback returned by the native factory. + @Test public void testOutsideReturnDependency() { + CodeGen code = new CodeGen("return 0;").parse(); + code._phase = CodeGen.Phase.Opto; + code.externFunc(100,"factory"); + TypeFunPtr callback = TypeFunPtr.make(false,TypeFunPtr.TEMPTY,TypeInteger.BOT); + TypeFunPtr factory = TypeFunPtr.make1((byte)2,true,TypeFunPtr.TEMPTY,callback,100); + CallNode call = new CallNode(null,ConstantNode.raw(Type.CONTROL), + ConstantNode.raw(TypeMem.START),ConstantNode.raw(factory)).init(); + CallEndNode end = new CallEndNode(call,TypeRPC.BOT); + int[] foreign = XInt.dual(XInt.make(7)); + code._start._type = TypeTuple.make(Type.CONTROL,TypeMem.START.makeFrom(foreign,XInt.EMPTY),TypeInteger.BOT); + TypeTuple before = (TypeTuple)end.compute(); + assertFalse(XInt.bit(((TypeFunPtr)before.ret()).fidxs(),7)); + boolean depends = false; + for( int i = 0; i<code._start.nDeps(); i++ ) depends |= code._start.dep(i)==end; + assertTrue(depends); + code._start._type = TypeTuple.make(Type.CONTROL,TypeMem.START.makeFrom(XInt.FULL,XInt.EMPTY),TypeInteger.BOT); + TypeTuple after = (TypeTuple)end.compute(); + assertTrue(before.isa(after)); + assertTrue(XInt.bit(((TypeFunPtr)after.ret()).fidxs(),7)); + } + + // Public construction exposes the allocator; private-memory helpers + // remain internal and can inline into it. + @Test public void testPublicAllocator() { + CodeGen code = new CodeGen(""" + struct Box { int n; new Box = { int x -> n=x; }; }; + return new Box(arg).n; + """).parse(); + FunNode allocator = named(code,"Test.Box.<new>"); + int[] helpers = XInt.EMPTY; + for( FunNode fun : code._linker ) + if( fun!=null && fun.sig().ret() instanceof TypeMem ) + helpers = XInt.make(helpers,fun.sig().fidx()); + assertNotSame(XInt.EMPTY,helpers); + code.driver(CodeGen.Phase.TypeCheck); + TypeMem outside = ((TypeTuple)code._start._type).mem(); + assertTrue(allocator.unknownCallers()); + assertTrue(XInt.bit(outside._escFs,allocator.sig().fidx())); + for( int fidx = XInt.next(helpers,0); fidx>=0; fidx = XInt.next(helpers,fidx) ) + assertFalse(XInt.bit(outside._escFs,fidx)); + assertEquals("42",Eval2.eval(code,42)); + } + + private static FunNode named(CodeGen code, String name) { + for( FunNode fun : code._linker ) + if( fun!=null && name.equals(fun._name) ) return fun; + throw new AssertionError("Missing function "+name); + } +} diff --git a/chapter28a/src/test/java/com/seaofnodes/simple/ModulesTest.java b/chapter28a/src/test/java/com/seaofnodes/simple/ModulesTest.java index be54b43d..69f8d862 100644 --- a/chapter28a/src/test/java/com/seaofnodes/simple/ModulesTest.java +++ b/chapter28a/src/test/java/com/seaofnodes/simple/ModulesTest.java @@ -11,6 +11,9 @@ import com.seaofnodes.simple.node.CallNode; import com.seaofnodes.simple.node.FunNode; import com.seaofnodes.simple.node.LoopNode; import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.type.XInt; +import com.seaofnodes.simple.type.TypeMem; +import com.seaofnodes.simple.type.TypeTuple; import com.seaofnodes.simple.util.Ary; import java.io.File; import java.io.FileWriter; @@ -71,27 +74,56 @@ public class ModulesTest { for( long seed : new long[]{1,123,987654321} ) { CodeGen code = new CodeGen(src,seed,true).driver(CodeGen.Phase.Parse); FunNode visible = null; + int[] privateFidxs = XInt.EMPTY; int checked = 0; for( FunNode fun : code._linker ) { if( fun==null || fun._name==null ) continue; if( fun._name.equals("visible") ) { - assertTrue(code.publicFIDX(fun.sig().fidx())); visible = fun; checked++; } if( fun._name.equals("_hidden") || fun._name.equals("hiddenClassMethod") ) { - assertFalse(code.publicFIDX(fun.sig().fidx())); + privateFidxs = XInt.make(privateFidxs,fun.sig().fidx()); checked++; } } assertEquals(3,checked); code.driver(CodeGen.Phase.TypeCheck); + TypeMem outside = ((TypeTuple)code._start._type).mem(); + assertTrue(XInt.bit(outside._escFs,visible.sig().fidx())); + for( int fidx = XInt.next(privateFidxs,0); fidx>=0; fidx = XInt.next(privateFidxs,fidx) ) + assertFalse(XInt.bit(outside._escFs,fidx)); assertTrue(visible.unknownCallers()); assertEquals(TypeInteger.U8,visible.parm(3)._type); assertEquals("0",Eval2.eval(code,0)); } } + @Test public void testPublicFunctionAlias() { + CodeGen code = new CodeGen(""" + val _hidden = { int x -> x; }; + val visible = _hidden; + val _private = { int x -> x+1; }; + return 0; + """).parse(); + int published = -1, unpublished = -1; + for( FunNode fun : code._linker ) { + if( fun==null || fun._name==null ) continue; + if( fun._name.equals("_hidden") ) published = fun.sig().fidx(); + if( fun._name.equals("_private") ) unpublished = fun.sig().fidx(); + } + assertTrue(published>=0 && unpublished>=0); + var clz = code.compunit()._clz; + code.driver(CodeGen.Phase.TypeCheck); + TypeMem outside = ((TypeTuple)code._start._type).mem(); + // A public field can publish a privately named function. + assertTrue(XInt.bit(outside._escFs,published)); + assertFalse(XInt.bit(outside._escFs,unpublished)); + assertTrue(XInt.bit(outside._escAs,clz.field("visible")._alias)); + assertFalse(XInt.bit(outside._escAs,clz.field("_hidden")._alias)); + assertFalse(XInt.bit(outside._escAs,clz.field("_private")._alias)); + } + @Test public void testNeverReturnPointerRiscV() { new CodeGen(""" struct _S { int x; }; |
