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 --- .../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 +++--- 23 files changed, 175 insertions(+), 127 deletions(-) (limited to 'chapter29/src/main/java') diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java index ba7c0c1d..b4c93e1e 100644 --- a/chapter29/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java +++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java @@ -96,80 +96,11 @@ public class CodeGen { return null; } - // Frozen after ParseAll: public initializers and function-valued fields. - public boolean publicFIDX(int fidx) { - return fidx >= GlobalBits.RESERVED && XInt.bit(publicFIDXs(),fidx); - } - - private void gatherPublicFields(TypeStruct clz, boolean classFields) { - for( Field fld : clz._fields ) { - // Private-memory helpers are reached through their allocator. - if( fld._fname.charAt(0)=='_' || fld._t==Type.TOP || - clz.hiddenInit(fld) ) continue; - if( classFields ) _publicAliases = XInt.make(_publicAliases,fld._alias); - if( fld._t instanceof TypeFunPtr tfp ) - _publicFIDXs = XInt.meet(_publicFIDXs,tfp._fidxs); - } - } - public static boolean hasUserConstructor(TypeStruct self) { Type t = Parser.TYPES.get(TypeStruct.addClzPrefix(self._name)); return t instanceof TypeStruct clz && clz.field("") != null; } - // A forward reference has a placeholder class, not a definition yet. - private static boolean resolvedClass(TypeStruct clz) { - Type t = Parser.TYPES.get(clz._name.substring(TypeStruct.CLZ.length())); - return !(t instanceof TypeStruct ts) || !ts._fref; - } - - private int[] _publicFIDXs, _publicAliases; - private TypeMem _publicEscapes; - - /** - * Snapshot the public interface after ParseAll for interface checks and - * the default escapes of opaque external types. This is not an upper - * bound on all escapes: a public function can return a private object or - * callback. Escape flow preserves those actual values separately. - */ - void freezePublicInterface() { - assert _publicFIDXs==null && _publicAliases==null; - _publicFIDXs = _publicAliases = XInt.EMPTY; - for( Type t : Parser.TYPES.values() ) - if( t instanceof TypeStruct clz && - TypeStruct.startsClzPrefix(clz._name) && - TypeStruct.isPublicClassName(clz._name) && - resolvedClass(clz) ) { - // Gather function pointers from public fields of both class - // and instance objects. - gatherPublicFields(clz,true); - Type self = Parser.TYPES.get(clz._name.substring(TypeStruct.CLZ.length())); - if( self instanceof TypeStruct ts ) gatherPublicFields(ts,false); - } - // Limit broad function types to known FIDXs, excluding reserved bits. - // Public class initializers are roots without needing a field reference. - int[] fidxs = XInt.EMPTY; - for( int fidx = GlobalBits.RESERVED; fidx<_linker._len; fidx++ ) { - FunNode fun = _linker.atX(fidx); - if( XInt.bit(_publicFIDXs,fidx) || (fun!=null && fun.isClz() && fun.isPublic()) ) - fidxs = XInt.make(fidxs,fidx); - } - _publicFIDXs = fidxs; - _publicEscapes = TypeMem.START.makeFrom(_publicFIDXs,_publicAliases); - } - - public int[] publicFIDXs() { assert _publicFIDXs !=null; return _publicFIDXs; } - public int[] publicAliases(){ assert _publicAliases!=null; return _publicAliases; } - public boolean publicInterfaceFrozen() { return _publicFIDXs != null; } - - // Before Opto upgrades the IR, a forward reference might still name a - // local class. Afterwards, unresolved names describe external objects. - public TypeMem publicEscapes() { - if( _phase==null || _phase.ordinal()>>5)+2); + Arrays.fill(foreign,1,foreign.length,-1); + for( int i = 0; i<_local; i++ ) { + String owner = _local2Clz.at(i); + if( owner==null || !owner.startsWith("C:") ) + foreign[(i>>>5)+1] &= ~(1< oldTypes = new Ary<>(Type.class); WorkList deps = new WorkList<>(); @@ -158,11 +161,9 @@ 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);` + // An infinite target set uses the outside continuation. Escaped local + // callbacks are called through Start; their effects feed that same + // outside state. Do not attach unrelated same-arity function bodies. 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 @@ -185,10 +186,10 @@ abstract public class Opto { TypeMem tmem = (TypeMem)tt._types[1]; int[] fidxs = tmem._escFs; if( XInt.isHigh(fidxs) ) { - // Unknown values can later refine to private callbacks too. - // FULL must cover every live target, just like the finite case. + // A cofinite set includes foreign identities but can exclude local + // functions. Honor those exclusions just as in the finite case. for( FunNode fun : code._linker ) - if( fun != null && !fun.isDead() ) + if( fun != null && !fun.isDead() && XInt.bit(fidxs,fun.sig().fidx()) ) linkStart(code,fun); return; } @@ -200,7 +201,8 @@ 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 defaults read Start's changing outside state. + // Scalar defaults remain constants; hidden self uses the owning class. static void linkStart( CodeGen code, FunNode fun ) { assert !fun.isDead(); StartCUNode start = fun._compunit._start; @@ -212,7 +214,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); } diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java index b77b224c..5f8a0ec0 100644 --- a/chapter29/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java +++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java @@ -118,11 +118,6 @@ public abstract class ParseAll { for( CompUnit cu : code._compunits.values() ) if( cu._ext==null ) cu._clz = (TypeStruct)Parser.TYPES.get(TypeStruct.addClzPrefix(cu._cname)); - - // 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/chapter29/src/main/java/com/seaofnodes/simple/node/CLInitNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/CLInitNode.java index f8c1c925..fb762ff3 100644 --- a/chapter29/src/main/java/com/seaofnodes/simple/node/CLInitNode.java +++ b/chapter29/src/main/java/com/seaofnodes/simple/node/CLInitNode.java @@ -84,10 +84,20 @@ public class CLInitNode extends CFGNode implements MultiNode { return TypeTuple.make(in(0)._type,mem()._type,mem()._type,Type.XCONTROL); 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); + } + } Type call = ready()._type.isHigh() || ready()._type==TypeInteger.TRUE ? Type.XCONTROL : in(0)._type; return TypeTuple.make(in(0)._type,bulk,slice,call); } @@ -181,6 +191,10 @@ public class CLInitNode extends CFGNode implements MultiNode { // Replace the analysis edge with the ordinary call. Its arguments // are the same ones already analyzed before lowering. call.link(target); + // Publish the linked effects before projections can feed callers. + // The unlinked CallEnd's pessimistic seed is not the replacement for + // the precise initializer effects already analyzed above. + end.setType(end.compute()); unlink(); Node called = CodeGen.CODE.add(new CProjNode(end,0,ScopeNode.CTRL).init()); Node after = CodeGen.CODE.add(new ProjNode(end,1,ScopeNode.MEM0).init()); diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/CallEndNode.java index 4056e24e..b5156c2d 100644 --- a/chapter29/src/main/java/com/seaofnodes/simple/node/CallEndNode.java +++ b/chapter29/src/main/java/com/seaofnodes/simple/node/CallEndNode.java @@ -86,9 +86,15 @@ public class CallEndNode extends CFGNode implements MultiNode { // Mid-fold, just take the inlined callee's return. if( _folding ) return in(1)._type; - // Unknown targets can change any memory and return the declared type. - if( XInt.isHigh(tfp.fidxs()) ) - return TypeTuple.make(Type.CONTROL,TypeMem.BOT,tfp.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