summaryrefslogtreecommitdiff
path: root/src/main/java/com/seaofnodes/simple/node
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-08-10 11:11:18 -0700
committerCliff Click <cliffc@acm.org>2026-10-24 15:56:44 -0700
commitecd82ce8ebbefef223cd934cd87802f4d03c1867 (patch)
treed7cb8e837ec90a646c0b6e15dcb93dea6b10ffd3 /src/main/java/com/seaofnodes/simple/node
parentf5f0580331b4e29a0a0dc43d9885dfe0f0aed865 (diff)
downloadSimple-linear-chapter26.tar.gz
Simple-linear-chapter26.zip
Chapter 26: Construction and Private Memorylinear-chapter26
Co-authored-by: Hsiang-Ying Fu <66062782+fuhsnn@users.noreply.github.com> Co-authored-by: fuhsnn <66062782+fuhsnn@users.noreply.github.com> Co-authored-by: Symious <14933944+symious@users.noreply.github.com> Co-authored-by: Jun Kawasaki <jun784@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'src/main/java/com/seaofnodes/simple/node')
-rw-r--r--src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/node/CallEndNode.java6
-rw-r--r--src/main/java/com/seaofnodes/simple/node/CallNode.java26
-rw-r--r--src/main/java/com/seaofnodes/simple/node/EscapeMach.java12
-rw-r--r--src/main/java/com/seaofnodes/simple/node/EscapeNode.java58
-rw-r--r--src/main/java/com/seaofnodes/simple/node/FunNode.java15
-rw-r--r--src/main/java/com/seaofnodes/simple/node/IfNode.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/node/LoadNode.java50
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MemMergeNode.java15
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MemOpNode.java4
-rw-r--r--src/main/java/com/seaofnodes/simple/node/NewNode.java11
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ReturnNode.java8
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ScopeNode.java27
-rw-r--r--src/main/java/com/seaofnodes/simple/node/StoreNode.java14
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java3
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java3
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java3
17 files changed, 225 insertions, 34 deletions
diff --git a/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
index 8399ed09..e6f70c64 100644
--- a/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
@@ -101,6 +101,7 @@ public class BulkMemPhiNode extends PhiNode {
case ConstantNode con -> 0;
case MemMergeNode mmm -> missingAlias(mmm);
case MemOpNode mem -> unsplit(mem._alias);
+ case EscapeNode esc -> unsplit(esc._field._alias);
case BulkMemPhiNode bulk -> missingAlias(bulk);
default -> throw new AssertionError("Unexpected memory input "+n);
};
@@ -118,6 +119,7 @@ public class BulkMemPhiNode extends PhiNode {
case MemMergeNode mmm -> missingAlias(mmm);
case BulkMemPhiNode bulk -> missingAlias(bulk);
case MemOpNode mem -> unsplit(mem._alias);
+ case EscapeNode esc -> unsplit(esc._field._alias);
case MemPhiNode phi -> unsplit(phi._alias);
default -> {
assert use instanceof ReturnNode || use instanceof CallNode || use instanceof ParmNode
diff --git a/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
index aa586b17..604bb9da 100644
--- a/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
@@ -163,7 +163,8 @@ public class CallEndNode extends CFGNode implements MultiNode {
}
for( int i=0; i<len; i++ ) {
Node n = TINYBODY[i];
- if( n instanceof StoreNode || n instanceof MemMergeNode ) {
+ if( n instanceof StoreNode || n instanceof MemMergeNode ||
+ n instanceof NewNode || n instanceof ProjNode || n instanceof EscapeNode ) {
for( Node in : n._inputs ) copy[i].addDef(mapped(in,copy));
CodeGen.CODE.add(copy[i]);
}
@@ -211,7 +212,8 @@ public class CallEndNode extends CFGNode implements MultiNode {
if( n==ret ) continue;
if( n instanceof ParmNode parm && parm.region()!=fun ) return 0;
if( n!=fun && !(n instanceof ParmNode) &&
- !(n instanceof StoreNode) && !(n instanceof MemMergeNode) )
+ !(n instanceof StoreNode) && !(n instanceof MemMergeNode) &&
+ !(n instanceof NewNode) && !(n instanceof ProjNode) && !(n instanceof EscapeNode) )
return 0;
if( n.nOuts()>TINYBODY.length ) return 0;
for( Node use : n._outputs ) {
diff --git a/src/main/java/com/seaofnodes/simple/node/CallNode.java b/src/main/java/com/seaofnodes/simple/node/CallNode.java
index b2708531..21f438da 100644
--- a/src/main/java/com/seaofnodes/simple/node/CallNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/CallNode.java
@@ -6,6 +6,7 @@ import com.seaofnodes.simple.IterPeeps;
import com.seaofnodes.simple.Parser;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.XInt;
import com.seaofnodes.simple.type.TypeFunPtr;
import com.seaofnodes.simple.util.Utils;
@@ -16,9 +17,12 @@ public class CallNode extends CFGNode {
// Source location for late reported errors
public final Parser.Lexer _loc;
+ // A forward field call records its receiver before its declaration tells
+ // us whether this is a method or an ordinary function stored in a field.
+ public String _method;
public CallNode(Parser.Lexer loc, Node... nodes) { super(nodes); _loc = loc; }
- public CallNode(CallNode call) { super(call); _loc = call._loc; }
+ public CallNode(CallNode call) { super(call); _loc = call._loc; _method=call._method; }
@Override public String label() { return "Call"; }
@@ -90,6 +94,15 @@ public class CallNode extends CFGNode {
public Node idealize() {
CallEndNode cend = cend();
if( cend==null ) return null; // Still building
+ if( _method!=null ) {
+ if( !(arg(2)._type instanceof com.seaofnodes.simple.type.TypeMemPtr ptr) ) return null;
+ var field=ptr._obj.field(_method);
+ if( field==null ) return null;
+ if( !(fptr()._type instanceof TypeFunPtr sig) ) return null;
+ if( !field._final || CodeGen.CODE.externFunc(sig)!=null ) delDef(2);
+ _method=null;
+ return this;
+ }
// Link: call calls target function. Linking makes the target FunNode
// point to this Call, and all his Parms point to the call arguments;
@@ -97,12 +110,11 @@ public class CallNode extends CFGNode {
Node progress = null;
if( fptr()._type instanceof TypeFunPtr tfp && tfp.nargs() == nargs() ) {
// If fidxs is negative, then infinite unknown functions
- long fidxs = tfp.fidxs();
- if( fidxs > 0 ) {
+ int[] fidxs = tfp.fidxs();
+ if( !XInt.isHigh(fidxs) ) {
// Wipe out the return which matching in the linker table
- // Walk the (63 max) bits and link
- for( ; fidxs!=0; fidxs = TypeFunPtr.nextFIDX(fidxs) ) {
- int fidx = Long.numberOfTrailingZeros(fidxs);
+ // Walk the function identities and link each possible callee.
+ for( int fidx=XInt.next(fidxs,-1); fidx>=0; fidx=XInt.next(fidxs,fidx) ) {
TypeFunPtr tfp0 = tfp.makeFrom(fidx);
FunNode fun = CodeGen.CODE.link(tfp0);
if( fun!=null && !fun._folding && !linked(fun) )
@@ -166,7 +178,7 @@ public class CallNode extends CFGNode {
return Parser.error( "Argument #" + i + " isa " + arg(i+2)._type + ", but must be a " + tfp.arg(i), _loc );
}
- if( tfp.fidxs() < 0 )
+ if( XInt.isHigh(tfp.fidxs()) )
throw Utils.TODO(); // Infinite unknown TFPs? Should be fairly precise CG
return null;
diff --git a/src/main/java/com/seaofnodes/simple/node/EscapeMach.java b/src/main/java/com/seaofnodes/simple/node/EscapeMach.java
new file mode 100644
index 00000000..b6ec04e9
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/EscapeMach.java
@@ -0,0 +1,12 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.*;
+
+/** Publication orders memory; it needs no runtime instruction or register. */
+public class EscapeMach extends EscapeNode implements MachNode {
+ public EscapeMach(EscapeNode esc) { super(esc); _inputs.set(3,null); }
+ @Override public String op() { return label(); }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return null; }
+ @Override public void encoding(Encoding enc) { }
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/EscapeNode.java b/src/main/java/com/seaofnodes/simple/node/EscapeNode.java
new file mode 100644
index 00000000..75ecce64
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/EscapeNode.java
@@ -0,0 +1,58 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.*;
+
+/** Publish one initialized field: {control, public memory, private memory, self}.
+ * This is an explicit construction boundary, not escape discovery. The next
+ * chapter will discover which objects and functions unknown code can reach.
+ */
+public class EscapeNode extends Node {
+ public Field _field;
+ private final Parser.Lexer _loc;
+ public EscapeNode(Parser.Lexer loc, Field field, Node ctrl, Node pub, Node priv, Node self) {
+ super(ctrl,pub,priv,self);
+ _field=field;
+ _loc=loc;
+ }
+ public EscapeNode(EscapeNode esc) { super(esc); _field=esc._field; _loc=esc._loc; }
+ public Node pub() { return in(1); }
+ public Node priv() { return in(2); }
+ public Node self() { return in(3); }
+ @Override public boolean isMem() { return true; }
+ @Override public String label() { return "Escape_"+_field._fname; }
+
+ private Type value() {
+ return MemMergeNode.contents(priv(),_field._alias,this);
+ }
+ @Override public Type compute() {
+ if( in(0)._type.isHigh() || priv()._type.isHigh() || pub()._type.isHigh() ) return TypeMem.TOP;
+ return TypeMem.make(_field._alias,value().meet(MemMergeNode.contents(pub(),_field._alias,this)).join(_field._t));
+ }
+ @Override public Node idealize() {
+ if( priv() instanceof MemMergeNode merge ) {
+ setDef(2,merge.alias(_field._alias));
+ return this;
+ }
+ if( pub() instanceof MemMergeNode merge ) {
+ setDef(1,merge.alias(_field._alias));
+ return this;
+ }
+ return null;
+ }
+ @Override public Parser.ParseException err() {
+ if( in(0)._type.isHigh() ) return null;
+ if( value()==Type.BOTTOM ) {
+ String name = self()._type instanceof TypeMemPtr p ? p._obj._name : "Object";
+ return Parser.error("'"+name+"' is not fully initialized, field '"+_field._fname+"' needs to be set in a constructor",_loc);
+ }
+ return null;
+ }
+ @Override boolean _upgradeType(java.util.HashMap<String,Type> types) {
+ Field next = (Field)_field.upgradeType(types);
+ if( next==_field ) return false;
+ unlock(); _field=next; return true;
+ }
+ @Override public boolean eq(Node n) { return _field==((EscapeNode)n)._field; }
+ @Override int hash() { return _field.hashCode(); }
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/FunNode.java b/src/main/java/com/seaofnodes/simple/node/FunNode.java
index 1ba57e85..2850ba92 100644
--- a/src/main/java/com/seaofnodes/simple/node/FunNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/FunNode.java
@@ -5,6 +5,7 @@ import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.codegen.Encoding;
import com.seaofnodes.simple.codegen.RegMask;
import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeMem;
import com.seaofnodes.simple.type.TypeFunPtr;
import com.seaofnodes.simple.type.TypeTuple;
import com.seaofnodes.simple.util.SB;
@@ -13,6 +14,9 @@ import java.util.BitSet;
import static com.seaofnodes.simple.codegen.CodeGen.CODE;
public class FunNode extends RegionNode {
+ // Generated allocation/initializer entry points cannot be called by users.
+ public boolean _constructor;
+
// When set true, this Call/CallEnd/Fun/Return is being trivially inlined
boolean _folding;
@@ -29,6 +33,7 @@ public class FunNode extends RegionNode {
if( fun!=null ) {
_sig = fun.sig();
_name = fun._name;
+ _constructor = fun._constructor;
} else {
_sig = TypeFunPtr.BOT;
_name = "";
@@ -73,6 +78,13 @@ public class FunNode extends RegionNode {
// Signature can improve over time
public TypeFunPtr sig() { return _sig; }
+ // Hidden initializer captures are discovered while parsing its body,
+ // before any call or function address has been made for this function.
+ public void addParseArg(Type type) {
+ Type[] args=java.util.Arrays.copyOf(_sig._sig,_sig.nargs()+1);
+ args[args.length-1]=type;
+ _sig=TypeFunPtr.make((byte)2,false,args,_sig.ret(),_sig.fidxs());
+ }
public void setSig( TypeFunPtr sig ) {
assert sig.isa(_sig);
if( _sig != sig ) {
@@ -114,7 +126,8 @@ public class FunNode extends RegionNode {
}
// Upgrade inferred or user-written return type to actual
- if( _ret!=null && _ret._type instanceof TypeTuple tt && tt.ret() != _sig.ret() ) {
+ if( _ret!=null && _ret._type instanceof TypeTuple tt &&
+ !(tt.ret() instanceof TypeMem) && tt.ret() != _sig.ret() ) {
setSig(_sig.makeFrom(tt.ret()));
return this;
}
diff --git a/src/main/java/com/seaofnodes/simple/node/IfNode.java b/src/main/java/com/seaofnodes/simple/node/IfNode.java
index 3f21417e..f1b76dc2 100644
--- a/src/main/java/com/seaofnodes/simple/node/IfNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/IfNode.java
@@ -46,7 +46,7 @@ public class IfNode extends CFGNode implements MultiNode {
// If constant is 0 then false branch is reachable
// Else true branch is reachable
if( t.isConstant() )
- return (t==Type.NIL || t==TypeInteger.ZERO || (t instanceof TypeFunPtr tfp && tfp._fidxs==0) ) ? TypeTuple.IF_FALSE : TypeTuple.IF_TRUE;
+ return (t==Type.NIL || t==TypeInteger.ZERO || (t instanceof TypeFunPtr tfp && tfp._fidxs==com.seaofnodes.simple.type.XInt.EMPTY) ) ? TypeTuple.IF_FALSE : TypeTuple.IF_TRUE;
// If adding a zero makes a difference, the predicate must not have a zero/null
if( !t.makeZero().isa(t) )
return TypeTuple.IF_TRUE;
diff --git a/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/src/main/java/com/seaofnodes/simple/node/LoadNode.java
index 24a2f9b8..b9dd2af6 100644
--- a/src/main/java/com/seaofnodes/simple/node/LoadNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/LoadNode.java
@@ -13,6 +13,7 @@ import java.util.BitSet;
* in chapter 10 this means Struct fields.
*/
public class LoadNode extends MemOpNode {
+ private final boolean _private;
/**
* Load a value from a ptr.field.
@@ -23,9 +24,13 @@ public class LoadNode extends MemOpNode {
* @param off The offset inside the struct base
*/
public LoadNode(Parser.Lexer loc, String name, int alias, Node mem, Node ptr, Node off) {
+ this(loc,name,alias,mem,ptr,off,false);
+ }
+ public LoadNode(Parser.Lexer loc, String name, int alias, Node mem, Node ptr, Node off, boolean priv) {
// The field declaration will supply storage width once the receiver
// is known. A parser guess must not bound the value of this Load.
super(loc, name, alias, true, Type.BOTTOM, mem, ptr, off);
+ _private=priv;
}
@Override boolean canDrop(MemOpNode other, Node dep) {
@@ -62,9 +67,18 @@ public class LoadNode extends MemOpNode {
if( ptr.nullable() ) return Type.BOTTOM;
Field field = ptr._obj.field(_name);
if( field==null ) return TypeScalar.BOT;
+ if( field._t instanceof TypeFunPtr ctor && ctor.ret() instanceof TypeMem ) return TypeScalar.BOT;
Type t = field._t;
if( t instanceof TypeConAry ary ) t=ary.elem();
if( _alias!=1 ) t=t.join(MemMergeNode.contents(mem(),_alias,this));
+ // An unknown helper result does not mean the field is unwritten.
+ // Keep its declared bound until private memory proves otherwise;
+ // this Load can be forwarded into an ordinary typed value Phi.
+ if( uninitialized() ) t=Type.BOTTOM;
+ if( m instanceof TypeMem pm && pm._t instanceof TypeStruct shape ) {
+ Field value = shape.field(_name);
+ t = value==null ? Type.BOTTOM : value._t;
+ }
if( ptr.isFinal() ) t=t.makeRO();
return t==Type.TOP ? TypeScalar.TOP : t==Type.BOTTOM ? TypeScalar.BOT : t;
}
@@ -87,6 +101,9 @@ public class LoadNode extends MemOpNode {
}
}
if( _alias==1 ) return null;
+ // A required field has no value yet. In particular, do not turn a
+ // read of its calloc bytes into a legal null/zero value.
+ if( uninitialized() ) return null;
if( mem() instanceof CastNode cast ) {
setDef(1,cast.in(1));
@@ -146,6 +163,24 @@ public class LoadNode extends MemOpNode {
return null;
}
+ private boolean uninitialized() {
+ if( !_private || _alias==1 ) return false;
+ Node mem=mem();
+ if( mem instanceof MemMergeNode merge ) mem=merge.alias(_alias);
+ return mem._type instanceof TypeMem m &&
+ ((m._alias==_alias && m._t==Type.BOTTOM) ||
+ (m._t instanceof TypeStruct s && s.field(_name)!=null && s.field(_name)._t==Type.BOTTOM));
+ }
+
+ @Override public Parser.ParseException err() {
+ if( ptr()._type instanceof TypeMemPtr ptr ) {
+ Field f=ptr._obj.field(_name);
+ if( f!=null && f._t instanceof TypeFunPtr ctor && ctor.ret() instanceof TypeMem )
+ return Parser.error("A constructor is only called by new",_loc);
+ }
+ return uninitialized() ? Parser.error("Cannot read uninitialized field '"+_name+"'",_loc) : super.err();
+ }
+
// Load a flavored zero from a New
private Node zero(NewNode nnn) {
TypeStruct ts = nnn._ptr._obj;
@@ -165,6 +200,9 @@ public class LoadNode extends MemOpNode {
switch( mem ) {
case MemMergeNode merge: mem=merge.alias(_alias); break;
case CastNode cast: mem=cast.in(1); break;
+ case EscapeNode esc:
+ mem = ptr==address(esc.self()) ? esc.priv() : esc.pub();
+ break;
case StoreNode st:
// A symbolic Store may write this field. Wait for its alias;
// skipping it can make a compound assignment read the old value.
@@ -177,7 +215,11 @@ public class LoadNode extends MemOpNode {
break;
case ProjNode proj:
if( !(proj.in(0) instanceof NewNode nnn) ) return null;
- if( ptr instanceof ProjNode p && p.in(0)==nnn ) return nnn;
+ if( ptr instanceof ProjNode p && p.in(0)==nnn ) {
+ if( nnn.mem()==null && proj._type instanceof TypeMem m &&
+ m._t instanceof TypeStruct shape && shape.field(_name)._t==Type.BOTTOM ) return null;
+ return nnn;
+ }
if( !(ptr instanceof ProjNode p && p.in(0) instanceof NewNode) ) return null;
mem=nnn.mem(); // Distinct allocations cannot overlap.
break;
@@ -222,7 +264,7 @@ public class LoadNode extends MemOpNode {
}
return folded(win).peephole();
}
- LoadNode ld=new LoadNode(_loc,_name,_alias,mem,ptr,off());
+ LoadNode ld=new LoadNode(_loc,_name,_alias,mem,ptr,off(),_private);
ld.setDef(0,in(0));
return ld.peephole();
}
@@ -255,11 +297,13 @@ public class LoadNode extends MemOpNode {
// Read-Only is a deep property, and cannot be cast-away
private Node castRO(Node rez) {
- if( (ptr()._type.isFinal() || _declaredType.isFinal()) && !rez._type.isFinal() )
+ if( (ptr()._type.isFinal() || (_declaredType instanceof TypeMemPtr p && p._ro)) && !rez._type.isFinal() )
return new ReadOnlyNode(rez).peephole();
return rez;
}
+ @Override public boolean eq(Node n) { return _private==((LoadNode)n)._private && super.eq(n); }
+
// When a load bypasses a store, the store might truncate bits - and the
// load will need to zero/sign-extend.
private Node extend(Node val) {
diff --git a/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
index 7a329117..e405d152 100644
--- a/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
@@ -42,6 +42,10 @@ public class MemMergeNode extends Node {
if( mem instanceof CastNode cast ) return contents(cast.in(1),alias,dep);
if( mem instanceof MemMergeNode merge )
return contents(merge.alias(alias),alias,dep);
+ if( mem._type instanceof TypeMem mt && mt._t instanceof TypeStruct shape ) {
+ int index = shape.findAlias(alias);
+ return index==-1 ? Type.BOTTOM : shape._fields[index]._t;
+ }
if( mem instanceof ProjNode proj && proj.in(0) instanceof NewNode nnn ) {
assert proj._idx==1 && nnn.field(alias)!=null;
dep.addDep(nnn);
@@ -69,6 +73,17 @@ public class MemMergeNode extends Node {
@Override public Type compute() {
+ if( in(1)!=null && in(1)._type instanceof TypeMem base && base._t instanceof TypeStruct shape ) {
+ // At a helper boundary, package the independent field slices for
+ // the call result. Constructor loops themselves carry local values.
+ Field[] fields=shape._fields.clone();
+ for( int i=0; i<fields.length; i++ ) {
+ Field f=fields[i];
+ Node slice=f._alias<nIns() ? in(f._alias) : null;
+ if( slice!=null ) fields[i]=f.makeFrom(contents(slice,f._alias,this));
+ }
+ return TypeMem.makePrivate(TypeStruct.make(shape._name,shape._open,fields));
+ }
for( Node n : _inputs )
if( n != null && !n._type.isHigh() )
return TypeMem.BOT;
diff --git a/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
index 1f0fc4d3..df1585ff 100644
--- a/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
@@ -130,7 +130,9 @@ public abstract class MemOpNode extends Node {
if( visit.get(mem._nid) ) return;
visit.set(mem._nid);
for( Node use : mem._outputs ) {
- if( use instanceof MemMergeNode merge ) {
+ if( use instanceof EscapeNode esc && esc._field._alias==_alias ) {
+ antiDeps(esc,deps,visit);
+ } else if( use instanceof MemMergeNode merge ) {
Node slice = _alias<merge.nIns() ? merge.in(_alias) : null;
if( (slice==mem) || (slice==null && merge.in(1)==mem) )
antiDeps(merge,deps,visit);
diff --git a/src/main/java/com/seaofnodes/simple/node/NewNode.java b/src/main/java/com/seaofnodes/simple/node/NewNode.java
index bdcdfdfc..a43efba2 100644
--- a/src/main/java/com/seaofnodes/simple/node/NewNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/NewNode.java
@@ -8,9 +8,10 @@ import com.seaofnodes.simple.util.SB;
import com.seaofnodes.simple.util.Utils;
-/** Allocate a zeroed object. Inputs {ctrl, $mem, size};
- * results {ptr, $mem}.
- * The memory input and result cover only the aliases in the allocated struct.
+/** Allocate a zeroed object. Inputs {ctrl, null, size}; results {ptr, private memory}.
+ * It starts outside the public heap. Helper exits store the field values, and
+ * Escape publishes the completed fields. Slot 1 remains reserved for memory;
+ * a non-null input supports the preceding chapter's graph fixtures.
*/
public class NewNode extends Node implements MultiNode {
@@ -22,7 +23,7 @@ public class NewNode extends Node implements MultiNode {
_ptr = ptr;
assert nodes.length==3;
assert nodes[0]._type==Type.CONTROL || nodes[0]._type==Type.XCONTROL;
- assert nodes[1]._type instanceof TypeMem;
+ assert nodes[1]==null || nodes[1]._type instanceof TypeMem;
assert nodes[2]._type instanceof TypeInteger || nodes[2]._type==Type.NIL;
}
@@ -56,7 +57,7 @@ public class NewNode extends Node implements MultiNode {
public TypeTuple compute() {
return in(0)._type.isHigh()
? TypeTuple.make(_ptr.makeHigh(),TypeMem.TOP)
- : TypeTuple.make(_ptr,TypeMem.BOT);
+ : TypeTuple.make(_ptr,mem()==null ? TypeMem.makePrivate(TypeMem.zeroed(_ptr._obj)) : TypeMem.BOT);
}
@Override
diff --git a/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
index 8fea803d..ce3feef2 100644
--- a/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
@@ -55,7 +55,10 @@ public class ReturnNode extends CFGNode {
// Upgrade signature based on return type
Type ret = expr()._type;
TypeFunPtr fcn = _fun.sig();
- if( ret != fcn.ret() && ret.isa(fcn.ret()) )
+ // Private field contents flow on the linked Return/CallEnd edges.
+ // Keep the helper's interface at MemBot; a current memory snapshot
+ // is not a new declaration for its self type or its methods.
+ if( !(ret instanceof TypeMem) && ret != fcn.ret() && ret.isa(fcn.ret()) )
_fun.setSig(fcn.makeFrom(ret));
// If dead (cannot be reached; infinite loop), kill the exit values
@@ -92,7 +95,7 @@ public class ReturnNode extends CFGNode {
RegionNode r = (RegionNode)ctrl();
// Assert that the Phis are in particular outputs; not reordered or shuffled
PhiNode mem = (PhiNode)r.out(0); assert mem.isMem();
- PhiNode rez = (PhiNode)r.out(1); assert !rez.isMem();
+ PhiNode rez = (PhiNode)r.out(1); assert !rez.isMem() || fun().sig().ret() instanceof TypeMem;
// Pop "inProgress" null off
r ._inputs.pop();
mem._inputs.pop();
@@ -108,6 +111,7 @@ public class ReturnNode extends CFGNode {
}
@Override public Parser.ParseException err() {
+ if( expr()._type instanceof TypeMem ) return null; // Hidden initializer result.
if( ctrl()._type != Type.CONTROL ) return null; // Exit path is dead
if( expr()._type==TypeScalar.BOT ) {
int kinds = returnKinds(expr(),new java.util.BitSet());
diff --git a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
index 3c1e9776..f65a822d 100644
--- a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
@@ -39,7 +39,10 @@ public class ScopeNode extends Node {
// Basic block scoping
public static class Block extends Kind { }
// Function scope
- public static class Func extends Kind { }
+ public static class Func extends Kind {
+ public final boolean _constructor;
+ public Func(boolean constructor) { _constructor=constructor; }
+ }
// Struct definition, mapping types to fields
public static class Define extends Kind { public Define(TypeMemPtr tmp) { _tmp=tmp; } public final TypeMemPtr _tmp; }
// Struct allocation, mapping values to fields
@@ -137,15 +140,25 @@ public class ScopeNode extends Node {
}
- // Allocation fields live in the scope immediately outside the constructor
- // block. Nested blocks retain that permission; surrounding bindings do not.
+ // Fields are writable local values until construction ends, even when the
+ // published field is final. Ordinary methods and final local variables do
+ // not inherit that permission. This also decides which loop Phis we need.
public boolean canInit(Var v) {
- for( int i=_kinds._len-1; i>0; i-- )
- if( _kinds.at(i) instanceof Kind.Alloc )
+ for( int i=_kinds._len-1; i>=0; i-- ) {
+ Kind k=_kinds.at(i);
+ if( k instanceof Kind.Alloc )
return v._idx >= _kinds.at(i-1)._lexSize && v._idx < _kinds.at(i)._lexSize;
+ if( k instanceof Kind.Define ) return kind(v)==k;
+ if( k instanceof Kind.Func fun )
+ return fun._constructor && i>0 && _kinds.at(i-1) instanceof Kind.Define def && kind(v)==def;
+ }
return false;
}
+ private boolean loopVar(Var v) {
+ return !v._final || (canInit(v) && !v.type().isHighOrConst());
+ }
+
public boolean inConstructor() { return _kinds.last() instanceof Kind.Define; }
public boolean inAllocation () { return _kinds.last() instanceof Kind.Alloc ; }
public boolean inFunction () { return _kinds.last() instanceof Kind.Func ; }
@@ -294,7 +307,7 @@ public class ScopeNode extends Node {
// Memory is one binding, with the same lazy loop Phi as scalar names.
for( int i=1; i<nIns(); i++ )
- dup.addDef(loop && !_vars.at(i)._final ? this : in(i));
+ dup.addDef(loop && loopVar(var(i)) ? this : in(i));
return dup;
}
@@ -356,7 +369,7 @@ public class ScopeNode extends Node {
// Fill in the backedge of any inserted Phis
void _endLoop( ScopeNode scope, Node back, Node exit ) {
for( int i=1; i<nIns(); i++ ) {
- if( _vars.at(i)._final ) continue; // Final vars did not get modified in the loop
+ if( !loopVar(var(i)) ) continue;
if( _vars.at(i).type().isHighOrConst() ) continue; // Cannot lift higher than a constant, so no Phi
if( back.in(i) != scope ) {
PhiNode phi = (PhiNode)in(i);
diff --git a/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/src/main/java/com/seaofnodes/simple/node/StoreNode.java
index a4abaa1d..caba7bdd 100644
--- a/src/main/java/com/seaofnodes/simple/node/StoreNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/StoreNode.java
@@ -45,6 +45,10 @@ public class StoreNode extends MemOpNode {
public Type compute() {
if( _alias==1 ) return TypeMem.BOT;
Type val = val()._type;
+ // Initializing Stores are emitted at helper exits for a fresh object.
+ // They replace this private field; Escape later joins it with public
+ // memory. Keep an unwritten BOTTOM visible to the completion check.
+ if( _init ) return TypeMem.make(_alias,val);
assert !_declaredType.isFRef();
Type t = val.join(_declaredType).meet(MemMergeNode.contents(mem(),_alias,this));
return TypeMem.make(_alias,t);
@@ -72,6 +76,10 @@ public class StoreNode extends MemOpNode {
return this;
}
+ if( _init && mem() instanceof ProjNode p && p.in(0) instanceof NewNode &&
+ val()._type.isConstant() && MemMergeNode.contents(mem(),_alias,this)==val()._type )
+ return mem(); // calloc already supplied this legal default.
+
// Simple store-after-store on same address. Should pick up the
// required init-store being stomped by a first user store.
if( mem() instanceof StoreNode st &&
@@ -87,7 +95,9 @@ public class StoreNode extends MemOpNode {
}
// Value is automatically truncated by narrow store
- if( val() instanceof AndNode and && and.in(2)._type.isConstant() ) {
+ // Private summaries describe the converted field value, so retain
+ // its conversion here. Public memory only needs the declared bound.
+ if( !_init && val() instanceof AndNode and && and.in(2)._type.isConstant() ) {
int log = _declaredType.log_size();
if( log<3 ) { // And-mask vs narrow store
long mask = ((TypeInteger)and.in(2)._type).value();
@@ -100,7 +110,7 @@ public class StoreNode extends MemOpNode {
}
// Store will chop high order bits off; math to change those bits can be dropped.
- if( val() instanceof SarNode shr &&
+ if( !_init && val() instanceof SarNode shr &&
shr.in(1) instanceof ShlNode shl &&
shr.in(2)._type.isConstant() &&
shl.in(2)._type.isConstant() ) {
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
index 0d5b5440..f004b0b8 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
@@ -225,6 +225,7 @@ public class arm extends Machine {
if( idx==1 ) return null;
// Count floats in signature up to index
if( idx-2 >= tfp.nargs() ) return null; // Anti-dependence
+ if( tfp.arg(idx-2) instanceof TypeMem ) return null; // Hidden private memory
// Count floats in signature up to index
int fcnt=0;
for( int i=2; i<idx; i++ )
@@ -266,7 +267,7 @@ public class arm extends Machine {
@Override public long callerSave() { return CALLER_SAVE; }
@Override public long neverSave() { return 1L<<RSP; }
@Override public RegMask retMask( TypeFunPtr tfp ) {
- return tfp.ret() instanceof TypeFloat ? D0_MASK : X0_MASK;
+ return tfp.ret() instanceof TypeMem ? null : tfp.ret() instanceof TypeFloat ? D0_MASK : X0_MASK;
}
@Override public int rpc() { return X30; }
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
index 7c994624..0309bfcd 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
@@ -138,6 +138,7 @@ public class riscv extends Machine {
if( idx==0 ) return RPC_MASK;
if( idx==1 ) return null;
if( idx-2 >= tfp.nargs() ) return null; // Anti-dependence
+ if( tfp.arg(idx-2) instanceof TypeMem ) return null; // Hidden private memory
// Count floats in signature up to index
int fcnt=0;
for( int i=2; i<idx; i++ )
@@ -178,7 +179,7 @@ public class riscv extends Machine {
@Override public long callerSave() { return CALLER_SAVE; }
@Override public long neverSave() { return (1L<<RSP) | (1L<<ZERO); }
@Override public RegMask retMask( TypeFunPtr tfp ) {
- return tfp.ret() instanceof TypeFloat ? FA0_MASK : A0_MASK;
+ return tfp.ret() instanceof TypeMem ? null : tfp.ret() instanceof TypeFloat ? FA0_MASK : A0_MASK;
}
@Override public int rpc() { return RPC; }
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
index f0598422..c854dd9d 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
@@ -101,6 +101,7 @@ public class x86_64_v2 extends Machine {
if( idx==0 ) return RPC_MASK;
if( idx==1 ) return null;
if( idx-2 >= tfp.nargs() ) return null; // Anti-dependence
+ if( tfp.arg(idx-2) instanceof TypeMem ) return null; // Hidden private memory
return switch( CodeGen.CODE._callingConv ) {
case "SystemV" -> callSys5 (tfp,idx,maxArgSlot);
case "Win64" -> callWin64(tfp,idx,maxArgSlot);
@@ -231,7 +232,7 @@ public class x86_64_v2 extends Machine {
}
@Override public long neverSave() { return 1L<<RSP; }
@Override public RegMask retMask( TypeFunPtr tfp ) {
- return tfp.ret() instanceof TypeFloat ? XMM0_MASK : RAX_MASK;
+ return tfp.ret() instanceof TypeMem ? null : tfp.ret() instanceof TypeFloat ? XMM0_MASK : RAX_MASK;
}
@Override public int rpc() { return RPC; }