summaryrefslogtreecommitdiff
path: root/chapter28a/src
diff options
context:
space:
mode:
Diffstat (limited to 'chapter28a/src')
-rw-r--r--chapter28a/src/main/java/com/seaofnodes/simple/Parser.java9
-rw-r--r--chapter28a/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java27
-rw-r--r--chapter28a/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java74
-rw-r--r--chapter28a/src/main/java/com/seaofnodes/simple/codegen/LocalIds.java14
-rw-r--r--chapter28a/src/main/java/com/seaofnodes/simple/codegen/Opto.java49
-rw-r--r--chapter28a/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java4
-rw-r--r--chapter28a/src/main/java/com/seaofnodes/simple/node/CLInitNode.java24
-rw-r--r--chapter28a/src/main/java/com/seaofnodes/simple/node/CallEndNode.java20
-rw-r--r--chapter28a/src/main/java/com/seaofnodes/simple/node/CallNode.java16
-rw-r--r--chapter28a/src/main/java/com/seaofnodes/simple/node/ConvertNode.java7
-rw-r--r--chapter28a/src/main/java/com/seaofnodes/simple/node/EscapeNode.java8
-rw-r--r--chapter28a/src/main/java/com/seaofnodes/simple/node/FunNode.java11
-rw-r--r--chapter28a/src/main/java/com/seaofnodes/simple/node/LoadNode.java67
-rw-r--r--chapter28a/src/main/java/com/seaofnodes/simple/node/ParmNode.java12
-rw-r--r--chapter28a/src/main/java/com/seaofnodes/simple/node/StartNode.java19
-rw-r--r--chapter28a/src/main/java/com/seaofnodes/simple/node/StopCUNode.java58
-rw-r--r--chapter28a/src/main/java/com/seaofnodes/simple/node/StoreNode.java9
-rw-r--r--chapter28a/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java4
-rw-r--r--chapter28a/src/main/java/com/seaofnodes/simple/type/TypeMem.java19
-rw-r--r--chapter28a/src/main/java/com/seaofnodes/simple/type/TypeStruct.java12
-rw-r--r--chapter28a/src/main/java/com/seaofnodes/simple/type/XInt.java13
-rw-r--r--chapter28a/src/test/java/com/seaofnodes/simple/Chapter10Test.java20
-rw-r--r--chapter28a/src/test/java/com/seaofnodes/simple/Chapter12Test.java2
-rw-r--r--chapter28a/src/test/java/com/seaofnodes/simple/Chapter18Test.java2
-rw-r--r--chapter28a/src/test/java/com/seaofnodes/simple/Chapter19Test.java2
-rw-r--r--chapter28a/src/test/java/com/seaofnodes/simple/Chapter20Test.java17
-rw-r--r--chapter28a/src/test/java/com/seaofnodes/simple/Chapter21Test.java31
-rw-r--r--chapter28a/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java22
-rw-r--r--chapter28a/src/test/java/com/seaofnodes/simple/EscapeFlowTest.java112
-rw-r--r--chapter28a/src/test/java/com/seaofnodes/simple/ModulesTest.java36
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; };