summaryrefslogtreecommitdiff
path: root/chapter29/src
diff options
context:
space:
mode:
Diffstat (limited to 'chapter29/src')
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java69
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/GlobalBits.java13
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/Opto.java26
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java5
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java6
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/CLInitNode.java20
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/CallEndNode.java15
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/CallNode.java6
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/ConstructNode.java11
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/ConvertNode.java13
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/EscapeNode.java24
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/FunNode.java13
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/InitViewNode.java30
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/LoadNode.java58
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/MemOpNode.java2
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/ParmNode.java19
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/StartNode.java13
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/StopCUNode.java2
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/StoreNode.java9
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/SummaryNode.java6
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/parser/Allocations.java2
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/parser/Construction.java56
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/parser/Functions.java7
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/parser/References.java2
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/parser/Types.java4
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/type/Type.java5
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java4
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/type/TypeMem.java13
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java19
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/type/TypeStruct.java2
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/type/XInt.java17
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/Chapter20Test.java23
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/Chapter21Test.java38
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/Chapter25Test.java66
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java22
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/ConstructionGroupTest.java21
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/EscapeFlowTest.java112
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/PartialReadTest.java155
38 files changed, 711 insertions, 217 deletions
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("<ctor>") != 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()<Phase.Opto.ordinal() ) return TypeMem.BOT;
- assert _publicEscapes!=null;
- return _publicEscapes;
- }
-
// ---------------------------
// Very common nodes, cached here
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/GlobalBits.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/GlobalBits.java
index bc9c9d39..acd2eee5 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/codegen/GlobalBits.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/GlobalBits.java
@@ -1,6 +1,7 @@
package com.seaofnodes.simple.codegen;
import com.seaofnodes.simple.util.*;
+import com.seaofnodes.simple.type.XInt;
import java.util.HashMap;
import java.util.Arrays;
import java.util.Objects;
@@ -28,6 +29,18 @@ public class GlobalBits {
// Local dense index
private int _local;
+ // Identities not owned by this compilation. Reserved bits are not foreign.
+ public int[] foreign() {
+ int[] foreign = XInt.alloc((_local>>>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<<i);
+ }
+ return XInt.intern(foreign);
+ }
+
GlobalBits() {
_local = RESERVED;
// Reserved null-class bits are global identities and are never
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/Opto.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/Opto.java
index 0c37ce39..6d653835 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/codegen/Opto.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/Opto.java
@@ -26,6 +26,9 @@ 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<>();
@@ -80,7 +83,7 @@ abstract public class Opto {
if( x instanceof ParmNode && x._type.isHighOrConst() ) deps.push(x);
// Construction identities may fold after the optimistic solve.
// Adds may need sorting after imported node IDs were assigned.
- if( x instanceof ConstructNode || x instanceof AddNode ) deps.push(x);
+ if( x instanceof ConstructNode || x instanceof InitViewNode || x instanceof AddNode ) deps.push(x);
// SCCP adds call edges and default arguments, not numeric
// operations. Gather unresolved modes on this required walk.
if( x instanceof ModeNode mode && mode.mode()==0 ) modes.push(x);
@@ -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/codegen/TypeCheck.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java
index 30e1415e..a86cd18a 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java
@@ -20,11 +20,13 @@ abstract class TypeCheck {
Ary<Node> errs = new Ary<>(Node.class);
Ary<ConstructNode> constructs = new Ary<>(ConstructNode.class);
+ Ary<InitViewNode> views = new Ary<>(InitViewNode.class);
code._stop.walk( n -> {
if( n.err() != null )
errs.add(n);
if( n instanceof CLInitNode ) code.add(n);
if( n instanceof ConstructNode con ) constructs.add(con);
+ if( n instanceof InitViewNode view ) views.add(view);
return null;
});
@@ -48,11 +50,13 @@ abstract class TypeCheck {
if( code._obs != null ) code._obs.error(min, min.err().getMessage());
throw min.err();
}
- // Checked construction boundaries have no runtime behavior. Gathered
+ // Checked construction boundaries and views have no runtime behavior. Gathered
// above during the existing error walk; no separate graph pass/solve.
for( ConstructNode con : constructs )
if( !con.isDead() ) con.subsume(con.in(1));
+ for( InitViewNode view : views )
+ if( !view.isDead() ) view.subsume(view.in(1));
}
private static int errorRank(Node n) {
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<nIns(); i++ )
+ state = (TypeTuple)state.meet(in(i)._type);
+ return state;
+ }
// Check for making a "C" call
int externs = 0;
@@ -98,7 +104,8 @@ public class CallEndNode extends CFGNode implements MultiNode {
// C calls have no Return edges. Simple calls have an extra self
// argument, so the two cannot be targets of the same valid call.
assert externs==tfp.nfcns();
- return TypeTuple.make(Type.CONTROL,call.exposed(this),tfp.ret());
+ TypeMem outside = CodeGen.CODE._start.outside(this).escapes(call.exposed(this));
+ return TypeTuple.make(Type.CONTROL,outside,outside.outside(tfp.ret()));
}
// An unlinked target may have effects, so early conservative return.
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/CallNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/CallNode.java
index 18be3488..91d27330 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/node/CallNode.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/CallNode.java
@@ -100,7 +100,9 @@ public class CallNode extends CFGNode {
if( !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);
+ 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);
}
@@ -244,7 +246,7 @@ public class CallNode extends CFGNode {
// Check for args
for( int i=0; i<tfp.nargs(); i++ )
- if( !arg(i+2)._type.isa(tfp.arg(i)) || !arg(i+2)._type.accessISA(tfp.arg(i)) ) {
+ if( !arg(i+2)._type.isa(tfp.arg(i) instanceof TypeMemPtr p ? p.partialBound() : tfp.arg(i)) || !arg(i+2)._type.accessISA(tfp.arg(i)) ) {
if( arg(i+2)._type instanceof TypeMemPtr actual && actual._partial &&
tfp.arg(i) instanceof TypeMemPtr formal && !formal._partial )
return Parser.error("Cannot pass an incomplete object to a parameter without '^' (ordinary function or constructor)",_loc);
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/ConstructNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/ConstructNode.java
index 3984e48f..0af37a02 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/node/ConstructNode.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/ConstructNode.java
@@ -49,10 +49,17 @@ public class ConstructNode extends Node {
return ptr.withPartial(partial);
}
@Override public Node idealize() {
- // Wait for Opto to install closed field types. Afterwards, a proven
- // identity with initialized fields has no remaining error obligation.
+ // Restricting an already restricted view is redundant, including after
+ // inlining. A partial Parm type alone can later sharpen to complete.
+ if( _partial && partialView(in(1)) ) return in(1);
+ // Wait for Opto to install closed field types. Afterwards, a proven
+ // identity with initialized fields is good, and can be removed.
+ // Otherwise we're keeping this Node until we error report.
return !_partial && CodeGen.CODE._phase.ordinal()>=CodeGen.Phase.Opto.ordinal() && _type==in(1)._type && err()==null ? in(1) : null;
}
+ static boolean partialView(Node n) {
+ return n instanceof InitViewNode || n instanceof ConstructNode con && con._partial;
+ }
@Override public Parser.ParseException err() {
if( in(2)==null || in(2)._type.isHigh() || !(in(1)._type instanceof TypeMemPtr ptr) ) return null;
Type mem = in(2)._type;
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/ConvertNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/ConvertNode.java
index 47c8ecbe..7616932d 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/node/ConvertNode.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/ConvertNode.java
@@ -40,8 +40,17 @@ public class ConvertNode extends Node {
Type src = val()._type;
if( src.isHigh() ) return Type.TOP;
if( src==Type.BOTTOM || src==TypeScalar.BOT ) return src;
- if( _dst instanceof TypeMemPtr ptr ) return ptr.withPartial(TypeMemPtr.mayBePartial(src));
- if( !(_dst instanceof TypeMemPtr) && src.isa(_dst) ) return src;
+ if( _dst instanceof TypeMemPtr ptr ) {
+ // Assigning to a nullable field does not make a non-null value
+ // null. Keep this fact during SCCP, before idealize removes us.
+ // Shape/access still follow the declaration: switching between
+ // the entire source and destination types is not monotone.
+ if( src instanceof TypeMemPtr actual && actual.notNull() ) ptr = (TypeMemPtr)ptr.nonZero();
+ ptr = ptr.withPartial(TypeMemPtr.mayBePartial(src));
+ return src instanceof TypeMemPtr actual && actual._partial && actual._obj._name==ptr._obj._name
+ ? ptr.initialized(actual._obj) : ptr;
+ }
+ if( src.isa(_dst) ) return src;
return _dst;
}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/EscapeNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/EscapeNode.java
index 02f08c19..57340609 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/node/EscapeNode.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/EscapeNode.java
@@ -9,19 +9,26 @@ import java.util.HashMap;
import java.util.IdentityHashMap;
/**
- * Merge public and private aliases.
+ * Merge public and private aliases. A borrow orders partial-reader calls
+ * after constructor Stores without adding the private values to escape sets.
* 0 - control
* 1 - private pointer
* 2 - private alias
* 3 - public alias
*/
public class EscapeNode extends TypeNode {
+ public final boolean _borrow;
public EscapeNode(Field fld, Node self, Node priv, Node pub ) {
+ this(fld,self,priv,pub,false);
+ }
+ public EscapeNode(Field fld, Node self, Node priv, Node pub, boolean borrow ) {
super(fld,null,self,priv,pub);
+ _borrow = borrow;
}
public EscapeNode(EscapeNode esc ) {
super(esc); // Copy constructor
+ _borrow = esc._borrow;
// Except we do not want/need self ptr past Opto.
// *NOTE* the weird direct stomp; this is correct because this
// constructor does NOT set the backedges, because it is used to
@@ -38,6 +45,7 @@ public class EscapeNode extends TypeNode {
@Override public Tag serialTag() { return Tag.Escape; }
@Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
+ assert !_borrow; // Source ^ interfaces cannot yet be serialized.
baos.packed2(types.get(_con));
}
static Node make( BAOS bais, Type[] types) {
@@ -73,13 +81,20 @@ public class EscapeNode extends TypeNode {
// t is meet of pub & priv. Pub memory can be very weak (e.g. alias 1)
// and we're no worse than the declared field type
- Type t = tpriv.meet(tpub).join(fld._t);
+ Type bound = fld._t;
+ if( _borrow && bound instanceof TypeMemPtr ptr && TypeMemPtr.mayBePartial(tpriv) )
+ bound = ptr.withPartial(true).partialBound();
+ Type t = tpriv.meet(tpub).join(bound);
+ // Lending memory to a local read-only partial argument is an ordering
+ // edge, not publication to unknown callers. Keep the public escape sets.
+ if( _borrow )
+ return TypeMem.make(fld._alias,t,false,false,false,mpub._escFs,mpub._escAs);
int[] fidxes = XInt.meet(mpriv._escFs, mpub._escFs);
int[] aliases = XInt.meet(mpriv._escAs, mpub._escAs);
// 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.publicEscapes();
+ 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);
}
@@ -112,11 +127,12 @@ public class EscapeNode extends TypeNode {
}
@Override public boolean eq(Node n) {
+ if( _borrow!=((EscapeNode)n)._borrow ) return false;
if( fld()._alias!=((EscapeNode)n).fld()._alias )
return false;
assert _con == ((EscapeNode)n)._con;
return true;
}
- @Override int hash() { return _con.hashCode(); }
+ @Override int hash() { return _con.hashCode() ^ (_borrow ? 1024 : 0); }
}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/FunNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/FunNode.java
index de32bea9..8b6b347e 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/node/FunNode.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/FunNode.java
@@ -97,6 +97,19 @@ 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( t instanceof TypeMemPtr ptr && ptr._partial &&
+ !(idx==0 && sig().nargs()>1 && sig().arg(1) instanceof TypeMem) ) return ptr.partialBound();
+ if( idx!=0 || t!=TypePtr.PTR ) return t;
+ TypeStruct clz = (TypeStruct)Parser.TYPES.get(TypeStruct.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/chapter29/src/main/java/com/seaofnodes/simple/node/InitViewNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/InitViewNode.java
new file mode 100644
index 00000000..46988a99
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/InitViewNode.java
@@ -0,0 +1,30 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.*;
+
+/** A program-point view of self. Memory proves which slots may be read;
+ * their values still come from ordinary Loads. Erased after TypeCheck. */
+public class InitViewNode extends TypeNode {
+ public InitViewNode(TypeMemPtr declared, Node self, Node mem) { super(declared,null,self,mem); }
+ @Override public String label() { return "InitView"; }
+ @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p("initView(").n(in(1)).p(")"); }
+ @Override public Type compute() {
+ Type ptr = in(1)._type, mem = in(2)._type;
+ if( ptr.isHigh() || mem.isHigh() ) return Type.TOP;
+ if( !(ptr instanceof TypeMemPtr p) ) return ptr;
+ TypeStruct state = mem instanceof TypeMem m && m._one && m._t instanceof TypeStruct ts
+ ? ts : p._obj.makeInit();
+ // An earlier alias can have fewer initialized slots. The declaration
+ // supplies field types; this program point's memory supplies the proof.
+ return p.makeFrom(((TypeMemPtr)_con)._obj).withPartial(true).initialized(state);
+ }
+ @Override public Node idealize() {
+ // Closed declarations and settled memory must prove this view redundant.
+ // Keep a structural partial restriction: a Parm's current partial type
+ // alone is not enough, since inlining can substitute a complete caller.
+ return CodeGen.CODE._phase.ordinal()>=CodeGen.Phase.Opto.ordinal() &&
+ _type==in(1)._type && ConstructNode.partialView(in(1)) ? in(1) : null;
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/LoadNode.java
index d62152e5..85ab29f9 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/node/LoadNode.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/LoadNode.java
@@ -76,7 +76,6 @@ public class LoadNode extends MemOpNode {
return Type.BOTTOM; // Error in is error out
if( !(ptr0 instanceof TypeMemPtr tmp) )
return scalar(mem._t);
- if( tmp._partial && (!mem._one || mem._clz) ) return TypeScalar.BOT;
if( tmp.nullable() )
return Type.BOTTOM; // Load from nil-able is error
@@ -87,6 +86,8 @@ public class LoadNode extends MemOpNode {
if( pfld == null )
return tmp.isHigh() || tmp._obj.isHigh() ? TypeScalar.TOP : TypeScalar.BOT;
+ if( tmp._partial && (!mem._one || mem._clz) && pfld._t==TypeScalar.BOT ) return TypeScalar.BOT;
+
Type t = pfld._t;
boolean readonly = t instanceof TypeMemPtr view && view._ro;
// Load member of constant array
@@ -97,6 +98,13 @@ public class LoadNode extends MemOpNode {
t = mem._alias==_alias ? mem._t : actual==null ? TypeScalar.BOT : actual._t;
} else 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.
@@ -104,7 +112,10 @@ public class LoadNode extends MemOpNode {
t = t.makeRO();
// A completed receiver gives a completed view of its reachable fields.
// Through self, keep object references restricted until the root new.
- if( t instanceof TypeMemPtr value ) t = value.withPartial(tmp._partial && !value._obj.isAry());
+ if( t instanceof TypeMemPtr value ) {
+ value = value.withPartial(tmp._partial && !value._obj.isAry());
+ t = value._partial ? value.partialBound() : value;
+ }
return scalar(t);
}
@@ -225,38 +236,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) {
+ 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;
- boolean external = ptr._type instanceof TypeMemPtr tmp &&
- (fld=tmp._obj.field(_name))!=null && fld._extern;
+ 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==address(st.ptr()) || ptr==address(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( _type instanceof TypeMemPtr ) {
+ if( expected instanceof TypeMemPtr ) {
Type value = addDep(st.val())._type;
if( ptr()._type.isFinal() || declaredType().isFinal() ) value = value.makeRO();
- if( !value.isa(_type) ) return null;
+ 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,address(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;
@@ -310,7 +333,16 @@ public class LoadNode extends MemOpNode {
// 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) {
- while( ptr instanceof ReadOnlyNode || ptr instanceof ConstructNode con && con._partial ) {
+ // 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 InitViewNode || ptr instanceof ReadOnlyNode || ptr instanceof ConstructNode con && con._partial ) {
addDep(ptr);
ptr = ptr.in(1);
}
@@ -347,7 +379,7 @@ public class LoadNode extends MemOpNode {
Parser.ParseException err = super.err();
if( err!=null ) return err;
if( (in(0)==null || !in(0)._type.isHigh()) && _type==TypeScalar.BOT &&
- mem()._type instanceof TypeMem mem && mem._one && !mem._clz )
+ mem()._type instanceof TypeMem mem && ((mem._one && !mem._clz) || (ptr()._type instanceof TypeMemPtr p && p._partial)) )
return Parser.error("Field '"+_name+"' might not be initialized",_loc);
return null;
}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
index b453550d..35183380 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
@@ -123,7 +123,7 @@ public abstract class MemOpNode extends TypeNode {
// Already an error, but better error messages come from elsewhere
if( ptr == Type.BOTTOM ) return null;
if( ptr.isHigh() ) return null; // Assume it will fall to not-null
- if( ptr instanceof TypeMemPtr p && p._partial &&
+ if( !_isLoad && ptr instanceof TypeMemPtr p && p._partial &&
!(mem()._type instanceof TypeMem m && m._one && !m._clz) )
return Parser.error("Cannot access field '"+_name+"' through an incomplete object",_loc);
// Better be a not-nil TMP
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/ParmNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/ParmNode.java
index 9c40c75d..aea402f6 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/node/ParmNode.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/ParmNode.java
@@ -20,7 +20,7 @@ public class ParmNode extends PhiNode {
super(label, inputs);
_idx = idx;
_declaredType = declaredType;
- _type = declaredType;
+ _type = declaredBound();
}
public ParmNode(ParmNode parm) { super(parm, parm._label ); _idx = parm._idx; _declaredType = parm._declaredType; }
@Override public Tag serialTag() { return Tag.Parm; }
@@ -49,6 +49,12 @@ public class ParmNode extends PhiNode {
public FunNode fun() { return (FunNode)in(0); }
+ // The hidden constructor receiver has private memory for checking reads.
+ // Only borrowed source parameters need an initially empty field promise.
+ private Type declaredBound() {
+ return !(_idx==2 && "self".equals(_label)) && _declaredType instanceof TypeMemPtr ptr ? ptr.partialBound() : _declaredType;
+ }
+
@Override
protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
if( fun()._name!=null && fun()._name.endsWith("<clinit>") && _label.equals("arg") )
@@ -68,7 +74,7 @@ public class ParmNode extends PhiNode {
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;
+ return declaredBound();
// Set type to local top of the starting type
Type t = Type.TOP;
for( int i = 1; i < nIns(); i++ ) {
@@ -78,7 +84,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
@@ -88,7 +99,7 @@ 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();
- t = t.join(_declaredType);
+ t = t.join(declaredBound());
return t==Type.TOP && _declaredType instanceof TypeMem ? TypeMem.TOP : t;
}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/StartNode.java
index 78bd8219..36583884 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/node/StartNode.java
+++ b/chapter29/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; }
@@ -67,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/chapter29/src/main/java/com/seaofnodes/simple/node/StopCUNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/StopCUNode.java
index 8a554390..ead635dd 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/node/StopCUNode.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/StopCUNode.java
@@ -79,7 +79,7 @@ public class StopCUNode extends StopNode {
// 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 = xmem.escapesFrom(tret.ret(),start.outside(this));
xmem = (TypeMem)xmem.meet(tret.mem());
}
}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/StoreNode.java
index 4114b47c..010d5f61 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/node/StoreNode.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/StoreNode.java
@@ -98,6 +98,7 @@ public class StoreNode extends MemOpNode {
// that field again; retain the declared signature instead of unfolding
// successively sharper copies of its private-memory return type.
if( mem._one && decl instanceof TypeFunPtr && val instanceof TypeFunPtr ) val = 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.
@@ -110,14 +111,14 @@ public class StoreNode extends MemOpNode {
// This singleton slice contains just the stored value. Copying
// bulk memory's escapes would freeze a transient FULL bound into
// the constant, permanently exposing unrelated private functions.
- return TypeMem.make(_alias,val,true,true,true,null,null).escapesFrom(val);
+ 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,fld==null ? _init : fld._final,null,null).escapesFrom(val);
+ return TypeMem.make(_alias,val,true,false,fld==null ? _init : fld._final,null,null).escapesFrom(val,outside);
// Normal aliasing Store.
assert mem._alias==1 || mem._alias==_alias; // Perfect aliasing
@@ -132,7 +133,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
@@ -330,6 +331,8 @@ public class StoreNode extends MemOpNode {
public Parser.ParseException err() {
Parser.ParseException err = super.err();
if( err != null ) return err;
+ if( !_init && ptr()._type instanceof TypeMemPtr p && p._partial )
+ return Parser.error("Cannot modify a field through an incomplete object outside its constructor",_loc);
if( TypeMemPtr.mayBePartial(val()._type) &&
!(mem()._type instanceof TypeMem mem && mem._one && !mem._clz) )
return Parser.error("Cannot store an incomplete object into an ordinary object",_loc);
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/SummaryNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/SummaryNode.java
index c22b4bdd..483aab89 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/node/SummaryNode.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/SummaryNode.java
@@ -26,7 +26,8 @@ public class SummaryNode extends CFGNode implements MultiNode {
// A library summary does not forget escapes already present in the caller.
TypeMem mem = in(1)._type instanceof TypeMem m ? m : in(1)._type.isHigh() ? TypeMem.TOP : TypeMem.BOT;
mem = (TypeMem)mem.meet(_state.mem());
- for( int i = 2; i<nIns(); i++ ) mem=mem.escapes(in(i)._type);
+ TypeMem outside = CodeGen.CODE._start.outside(this);
+ for( int i = 2; i<nIns(); i++ ) mem = mem.escapesFrom(in(i)._type,outside);
return TypeTuple.make(_state.ctl(),mem,_state.ret());
}
@Override public Node idealize() { return null; }
@@ -34,7 +35,8 @@ public class SummaryNode extends CFGNode implements MultiNode {
dep.addDepForwards(this);
if( dep.addDepForwards(in(0))._type.isHigh() ) return TypeMem.START;
TypeMem mem = (TypeMem)_state.mem();
- for( int i = 2; i<nIns(); i++ ) mem=mem.escapes(dep.addDepForwards(in(i))._type);
+ TypeMem outside = CodeGen.CODE._start.outside(dep);
+ for( int i = 2; i<nIns(); i++ ) mem = mem.escapesFrom(dep.addDepForwards(in(i))._type,outside);
return TypeMem.make(1,Type.BOTTOM,false,false,false,mem._escFs,mem._escAs);
}
@Override boolean _upgradeType(HashMap<String,Type> types) {
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/parser/Allocations.java b/chapter29/src/main/java/com/seaofnodes/simple/parser/Allocations.java
index 0eda2c62..be68e10e 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/parser/Allocations.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/parser/Allocations.java
@@ -150,7 +150,7 @@ final class Allocations {
new ProjNode(P._ref._start,1,ScopeNode.MEM0).peephole()).peephole();
Node[] parms = new Node[nargs];
for( int i = 0; i<nargs; i++ )
- parms[i] = new ParmNode("arg"+i,i+2,atypes[i],fun,ConstantNode.seed(atypes[i]).peephole()).peephole();
+ parms[i] = new ParmNode("arg"+i,i+2,atypes[i],fun,ConstantNode.seed(fun.defaultArg(i)).peephole()).peephole();
NewNode nnn = new NewNode(ts,fun,ConFldOffNode.make(ts," len")).init();
Node self = new ProjNode(nnn,0,ts.str()).init();
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/parser/Construction.java b/chapter29/src/main/java/com/seaofnodes/simple/parser/Construction.java
index 2720ad3d..6dec8ac1 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/parser/Construction.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/parser/Construction.java
@@ -2,6 +2,7 @@ package com.seaofnodes.simple.parser;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.Ary;
/**
* Field initialization is ordinary forward typing: New supplies Scalar.BOT
@@ -10,17 +11,18 @@ import com.seaofnodes.simple.type.*;
* initialized integer. Loads and merged Returns inspect these types after the
* parser closes loops and compute() stabilizes. No backwards initialization walk.
*
- * References have just a yes/no restriction. Constructor self and ^ parameters
- * are incomplete. An allocation result is incomplete iff an actual argument is
- * incomplete. Copies and Phis preserve this restriction. Only self may be read
- * while incomplete; object-reference fields read from self conservatively remain
- * incomplete. Arrays are born initialized and cannot contain incomplete refs.
- * Ordinary calls and stores outside self require complete references.
+ * Constructor self and ^ parameters are incomplete. InitViewNode combines self
+ * with its current private memory: set fields carry their declared types and
+ * missing fields carry Scalar.BOT. Local ^ parameters meet these shapes through
+ * ordinary forward flow; unknown callers promise no initialized fields. Only
+ * the original constructor may write through an incomplete pointer.
*
- * This permits cycles to be assembled but not traversed until the root new
- * returns. A new with only complete arguments, including null, returns complete.
- * A ^ formal is always restricted inside its constructor, even when a caller
- * passes a complete argument. No field requirements are inferred.
+ * Calls borrowing self receive its Stores on the ordinary public memory edge,
+ * using aliasable Escape slices without publishing its identity. Field values
+ * come from memory, not the pointer's initialization shape. Incomplete values
+ * may be retained only in private construction memory. Nested object references
+ * conservatively promise no initialized fields; arrays are born initialized.
+ * No field requirements are inferred and no extra memory arguments are added.
*
* Incompleteness lives in TypeMemPtr, not a parser provenance graph. Ordinary
* Phis carry it around loops, conversions preserve it, and ConstructNode types
@@ -39,7 +41,7 @@ import com.seaofnodes.simple.type.*;
* Reading a pointer field through a completed receiver gives a completed view
* of that field too: its object's construction could only retain incomplete
* references from the enclosing construction. There is no group ID to clear.
- * A read through incomplete self conservatively returns an incomplete reference
+ * A read through an incomplete pointer conservatively returns an incomplete reference
* (except initialized arrays), even when the stored value happens to be complete.
* Own-self access is identified by the receiver and its private memory, not by
* the nominal class: a ^ argument of the same class cannot use that exception.
@@ -53,7 +55,8 @@ import com.seaofnodes.simple.type.*;
* Hidden helpers have only allocator callers, while the public constructor
* binding denotes the allocator. Private-memory Parms therefore need no
* fictitious external default or constructor-specific transfer function.
- * There is no extra construction pass; see docs/deferred-construction.md.
+ * InitView and Construct are erased by the existing TypeCheck walk. There is
+ * no extra construction pass; see docs/construction-groups.md.
*/
final class Construction {
private final Parser P;
@@ -83,8 +86,33 @@ final class Construction {
return P._scope.in(P._scope.update(mem,null));
}
+ Node view(Node ptr) {
+ return own(ptr) ? new InitViewNode((TypeMemPtr)((ParmNode)_frame._self)._declaredType,ptr,selfMem()).peephole() : ptr;
+ }
+
+ // Carry the constructor's Stores on the call's ordinary memory edge.
+ // The borrowed receiver is read-only; this does not publish its identity.
+ Node callMemory(Ary<Node> args, Node pub) {
+ if( !_frame._owner ) return pub;
+ boolean lends = false;
+ for( int i=2; i<args.size()-1; i++ ) lends |= own(args.at(i));
+ if( !lends ) return pub;
+ Node self = _frame._self;
+ TypeStruct ts = (TypeStruct)Parser.TYPES.get(((TypeMemPtr)self._type)._obj._name);
+ Node priv = selfMem().keep();
+ for( Field fld : ts._fields ) {
+ pub.keep();
+ Node loan = new EscapeNode(fld,self,priv,pub,true).peephole();
+ Node next = P.mergeAlias(pub,fld._alias,loan);
+ pub.unkeep();
+ pub = next;
+ }
+ priv.unkeep();
+ return pub;
+ }
+
boolean own(Node ptr) {
- while( ptr instanceof ConvertNode || ptr instanceof ReadOnlyNode || ptr instanceof GuardNode || (ptr instanceof ConstructNode con && con._partial) ) ptr = ptr.in(1);
+ while( ptr instanceof InitViewNode || ptr instanceof ConvertNode || ptr instanceof ReadOnlyNode || ptr instanceof GuardNode || (ptr instanceof ConstructNode con && con._partial) ) ptr = ptr.in(1);
return _frame._owner && ptr==_frame._self;
}
@@ -97,6 +125,6 @@ final class Construction {
void checkSerializable() {
if( _borrowed )
- throw P.error("Separate compilation of constructors with '^' parameters is not supported yet");
+ throw P.error("Separate compilation of functions with '^' parameters is not supported yet");
}
}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/parser/Functions.java b/chapter29/src/main/java/com/seaofnodes/simple/parser/Functions.java
index 50775349..14dee33b 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/parser/Functions.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/parser/Functions.java
@@ -74,8 +74,8 @@ final class Functions {
Type t = P.types.parseType(); // Arg type
if( t==null ) break;
if( P.match("^") ) {
- if( !constructor || !(t instanceof TypeMemPtr ptr) || ptr._obj.isAry() )
- throw P.error("'^' requires a struct parameter of a constructor");
+ if( !(t instanceof TypeMemPtr ptr) || ptr._obj.isAry() )
+ throw P.error("'^' requires a struct parameter");
t = ptr.withPartial(true);
P.construction._borrowed = true;
}
@@ -131,6 +131,7 @@ final class Functions {
}
// Into the call
+ args.set(1,P.construction.callMemory(args,args.at(1)));
CallNode call = (CallNode)new CallNode(site, args.asAry()).peephole();
// Post-call setup
CallEndNode cend = (CallEndNode)new CallEndNode(call,TypeRPC.constant(P._code.rpc(P._ref._cname))).peephole();
@@ -291,7 +292,7 @@ final class Functions {
for( int i = 0; i<ids.length; i++ ) {
Type t = sig.arg(i);
// Args take a default input if called from Start/external-world
- Node parm = new ParmNode(ids[i],i+2,t,fun,ConstantNode.seed(t).peephole()).peephole();
+ Node parm = new ParmNode(ids[i],i+2,t,fun,ConstantNode.seed(fun.defaultArg(i)).peephole()).peephole();
if( t instanceof TypeMemPtr ptr && ptr._partial ) parm = new ConstructNode(parm).peephole();
P._scope.define(ids[i], t, (i==0 && ids[0]=="self") || (finals!=null && finals.get(i)), parm, loc);
}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/parser/References.java b/chapter29/src/main/java/com/seaofnodes/simple/parser/References.java
index 737ba4c9..483bd7a8 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/parser/References.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/parser/References.java
@@ -94,7 +94,7 @@ final class References {
int selfx = P._scope.kindFcn(var)._lexSize;
// Might be a method call from inside a method, so no explicit "self".
// Pass the in-scope "self"
- return P.expressions.parsePostfixMethod(rvalue,P._scope.in(P._scope.update(P._scope.var(selfx),null)));
+ return P.expressions.parsePostfixMethod(P.construction.view(rvalue),P._scope.in(P._scope.update(P._scope.var(selfx),null)));
}
// Assign-update direct into Scope
if( var._final && !P._scope.canInit(var) )
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/parser/Types.java b/chapter29/src/main/java/com/seaofnodes/simple/parser/Types.java
index 3cb474b1..ffd3a0f4 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/parser/Types.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/parser/Types.java
@@ -146,8 +146,8 @@ final class Types {
Type t0 = parseType(); // Either return or first arg
if( t0==null ) return P.posT(old); // Not a function
Ary<Type> ts = new Ary<>(Type.class);
- // Always reserve a receiver slot. Outside constructor scopes it is
- // broad and unused, so call sites can uniformly pass their self.
+ // Always reserve a receiver slot. Outside constructor scopes the
+ // broad signature also marks the hidden slot for the machine ABI.
ts.push(implicitSelfType());
if( P.match("}") ) // No-arg function { -> type }
return TypeFunPtr.make(P.match("?"),ts.asAry(),t0);
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/type/Type.java b/chapter29/src/main/java/com/seaofnodes/simple/type/Type.java
index 6c8a3e8d..0f655f49 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/type/Type.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/type/Type.java
@@ -623,11 +623,12 @@ public class Type /*implements Cloneable*/ {
private static int[] remapBits(GlobalBits fileBits, GlobalBits bits, int[] fileLocals, int firstMapped ) {
if( fileLocals == XInt.EMPTY || fileLocals == XInt.FULL )
return fileLocals;
- assert !XInt.isHigh(fileLocals);
+ boolean complement = XInt.isHigh(fileLocals);
+ if( complement ) fileLocals = XInt.dual(fileLocals);
int[] locals = XInt.EMPTY;
for( int fileLocal = XInt.next(fileLocals,-1); fileLocal >=0; fileLocal = XInt.next(fileLocals,fileLocal) )
locals = XInt.make(locals,fileLocal < firstMapped ? fileLocal : bits.map(fileBits,fileLocal));
- return locals;
+ return complement ? XInt.dual(locals) : locals;
}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java
index 56f47bff..19ec5019 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java
@@ -79,6 +79,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/chapter29/src/main/java/com/seaofnodes/simple/type/TypeMem.java b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeMem.java
index 79543e56..5faa7107 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/type/TypeMem.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeMem.java
@@ -104,15 +104,24 @@ public class TypeMem extends Type {
// 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) {
- TypeStruct ts = t instanceof TypeMemPtr ptr ? ptr._obj : t instanceof TypeStruct obj ? obj : null;
- if( ts!=null && ts._open && ts._fref && !ts.isAry() )
+ 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/chapter29/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java
index 943710d1..d9e11f8f 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java
@@ -91,6 +91,25 @@ public class TypeMemPtr extends TypeNil {
return partial==_partial ? this : make(_nil,_obj,_one,_pub,_ro,partial);
}
+ // A partial declaration promises no initialized slots. Actual callers may
+ // supply a sharper shape; the declaration must not manufacture those facts.
+ public TypeMemPtr partialBound() { return _partial ? makeFrom(_obj.makeInit()) : this; }
+
+ // Project field initialization onto declared field types, not stored
+ // constants. A pointer field being set does not complete its referent.
+ public TypeMemPtr initialized(TypeStruct state) {
+ TypeStruct shape = _obj;
+ for( Field fld : _obj._fields ) {
+ Field actual = state.field(fld._fname);
+ Type t = actual==null ? TypeScalar.BOT : actual._t;
+ if( t==Type.BOTTOM || t==TypeScalar.BOT ) t = TypeScalar.BOT;
+ else if( t.isHigh() ) t = TypeScalar.TOP;
+ else t = fld._t==Type.BOTTOM ? TypeScalar.BOT : fld._t==Type.TOP ? TypeScalar.TOP : fld._t;
+ shape = shape.replace(fld.makeFrom(t));
+ }
+ return makeFrom(shape);
+ }
+
// Conservative on unresolved scalar/pointer families; null is complete.
public static boolean mayBePartial(Type t) {
return t instanceof TypeMemPtr ptr ? ptr._partial
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/type/TypeStruct.java b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
index d699e365..7c9758bd 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
+++ b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
@@ -99,7 +99,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.
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/type/XInt.java b/chapter29/src/main/java/com/seaofnodes/simple/type/XInt.java
index 3e74df84..06c9d466 100644
--- a/chapter29/src/main/java/com/seaofnodes/simple/type/XInt.java
+++ b/chapter29/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);
@@ -203,7 +204,7 @@ public abstract class XInt {
}
public static int[] packed(BAOS bais) {
int len = bais.packed1();
- int[] xs = free(len);
+ int[] xs = alloc(len);
for( int i=1; i<len; i++ )
xs[i] = bais.packed4();
return intern(xs);
@@ -230,7 +231,7 @@ public abstract class XInt {
public static int[] remap( int[] xs, AryInt map ) {
if( xs==FULL ) return xs;
assert !isHigh(xs);
- int[] ys = free(xs.length);
+ int[] ys = alloc(xs.length);
for( int bit = next(xs,0); bit >=0; bit = next(xs,bit) ) {
int bat = map.at(bit);
ys[idx(bat)] |= mask(bat);
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/Chapter20Test.java b/chapter29/src/test/java/com/seaofnodes/simple/Chapter20Test.java
index 6cf92dc3..f5eb2b36 100644
--- a/chapter29/src/test/java/com/seaofnodes/simple/Chapter20Test.java
+++ b/chapter29/src/test/java/com/seaofnodes/simple/Chapter20Test.java
@@ -242,13 +242,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++ )
@@ -256,11 +249,19 @@ 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));
+};
+
""";
- // The implicit return exposes _hashCodeString, so retain its body too.
- testCPU(src,"x86_64_v2", "SystemV",21,null);
- testCPU(src,"riscv" , "SystemV",9,null);
- testCPU(src,"arm" , "SystemV",7,null);
+ // Return the public hashCode wrapper; keep its helper private.
+ testCPU(src,"x86_64_v2", "SystemV",12,null);
+ testCPU(src,"riscv" , "SystemV",8,null);
+ testCPU(src,"arm" , "SystemV",6,null);
}
@Test
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/chapter29/src/test/java/com/seaofnodes/simple/Chapter21Test.java
index f1dc54e2..e15c2581 100644
--- a/chapter29/src/test/java/com/seaofnodes/simple/Chapter21Test.java
+++ b/chapter29/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,11 +375,18 @@ 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));
+};
""";
- // The implicit return exposes _hashCodeString, so retain its body too.
- testCPU(src,"x86_64_v2", "SystemV",21,null);
- testCPU(src,"riscv" , "SystemV",9,null);
- testCPU(src,"arm" , "SystemV",7,null);
+ // Return the public hashCode wrapper; keep its helper private.
+ testCPU(src,"x86_64_v2", "SystemV",12,null);
+ testCPU(src,"riscv" , "SystemV",8,null);
+ testCPU(src,"arm" , "SystemV",6,null);
}
@Test public void testStringExport() throws IOException {
@@ -408,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++ )
@@ -422,8 +415,15 @@ 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", "",21);
+ TestC.runC(src, "stringHash", "",12);
}
@Test public void testLoop2() {
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/Chapter25Test.java b/chapter29/src/test/java/com/seaofnodes/simple/Chapter25Test.java
index f5eba8f5..5f615282 100644
--- a/chapter29/src/test/java/com/seaofnodes/simple/Chapter25Test.java
+++ b/chapter29/src/test/java/com/seaofnodes/simple/Chapter25Test.java
@@ -5,6 +5,7 @@ import com.seaofnodes.isa.eval.EvalRisc5;
import com.seaofnodes.isa.eval.EvalArm64;
import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.parser.Parser;
import com.seaofnodes.simple.codegen.RegAllocTestSupport.CheckedCodeGen;
import com.seaofnodes.simple.codegen.ElfReader;
import com.seaofnodes.simple.codegen.ParseAll;
@@ -12,6 +13,8 @@ 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.TypeMem;
+import com.seaofnodes.simple.type.TypeTuple;
import com.seaofnodes.simple.type.XInt;
import com.seaofnodes.simple.util.Ary;
import java.io.File;
@@ -73,21 +76,25 @@ public class Chapter25Test {
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));
@@ -101,25 +108,52 @@ public class Chapter25Test {
val _private = { int x -> x+1; };
return 0;
""").parse();
- int checked = 0;
+ int published = -1, unpublished = -1;
for( FunNode fun : code._linker ) {
if( fun==null || fun._name==null ) continue;
- if( fun._name.equals("_hidden") ) {
- // A public field can publish a privately named function.
- assertTrue(code.publicFIDX(fun.sig().fidx()));
- checked++;
- }
- if( fun._name.equals("_private") ) {
- assertFalse(code.publicFIDX(fun.sig().fidx()));
- checked++;
- }
+ if( fun._name.equals("_hidden") ) published = fun.sig().fidx();
+ if( fun._name.equals("_private") ) unpublished = fun.sig().fidx();
}
- assertEquals(2,checked);
+ assertTrue(published>=0 && unpublished>=0);
var clz = code.compunit()._clz;
- assertTrue(XInt.bit(code.publicAliases(),clz.field("visible")._alias));
- assertFalse(XInt.bit(code.publicAliases(),clz.field("_hidden")._alias));
- assertFalse(XInt.bit(code.publicAliases(),clz.field("_private")._alias));
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 testImportedEscapeExclusions() throws IOException {
+ Path build=Path.of("build/objs");
+ Files.createDirectories(build);
+ Path dir=Files.createTempDirectory(build,"escape_exclusions_");
+ CodeGen library=new CodeGen(null,dir.toString(),null,"Lib","""
+ val _hidden_noInline={int n -> n+100;};
+ val apply_noInline={{int->int} f,int n -> f(n);};
+ return apply_noInline;
+ """,123,true,TypeInteger.BOT).driver("x86_64_v2","win64",false,false);
+ int savedHidden=((com.seaofnodes.simple.type.TypeFunPtr)
+ library.compunit()._clz.field("_hidden_noInline")._t).fidx();
+ // Allocate unrelated IDs before loading Lib so saved exclusions must
+ // remap by owner/slot, not by their numeric positions in the file.
+ CodeGen code=new CodeGen(null,dir.toString(),new Ary<>(new String[]{dir.toString()}),
+ "Client","""
+ val _other={int n -> n-1;};
+ val inc={int n -> n+1;};
+ return Lib.apply_noInline(inc,arg);
+ """,987654321,true,TypeInteger.BOT).driver(CodeGen.Phase.TypeCheck);
+ var clz=(com.seaofnodes.simple.type.TypeStruct)Parser.TYPES.get("class:Lib");
+ int hidden=((com.seaofnodes.simple.type.TypeFunPtr)clz.field("_hidden_noInline")._t).fidx();
+ assertNotEquals(savedHidden,hidden);
+ TypeMem outside=((TypeTuple)code._start._type).mem();
+ assertTrue(XInt.isHigh(outside._escFs));
+ assertFalse(XInt.bit(outside._escFs,hidden));
+ assertEquals("42",Eval2.eval(code,41));
+ Files.delete(dir.resolve("Lib.o"));
+ Files.delete(dir);
}
@Test public void testNeverReturnPointerRiscV() {
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java b/chapter29/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java
index 88ebb6b7..dae64fb0 100644
--- a/chapter29/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java
+++ b/chapter29/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java
@@ -158,6 +158,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/chapter29/src/test/java/com/seaofnodes/simple/ConstructionGroupTest.java b/chapter29/src/test/java/com/seaofnodes/simple/ConstructionGroupTest.java
index b5bbfce8..e544fcad 100644
--- a/chapter29/src/test/java/com/seaofnodes/simple/ConstructionGroupTest.java
+++ b/chapter29/src/test/java/com/seaofnodes/simple/ConstructionGroupTest.java
@@ -41,10 +41,11 @@ public class ConstructionGroupTest {
}
@Test public void testIncompleteChildReadAfterInlining() {
- // This lookup supplies a live result. Inlining must not turn self
- // complete inside the body before checking the use of child.
- rejects(parent("child = new Child(self); payload = child.parent==null;")+
- "return new Parent(arg).payload;","incomplete object");
+ // Child's own slots are initialized, even though its parent remains
+ // partial. Reading that slot does not complete the parent reference.
+ String src = parent("child = new Child(self); payload = child.parent==null;")+
+ "return new Parent(arg).payload;";
+ assertEquals("0",Eval2.eval(new CodeGen(src).driver(CodeGen.Phase.TypeCheck),0));
}
@Test public void testInitializerFeedsConstructor() {
@@ -61,14 +62,14 @@ public class ConstructionGroupTest {
@Test public void testNoIncompleteLookupOrEscape() {
rejects(parent("payload = 1; child = new Child(self); read(child);")+"return 0;","ordinary function");
rejects(parent("payload = 1; child = new Child(self);").replace("parent = par;","parent = par; value = par.payload;")+
- "return 0;","incomplete object");
+ "return 0;","Might be null");
rejects("""
struct Box { Test.C? value; }; val box = new Box;
struct C { int~ x; new C = { -> box.value = self; x = 1; }; };
return 0;
""","ordinary object");
rejects("struct C { C next = self; int~ x; new C = { -> x = next.x; }; }; return new C().x;",
- "incomplete object");
+ "might not be initialized");
}
@Test public void testAliasesAndLoopBackedges() {
@@ -82,8 +83,8 @@ public class ConstructionGroupTest {
"return 0;","ordinary function");
}
- @Test public void testConstructorOnlyQualifier() {
- rejects("struct C {}; val f = { C ^c -> return 0; }; return 0;","constructor");
+ @Test public void testPartialQualifier() {
+ new CodeGen("struct C {}; val f = { C ^c -> return 0; }; return 0;").driver(CodeGen.Phase.TypeCheck);
rejects("struct C { new C = { int ^x -> }; }; return 0;","struct parameter");
rejects(parent("payload = 1; child = new Child(self);").replace("^par","par")+"return 0;","without '^'");
CodeGen code = new CodeGen(parent("payload = 1; child = new Child(self);")+"return 0;")
@@ -127,7 +128,7 @@ public class ConstructionGroupTest {
}
@Test public void testDeclaredQualifierAndOwnReceiver() {
- // A complete caller does not grant permission to read a ^ formal.
+ // A public constructor's unknown callers promise no initialized slots.
for( String body : new String[]{"value = par.payload;", "value = par.payload*0;",
"int x = par.payload; value = x*0;", "value = par.payload==par.payload;"} )
rejects("""
@@ -137,7 +138,7 @@ public class ConstructionGroupTest {
new Child = { Parent ^par -> BODY };
};
return new Child(new Parent).value;
- """.replace("BODY",body),"incomplete object");
+ """.replace("BODY",body),"might not be initialized");
// The self exception belongs to the receiver, not every pointer of its class.
rejects("""
struct C {
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/EscapeFlowTest.java b/chapter29/src/test/java/com/seaofnodes/simple/EscapeFlowTest.java
new file mode 100644
index 00000000..71a281b5
--- /dev/null
+++ b/chapter29/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/chapter29/src/test/java/com/seaofnodes/simple/PartialReadTest.java b/chapter29/src/test/java/com/seaofnodes/simple/PartialReadTest.java
new file mode 100644
index 00000000..a6230b69
--- /dev/null
+++ b/chapter29/src/test/java/com/seaofnodes/simple/PartialReadTest.java
@@ -0,0 +1,155 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.node.InitViewNode;
+import com.seaofnodes.simple.node.ConstructNode;
+import com.seaofnodes.simple.parser.Parser;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class PartialReadTest {
+ private static String program(String body, String helper) {
+ return "struct _C { int~ x; int~ y; new _C = { int n -> " + body +
+ " }; }; val _read_noInline = { _C ^p -> " + helper +
+ " }; return new _C(arg).y;";
+ }
+ private static void rejects(String source, String message) {
+ try {
+ new CodeGen(source).driver(CodeGen.Phase.TypeCheck);
+ fail("Accepted " + source);
+ } catch( Parser.ParseException e ) {
+ assertTrue(e.getMessage(),e.getMessage().contains(message));
+ }
+ }
+ @Test public void testLocalRead() {
+ String src = program("x=n; y=_read_noInline(self);", "return p.x;");
+ for( long seed : new long[]{1,123,987654321} ) {
+ CodeGen code = new CodeGen(src,seed,true).driver(CodeGen.Phase.TypeCheck);
+ assertNull(code._stop.walk(n -> n instanceof InitViewNode ? n : null));
+ assertEquals("42",Eval2.eval(code,42));
+ }
+ new CodeGen(src).driver(CodeGen.Phase.Encoding,TestC.CPU_PORT,TestC.CALL_CONVENTION,true,false,0);
+ }
+ @Test public void testRedundantViewsFoldBeforeTypeCheck() {
+ String src=program("x=n; y=0; y=_read_noInline(self);", "return p.x;");
+ CodeGen code=new CodeGen(src).driver(CodeGen.Phase.IterPostOpto);
+ assertNull(code._stop.walk(n -> n instanceof InitViewNode ? n : null));
+ assertNull(code._stop.walk(n -> n instanceof ConstructNode c && !c._partial ? n : null));
+ code.driver(CodeGen.Phase.TypeCheck);
+ assertEquals("31",Eval2.eval(code,31));
+
+ // Here the view still masks y. A valid program need not initialize
+ // every field before lending self, so survival is not itself an error.
+ code=new CodeGen(program("x=n; y=_read_noInline(self);", "return p.x;"))
+ .driver(CodeGen.Phase.IterPostOpto);
+ assertNotNull(code._stop.walk(n -> n instanceof InitViewNode ? n : null));
+ code.driver(CodeGen.Phase.TypeCheck);
+ assertEquals("31",Eval2.eval(code,31));
+ }
+ @Test public void testInliningKeepsBorrowRestriction() {
+ rejects("""
+ struct _C {
+ int !x;
+ new _C={ int n -> x=n; if(!n) _write(self); };
+ };
+ val _write={ !_C ^p -> if(p.x) p.x=9; return p.x; };
+ val a=new _C(0); val b=new _C(1);
+ return _write(b)+a.x;
+ ""","incomplete object");
+ }
+ @Test public void testMissingAndBranchField() {
+ for( String use : new String[]{"p.x", "p.x*0", "p.x==p.x"} )
+ rejects(program("y=_read_noInline(self); x=n;", "return "+use+";"),"might not be initialized");
+ rejects(program("if(n) x=n; y=_read_noInline(self); x=0;", "return p.x;"),"might not be initialized");
+ }
+ @Test public void testActualValueFromMemory() {
+ for( String body : new String[]{"x=1; val p=self; x=2; y=_read_noInline(p);",
+ "val p=self; x=2; y=_read_noInline(p);"} ) {
+ CodeGen code = new CodeGen(program(body,"return p.x;")).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("2",Eval2.eval(code,0));
+ }
+ }
+ @Test public void testBorrowedWrite() {
+ rejects(program("x=n; y=_read_noInline(self);", "p.x=9; return p.x;")
+ .replace("int~ x", "int !x").replace("_C ^p", "!_C ^p"),"incomplete object");
+ }
+ @Test public void testUnknownCallerCannotRead() {
+ rejects(program("x=n; y=_read_noInline(self);", "return p.x;")
+ .replace("_read_noInline","read_noInline"),"might not be initialized");
+ }
+ @Test public void testForwardingAndMergedCallers() {
+ String src = program("x=n; y=_read_noInline(self);", "return _next_noInline(p);")
+ + " val _next_noInline = { _C ^p -> return p.x; };";
+ assertEquals("17",Eval2.eval(new CodeGen(src).driver(CodeGen.Phase.TypeCheck),17));
+ rejects(program("y=_read_noInline(self); x=n; y=_read_noInline(self);", "return p.x;"),"might not be initialized");
+ }
+ @Test public void testLoops() {
+ String src = program("x=1; for(int i=0; i<n; i++) x+=2; y=_read_noInline(self);", "return p.x;");
+ for( long seed : new long[]{1,123,987654321} ) {
+ CodeGen code = new CodeGen(src,seed,true).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("7",Eval2.eval(code,3));
+ assertEquals("1",Eval2.eval(code,0));
+ }
+ rejects(program("for(int i=0; i<n; i++) x=n; y=_read_noInline(self); x=0;", "return p.x;"),"might not be initialized");
+ }
+ @Test public void testNoPublication() {
+ rejects("struct Box { Test._C? !p; }; val box=new Box; " +
+ program("x=n; y=_read_noInline(self);", "box.p=p; return p.x;"),"ordinary object");
+ }
+ @Test public void testConstructorReadAndRetention() {
+ String src = """
+ struct _Child {
+ Test._Parent parent;
+ int~ seen;
+ new _Child = { Test._Parent ^p -> parent=p; seen=p.x; };
+ };
+ struct _Parent {
+ int~ x;
+ _Child child;
+ new _Parent = { int n -> x=n; child=new _Child(self); };
+ };
+ val p=new _Parent(arg); return p.child.seen+p.child.parent.x;
+ """;
+ assertEquals("22",Eval2.eval(new CodeGen(src).driver(CodeGen.Phase.TypeCheck),11));
+ rejects(src.replace("x=n; child=new _Child(self);","child=new _Child(self); x=n;"),"might not be initialized");
+ }
+ @Test public void testInlineReadAndWrite() {
+ String src=program("x=n; y=_read_noInline(self);","return p.x;").replace("_noInline","");
+ assertEquals("19",Eval2.eval(new CodeGen(src).driver(CodeGen.Phase.TypeCheck),19));
+ rejects(src.replace("return p.x;","p.x=9; return p.x;").replace("int~ x","int !x").replace("_C ^p","!_C ^p"),"incomplete object");
+ rejects(src.replace("x=n; y=", "y=").replace("(self);", "(self); x=n;"),"might not be initialized");
+ }
+ @Test public void testArrayAndNestedReference() {
+ String src="""
+ struct _C {
+ int[] data;
+ int~ y;
+ new _C = { int n -> data=new int[1]; data[0]=n; y=_read_noInline(self); };
+ };
+ val _read_noInline={ _C ^p -> return p.data[0]; };
+ return new _C(arg).y;
+ """;
+ assertEquals("23",Eval2.eval(new CodeGen(src).driver(CodeGen.Phase.TypeCheck),23));
+ // A set reference slot is not proof that its referent is initialized.
+ rejects("struct D { int x=7; }; "+src.replace("int[] data","D data")
+ .replace("data=new int[1]; data[0]=n;","data=new D;")
+ .replace("p.data[0]","p.data.x"),"might not be initialized");
+ }
+ @Test public void testCompleteBorrowCannotPublishPartial() {
+ rejects("""
+ struct _C { int x=7; };
+ struct Box { _C? p; }; val box=new Box;
+ val _store={ !_C ^dst, _C ^src -> box.p=src; return dst.x; };
+ return _store(new _C,new _C);
+ ""","ordinary object");
+ }
+ @Test public void testSameClassMemory() {
+ String src=program("x=n; y=n ? _read_noInline(self) : x;",
+ "val other=new _C(0); return p.x+other.y;");
+ for( long seed : new long[]{1,123,987654321} ) {
+ CodeGen code=new CodeGen(src,seed,true).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("29",Eval2.eval(code,29));
+ assertEquals("0",Eval2.eval(code,0));
+ }
+ }
+}