summaryrefslogtreecommitdiff
path: root/src/main/java/com/seaofnodes/simple
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
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')
-rw-r--r--src/main/java/com/seaofnodes/simple/Parser.java652
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/CodeGen.java1
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/InstSelect.java1
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/Opto.java4
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java5
-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
-rw-r--r--src/main/java/com/seaofnodes/simple/type/Field.java10
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java58
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeMem.java49
-rw-r--r--src/main/java/com/seaofnodes/simple/type/XInt.java241
27 files changed, 1068 insertions, 214 deletions
diff --git a/src/main/java/com/seaofnodes/simple/Parser.java b/src/main/java/com/seaofnodes/simple/Parser.java
index 56233250..45c429d8 100644
--- a/src/main/java/com/seaofnodes/simple/Parser.java
+++ b/src/main/java/com/seaofnodes/simple/Parser.java
@@ -191,7 +191,13 @@ public class Parser {
for( StructNode init : INITS.values() )
init.unkeep().kill();
INITS.clear();
+ for( Ary<Node> captures : CAPTURES.values() )
+ for( Node value : captures ) value.unkill();
+ CAPTURES.clear();
stop.peephole();
+ // Helpers are built with init(), so their complete call graph exists
+ // before any inlining. Seed those nodes now along with the parsed body.
+ _code._start.walk(n -> { _code.add(n); return null; });
}
/**
@@ -201,7 +207,20 @@ public class Parser {
return parseFunctionBody(sig, loc, null, ids);
}
- private ReturnNode parseFunctionBody( TypeFunPtr sig, Lexer loc, java.util.BitSet finals, String... ids) {
+ private FunNode _privateFun;
+ private HashMap<String,Var> _fieldVars;
+ private record Constructor(TypeFunPtr sig, Lexer loc, int body, String[] ids, java.util.BitSet finals) {}
+ private Constructor _constructor;
+ private final HashMap<String,TypeFunPtr> ALLOCS = new HashMap<>();
+ private final HashMap<String,ReturnNode> INITIALIZERS = new HashMap<>();
+ private final HashMap<String,Ary<Node>> CAPTURES = new HashMap<>();
+ private Ary<Node> _captures;
+
+ private ReturnNode parseFunctionBody(TypeFunPtr sig, Lexer loc, java.util.BitSet finals, String... ids) {
+ return parseFunctionBody(sig,loc,finals,false,ids);
+ }
+
+ private ReturnNode parseFunctionBody(TypeFunPtr sig, Lexer loc, java.util.BitSet finals, boolean initializer, String... ids) {
// If this is a method, record & restore the existing struct fields,
// which are also the struct initializers. The method might update
// mutable fields, so when the function parse is done reset back to
@@ -212,6 +231,13 @@ public class Parser {
// Stack parser state on the local Java stack, and unstack it later
FunNode oldfun = _fun;
+ FunNode oldPrivate = _privateFun;
+ HashMap<String,Var> oldFields = _fieldVars;
+ Constructor oldConstructor = _constructor;
+ _fieldVars = new HashMap<>();
+ _constructor = null;
+ Ary<Node> oldCaptures = _captures;
+ _captures = initializer ? new Ary<>(Node.class) : null;
ScopeNode breakScope = _breakScope; _breakScope = null;
ScopeNode continueScope = _continueScope; _continueScope = null;
@@ -221,6 +247,8 @@ public class Parser {
// exact fidx, and ignores the return (because the fidx will only match
// the exact single function).
_code.link(fun);
+ _privateFun = sig.ret() instanceof TypeMem ? fun : null;
+ fun._constructor = _privateFun!=null;
Node rpc = new ParmNode("$rpc",0,TypeRPC.BOT,fun,con(TypeRPC.BOT)).peephole();
@@ -228,7 +256,8 @@ public class Parser {
RegionNode r = new RegionNode( null,null,null).init();
assert r.inProgress();
PhiNode rmem = new BulkMemPhiNode(ScopeNode.MEM0,r,null).init();
- PhiNode rrez = new PhiNode(ScopeNode.ARG0,r,null).init();
+ PhiNode rrez = (sig.ret() instanceof TypeMem
+ ? new BulkMemPhiNode("#selfMem",r,null) : new PhiNode(ScopeNode.ARG0,r,null)).init();
ReturnNode ret = new ReturnNode(r, rmem, rrez, rpc, fun).init();
fun.setRet(ret);
assert ret.inProgress();
@@ -238,7 +267,7 @@ public class Parser {
// Pre-call the function from Start, with worse-case arguments. This
// represents all the future, yet-to-be-parsed functions calls and
// external calls.
- _scope.push(new Kind.Func());
+ _scope.push(new Kind.Func(_privateFun!=null));
ctrl(fun); // Scope control from function
// Unknown callers supply whole memory; field aliases are discovered lazily.
_scope.mem(new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,con(TypeMem.BOT)).peephole());
@@ -248,10 +277,46 @@ public class Parser {
_scope.define(ids[i], t, finals==null ? t instanceof TypeNil : finals.get(i), new ParmNode(ids[i],i+2,t,fun,con(t)).peephole(), loc);
}
- // Parse the body
+ // Fields stay in Scope as ordinary SSA values throughout the body.
+ // Private memory is read at helper entry and written once at exit.
+ int fields = _scope.nIns();
+ if( initializer )
+ _scope.push(new Kind.Define((TypeMemPtr)TYPES.get(_nestedType)));
+ else if( _privateFun==fun ) {
+ TypeStruct shape=((TypeMemPtr)TYPES.get(_nestedType))._obj;
+ StructNode defaults=INITS.get(_nestedType);
+ for( int i=0; i<shape._fields.length; i++ ) {
+ Field f=shape._fields[i];
+ Var v=null;
+ for( int j=preParse.length-1; j>=0; j-- )
+ if( _scope.var(j)._name.equals(f._fname) ) { v=_scope.var(j); break; }
+ assert v!=null;
+ _fieldVars.put(f._fname,v);
+ if( !f._one ) {
+ Node value=missingInit(defaults.in(i),new java.util.BitSet()) ? con(Type.BOTTOM)
+ : new LoadNode(loc(),f._fname,f._alias,privateMem(),self(),off(shape,f._fname),true).peephole();
+ _scope.update(v,value);
+ }
+ }
+ }
Node last=ZERO;
- while (!peek('}') && !_lexer.isEOF())
+ while (!peek('}') && !_lexer.isEOF()) {
last = parseStatement();
+ if( initializer ) fields = initFields(fields);
+ }
+ if( initializer ) {
+ finishFields();
+ CAPTURES.put(_nestedType,_captures);
+ if( _constructor!=null ) {
+ Constructor ctor=_constructor;
+ int end=pos(ctor.body());
+ ReturnNode body=parseFunctionBody(ctor.sig(),ctor.loc(),ctor.finals(),ctor.ids());
+ body.fun().setName(shortName(_nestedType));
+ require("}");
+ pos(end);
+ }
+ }
+ if( _privateFun==fun ) last = storeFields(initializer);
// Last expression is the return except for the top-level main
if( ctrl()._type==Type.CONTROL )
@@ -277,8 +342,13 @@ public class Parser {
ret = (ReturnNode)ret.peephole();
// Function scope ends
+ if( initializer ) _scope.pop();
_scope.pop();
_fun = oldfun;
+ _privateFun = oldPrivate;
+ _fieldVars = oldFields;
+ _constructor = oldConstructor;
+ _captures = oldCaptures;
_breakScope = breakScope;
_continueScope = continueScope;
@@ -326,6 +396,7 @@ public class Parser {
else if (matchx("break") ) return parseBreak();
else if (matchx("continue")) return parseContinue();
else if (matchx("struct") ) return parseStruct();
+ else if (_scope.inConstructor() && matchx("new")) return parseConstructorDeclaration();
else if (matchx(";") ) return null; // Empty statement
// Break ambiguity around leading function types and starting a block
else if (peek('{') && !isTypeFun() ) {
@@ -336,6 +407,24 @@ public class Parser {
else return parseDeclarationStatement();
}
+ // In a struct declaration, new names the constructor. Its body still
+ // parses as a function; in expressions, new allocates and calls it.
+ private Node parseConstructorDeclaration() {
+ Lexer loc=loc();
+ String name=requireId();
+ String expect=shortName(_nestedType);
+ if( !name.equals(expect) )
+ throw error("Constructor name '"+name+"' must match type name '"+expect+"'");
+ require("=");
+ Node ctor=constructorFunc();
+ TypeFunPtr sig=(TypeFunPtr)ctor._type;
+ Node lift=liftExpr(ctor,sig,true);
+ if( !_scope.define(name,sig,true,lift,loc) )
+ throw error("Redefining constructor '"+name+"'");
+ _scope.lookup(name)._init=true;
+ return require(lift,";");
+ }
+
/**
* Parses a while statement
*
@@ -637,6 +726,8 @@ public class Parser {
private Node parseReturn() {
var expr = require(parseAsgn(), ";");
// Force the lazy whole-memory Phi before closing this exit.
+ if( _privateFun==_fun )
+ throw error("A constructor cannot return a value");
_fun.addReturn(ctrl(), _scope.mem(), expr);
ctrl(XCTRL); // Kill control
return expr;
@@ -677,8 +768,10 @@ public class Parser {
// The alloc() call added the allocation scope
!_scope.canInit(def) )
throw error("Cannot reassign final '"+name+"'");
+ checkSharedField(def);
// Parse assignment expression
+ checkCaptureWrite(def);
Node expr = parseAsgn();
// Lift expression, based on type
@@ -768,20 +861,28 @@ public class Parser {
boolean initialized = match("=");
Node expr;
if( initialized ) {
- expr = isExternDecl()
- ? externDecl(name,t)
- : parseAsgn();
+ expr = isExternDecl() ? externDecl(name,t)
+ : _scope.inConstructor() && name.equals(shortName(_nestedType))
+ ? constructorFunc() : parseAsgn();
// var/val, then type comes from expression
if( inferType ) {
if( expr._type==Type.NIL )
throw error("a not-null/non-zero expression");
t = expr._type;
if( !xfinal ) t = inferredType(t); // Widen if not final
+ // A final field is still a local variable during construction.
+ // Its constant default is a value, not a one-value declaration
+ // type: later assignments and loop Phis may change it.
+ else if( _scope.inConstructor() && !(t instanceof TypeFunPtr) ) {
+ Type wide=inferredType(t);
+ t = t instanceof TypeMemPtr ptr && ptr.notNull()
+ ? TypeMemPtr.make((byte)2,((TypeMemPtr)wide)._obj,false).withAccess(ptr._ro) : wide;
+ }
}
// expr is a constant function
if( t instanceof TypeFunPtr && expr._type instanceof TypeFunPtr tfp && tfp.isConstant() ) {
if( expr instanceof ExternNode ) t = expr._type; // Upgrade declared type to exact function
- else _code.link(tfp).setName(name); // Assign debug name to Simple function
+ else if( _code._link(tfp)!=null ) _code.link(tfp).setName(name); // Assign debug name to Simple function
}
} else {
@@ -828,60 +929,206 @@ public class Parser {
* @return zero
*/
private String _nestedType;
+ private static String shortName(String name) { return name.substring(name.lastIndexOf('.')+1); }
+
private Node parseStruct() {
- // "struct" already parsed, so expect the struct name next.
String old = _nestedType;
- String typeName = requireId();
- if( old!=null ) typeName = old+"."+typeName;
- _nestedType = typeName;
- // New declared or forward-ref type
- TypeStruct ts = TYPES.get(typeName) instanceof TypeMemPtr tmp ? tmp._obj : TypeStruct.open(typeName);
- TYPES.put(typeName,TypeMemPtr.make(ts));
-
- // A Block scope parse, and inspect the scope afterward for fields.
- _scope.push(new Kind.Define(TypeMemPtr.make(ts)));
+ String name = requireId();
+ if( old!=null ) name=old+"."+name;
+ _nestedType=name;
+ TypeMemPtr ptr = TYPES.get(name) instanceof TypeMemPtr p ? p : TypeMemPtr.make(TypeStruct.open(name));
+ TYPES.put(name,ptr);
require("{");
- int lexlen = _scope._kinds.last()._lexSize, nvar=lexlen;
- while (!peek('}') && !_lexer.isEOF()) {
- parseStatement().isKill();
- while( nvar < _scope._vars._len ) {
- Var v = _scope.var(nvar++);
- if( !v.isFRef() ) {
- ts = ts.add( Field.make( v._name, v.type(), _code.getALIAS(), v._final, v._final && v._init && _scope.in( v._idx )._type != Type.TOP ) );
- TYPES.put( typeName, TypeMemPtr.make( ts ) );
- }
- }
- }
-
- // Count struct declarations
- int varlen = _scope._vars._len;
- ts = ts.close();
- TYPES.put(typeName,TypeMemPtr.make(ts));
-
- // Generate an initializing struct
- StructNode s = new StructNode(ts);
- for( int i=lexlen; i<varlen; i++ )
- if( !_scope.var(i).isFRef() ) // Promote to outer scope, not defined here
- s.addDef(_scope.in(i)); // Keep the program code to generate instance
-
- // Save instance default constructor
- s.setType(s.compute()); // Do not call peephole, in case the entire struct is a big plain constant
- INITS.put(typeName,s.keep());
-
- // Having finalized the struct, upgrade all the 'self' pointers
- for( Node n : s._inputs )
- if( n._type instanceof TypeFunPtr tfp && _code.link(tfp) instanceof FunNode fun )
- _code.addAll( fun._outputs );
-
-
- // Done with struct/block scope
- _nestedType = old;
+ TypeFunPtr sig = _code.makeFun(TypeFunPtr.make(false,false,new Type[]{ptr,TypeMem.BOT},TypeMem.BOT));
+ ReturnNode init = parseFunctionBody(sig,loc(),null,true,"self","#selfMem");
+ init.fun()._name=name+".<init>";
+ INITIALIZERS.put(name,init);
+ TypeStruct shape = ((TypeMemPtr)TYPES.get(name))._obj;
+ ALLOCS.put(name,makeAllocator(shape,init));
+ _nestedType=old;
require("}");
require(";");
- _scope.pop();
return ZERO;
}
+ private int initFields(int index) {
+ TypeStruct shape = ((TypeMemPtr)TYPES.get(_nestedType))._obj;
+ while( index<_scope.nIns() ) {
+ Var v = _scope.var(index++);
+ if( v.isFRef() ) continue;
+ Node value = _scope.in(v);
+ // Only function declarations are shared across instances. A final
+ // data field can change in a named or inline constructor, so a
+ // constant default does not justify folding every future read.
+ boolean constant = v._final && v._init && value._type instanceof TypeFunPtr && value._type.isConstant();
+ Field field = Field.make(v._name,v.type(),_code.getALIAS(),v._final,constant);
+ shape=shape.add(field);
+ TYPES.put(_nestedType,TypeMemPtr.make(shape));
+ _fieldVars.put(v._name,v);
+ }
+ return index;
+ }
+
+ private void finishFields() {
+ TypeStruct shape = ((TypeMemPtr)TYPES.get(_nestedType))._obj.close();
+ TYPES.put(_nestedType,TypeMemPtr.make(shape));
+ StructNode constants = new StructNode(shape);
+ int first = _scope._kinds.last()._lexSize;
+ for( int i=first; i<_scope.nIns(); i++ )
+ if( !_scope.var(i).isFRef() ) constants.addDef(_scope.in(i));
+ constants.setType(constants.compute());
+ INITS.put(_nestedType,constants.keep());
+ }
+
+ private Node self() { return _scope.in(_scope.lookup("self")); }
+ private Node privateMem() { return _scope.in(_scope.lookup("#selfMem")); }
+
+ private boolean privateSelf(Node ptr) {
+ if( _privateFun!=_fun || _privateFun==null ) return false;
+ while( ptr instanceof ConvertNode || ptr instanceof ReadOnlyNode || ptr instanceof GuardNode ) ptr=ptr.in(1);
+ Node self=self();
+ while( self instanceof ConvertNode || self instanceof ReadOnlyNode || self instanceof GuardNode ) self=self.in(1);
+ return ptr==self;
+ }
+ private boolean mayBeSelf(Node ptr, java.util.BitSet seen) {
+ if( _privateFun==null || _privateFun!=_fun || seen.get(ptr._nid) ) return false;
+ seen.set(ptr._nid);
+ if( privateSelf(ptr) ) return true;
+ if( ptr instanceof ConvertNode || ptr instanceof ReadOnlyNode || ptr instanceof GuardNode )
+ return mayBeSelf(ptr.in(1),seen);
+ if( ptr instanceof PhiNode && !(ptr instanceof ParmNode) )
+ for( int i=1; i<ptr.nIns(); i++ )
+ if( ptr.in(i)!=null && mayBeSelf(ptr.in(i),seen) ) return true;
+ return false;
+ }
+ private void checkPublication(Node value) {
+ // Private stores can replace a field only while no public alias can
+ // observe it. Early publication needs the next chapter's escape state.
+ if( mayBeSelf(value,new java.util.BitSet()) )
+ throw error("Cannot publish self before the constructor finishes");
+ }
+ // The only writes to the new object's fields are at helper exits. Branches
+ // and loops above this point merge values, not private-memory snapshots.
+ private Node storeFields(boolean initializer) {
+ TypeStruct shape=((TypeMemPtr)TYPES.get(_nestedType))._obj;
+ Node priv=privateMem().keep();
+ for( Field f : shape._fields ) {
+ if( f._one ) continue;
+ Var v=_fieldVars.get(f._fname);
+ Node value=_scope.in(_scope.update(v,null)); // Force a lazy value Phi.
+ if( initializer && missingInit(value,new java.util.BitSet()) ) continue;
+ if( !initializer && ctrl()._type!=Type.XCONTROL && missingInit(value,new java.util.BitSet()) )
+ throw error("'"+shape._name+"' is not fully initialized, field '"+f._fname+"' needs to be set in a constructor");
+ checkPublication(value);
+ Node next=fieldStore(f,ctrl(),priv,self(),off(shape,f._fname),value);
+ priv.unkeep(); priv=next.keep();
+ }
+ return priv.unkeep();
+ }
+
+ private Node fieldStore(Field f, Node ctrl, Node priv, Node ptr, Node off, Node value) {
+ Node st=new StoreNode(loc(),f._fname,f._alias,f._t,priv,ptr,off,value,true);
+ st.setDef(0,ctrl);
+ return new MemMergeNode(priv,f._alias,st.peephole()).peephole();
+ }
+
+ private Node constructorFunc() {
+ require("{");
+ Ary<Type> types = new Ary<>(Type.class);
+ Ary<String> names = new Ary<>(String.class);
+ types.add(TYPES.get(_nestedType)); names.add("self");
+ types.add(TypeMem.BOT); names.add("#selfMem");
+ java.util.BitSet finals = new java.util.BitSet();
+ finals.set(0); // Rebinding self would detach the pointer from its private memory.
+ while( true ) {
+ Type t=type();
+ if( t==null ) break;
+ finals.set(names.size(),bindingFinal(t));
+ types.add(t); names.add(requireId());
+ match(",");
+ }
+ require("->");
+ TypeFunPtr sig = _code.makeFun(TypeFunPtr.make(false,false,types.asAry(),TypeMem.BOT));
+ // Bind the signature now, but parse the body after the field list is
+ // closed. Then even a later field has a local SSA slot before branches
+ // and loops are parsed. Strings/comments must not affect brace depth.
+ _constructor=new Constructor(sig,loc(),pos(),names.asAry(),finals);
+ int depth=1;
+ while( depth>0 ) {
+ if( peek('"') ) { parseString(); continue; }
+ if( match("'") ) { parseChar(); continue; }
+ char ch=_lexer.nextXChar();
+ if( ch==Character.MAX_VALUE ) throw errorSyntax("}");
+ if( ch=='{' ) depth++;
+ if( ch=='}' ) depth--;
+ }
+ return con(sig);
+ }
+
+ private CallEndNode rawCall(Node ctrl, Node pub, ReturnNode target, Node... args) {
+ Node[] inputs = new Node[args.length+3];
+ inputs[0]=ctrl; inputs[1]=pub;
+ System.arraycopy(args,0,inputs,2,args.length);
+ inputs[inputs.length-1]=new FunPtrNode(target.fun().sig(),target).peephole();
+ // Keep the complete declaration-side caller set before any inlining.
+ CallNode call = new CallNode(loc(),inputs).init();
+ CallEndNode end = new CallEndNode(call).init();
+ call.link(target.fun());
+ return end;
+ }
+
+ private Node publish(TypeStruct shape, Node ctrl, Node pub, Node priv, Node ptr) {
+ priv.keep(); ptr.keep();
+ for( Field f : shape._fields ) if( !f._one ) {
+ pub.keep();
+ Node esc = new EscapeNode(loc(),f,ctrl,pub,priv,ptr).peephole();
+ Node next = new MemMergeNode(pub,f._alias,esc).peephole();
+ pub.unkeep(); pub=next;
+ }
+ ptr.unkeep(); priv.unkill();
+ return pub;
+ }
+
+ private TypeFunPtr makeAllocator(TypeStruct shape, ReturnNode init) {
+ Field cf=shape.field(shortName(shape._name));
+ TypeFunPtr ctor=cf!=null && cf._t instanceof TypeFunPtr f ? f : null;
+ int nargs=ctor==null ? 0 : ctor.nargs()-2;
+ Ary<Node> captures=CAPTURES.get(shape._name);
+ Type[] types=new Type[nargs+captures.size()];
+ for( int i=0; i<nargs; i++ ) types[i]=ctor.arg(i+2);
+ for( int i=0; i<captures.size(); i++ ) types[nargs+i]=captures.at(i)._type;
+ TypeMemPtr ptr=TypeMemPtr.make(shape);
+ TypeFunPtr sig=_code.makeFun(TypeFunPtr.make(false,false,types,ptr));
+ FunNode fun=new FunNode(loc(),sig,shape._name,null,_code._start).init();
+ fun._name=shape._name+".<new>"; fun._constructor=true;
+ _code.link(fun);
+ Node rpc=new ParmNode("$rpc",0,TypeRPC.BOT,fun,con(TypeRPC.BOT)).init();
+ Node pub=new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,con(TypeMem.BOT)).init();
+ Node[] args=new Node[nargs+2];
+ for( int i=0; i<nargs; i++ ) args[i+2]=new ParmNode("arg"+i,i+2,types[i],fun,con(types[i])).init();
+ Node alloc=new NewNode(ptr,fun,null,off(shape," len")).init();
+ Node self=new ProjNode(alloc,0,shape._name).init();
+ Node priv=new ProjNode(alloc,1,"#selfMem").init();
+ Node[] initArgs=new Node[captures.size()+2];
+ initArgs[0]=self; initArgs[1]=priv;
+ for( int i=0; i<captures.size(); i++ )
+ initArgs[i+2]=new ParmNode("capture"+i,nargs+i+2,types[nargs+i],fun,con(types[nargs+i])).init();
+ CallEndNode end=rawCall(fun,pub,init,initArgs);
+ Node ctl=new CProjNode(end,0,ScopeNode.CTRL).init();
+ pub=new ProjNode(end,1,ScopeNode.MEM0).init();
+ priv=new ProjNode(end,2,"#selfMem").init();
+ if( ctor!=null ) {
+ args[0]=self; args[1]=priv;
+ end=rawCall(ctl,pub,_code.link(ctor).ret(),args);
+ ctl=new CProjNode(end,0,ScopeNode.CTRL).init();
+ pub=new ProjNode(end,1,ScopeNode.MEM0).init();
+ priv=new ProjNode(end,2,"#selfMem").init();
+ }
+ pub=publish(shape,ctl,pub,priv,self);
+ ReturnNode ret=new ReturnNode(ctl,pub,self,rpc,fun).init();
+ fun.setRet(ret); _code._stop.addDef(ret);
+ return sig;
+ }
// Parse and return a type or null. Valid types always are followed by an
// 'id' which the caller must parse. This lets us distinguish forward ref
@@ -1233,8 +1480,9 @@ public class Parser {
if( name!=null ) {
Var n = _scope.lookup(name);
if( n != null && !(n.type() instanceof TypeMemPtr) ) {
- if( n._final )
+ if( n._final && !_scope.canInit(n) )
throw error("Cannot reassign final '"+n._name+"'");
+ checkSharedField(n);
Node expr = liftExpr(peep(new AddNode(_scope.in(n),con(delta))),n.type(),true);
_scope.update(n,expr);
return expr;
@@ -1326,8 +1574,10 @@ public class Parser {
if( ch==0 ) // Normal primary, check for postfix updates
return parsePostfix(rvalue);
// Assign-update direct into Scope
- if( var._final )
+ if( var._final && !_scope.canInit(var) )
throw error("Cannot reassign final '"+var._name+"'");
+ checkSharedField(var);
+ checkCaptureWrite(var);
Node op = opAssign(ch,rvalue, var.type() );
_scope.update(var,op);
return postfix(ch) ? rvalue.unkeep() : op;
@@ -1339,6 +1589,8 @@ public class Parser {
// If a method var, allow nested blocks and methods, but not fcns or other structs
Kind kind = _scope.kind(var);
if( kind instanceof Kind.Define ) {
+ if( var.isFRef() ) return false; // Promote an unresolved lexical name, not self.name.
+ if( _privateFun==_fun && _privateFun!=null ) return true;
for( int i = _scope.depth()-1; i>=0 && var._idx < _scope._kinds.at(i)._lexSize; i-- ) {
Kind xkind = _scope._kinds.at(i);
if( xkind instanceof Kind.Block ) continue;
@@ -1352,6 +1604,17 @@ public class Parser {
// If a normal var, allow any nested blocks, and a last fcn -
// but not functions further out (which involves capture).
Node def = _scope.in(var._idx);
+ if( _captures!=null && _scope.kind(var) instanceof Kind.Func &&
+ !(var._final && def._type.isConstant()) && !(def instanceof FRefNode) ) {
+ if( !(def instanceof ParmNode parm && parm.fun()==_fun) ) {
+ _captures.add(def.keep());
+ Type type=def._type;
+ int index=_fun.sig().nargs();
+ _fun.addParseArg(type);
+ _scope.update(var,new ParmNode(var._name,index+2,type,_fun,con(type)).init());
+ }
+ return false;
+ }
for( int i = _scope.depth()-1; i>=0 && var._idx < _scope._kinds.at(i)._lexSize; i-- )
if( _scope._kinds.at(i) instanceof Kind.Func &&
!(def instanceof FRefNode) && !(var._final && def._type.isConstant()) )
@@ -1360,6 +1623,31 @@ public class Parser {
return false;
}
+ private void checkCaptureWrite(Var var) {
+ if( _captures==null ) return;
+ for( int i=_scope.depth()-1; i>=0 && var._idx<_scope._kinds.at(i)._lexSize; i-- )
+ if( _scope._kinds.at(i) instanceof Kind.Func )
+ throw error("Cannot assign captured variable '"+var._name+"' in an initializer");
+ }
+
+ // Inline constructors have local field slots too, but shared function
+ // declarations have no instance slot to publish. Do not silently discard
+ // an assignment to one of those declarations.
+ private void checkSharedField(Var var) {
+ if( !_scope.canInit(var) ) return;
+ if( _scope.kind(var) instanceof Kind.Define ) {
+ checkPrivateWrite(var._name);
+ return;
+ }
+ for( int i=_scope.depth()-1; i>0; i-- )
+ if( _scope._kinds.at(i) instanceof Kind.Alloc alloc ) {
+ Field field=alloc._tmp._obj.field(var._name);
+ if( field!=null && field._one )
+ throw error("Cannot reassign shared function '"+var._name+"'");
+ return;
+ }
+ }
+
// Check for assign-update, "x += e0".
// Returns "x + e0"; the caller assigns value.
// if ch is postfix, then lhs is kept and caller will unkeep and return it.
@@ -1417,38 +1705,85 @@ public class Parser {
if( !(t instanceof TypeMemPtr tmp) )
throw error("Cannot allocate a "+t.str());
- // Parse new struct { default_initialization }
- StructNode s = INITS.get(tmp._obj._name);
- if( s==null ) throw error("Unknown struct type '" + tmp._obj._name + "'");
-
- Field[] fs = s._ts._fields;
- // if the object is fully initialized, we can skip a block here.
- // Check for constructor block:
- boolean hasConstructor = match("{");
- Ary<Node> init=s._inputs; int idx=0;
- if( hasConstructor ) {
- idx = _scope.nIns();
- // Push a scope, and pre-assign all struct fields.
- _scope.push(new Kind.Block());
- Lexer loc = loc();
- for( int i=0; i<fs.length; i++ )
- // An initial TOP means the field needs to be initialized. We
- // store a BOT initially; any partial init will fall to BOT
- // (merge BOT and the partial) and be obviously only a partial
- // init. To be initialized the field needs a full clobber.
- _scope.define(fs[i]._fname, fs[i]._t, fs[i]._final, s.in(i)._type==Type.TOP ? con(Type.BOTTOM) : s.in(i), loc);
- // Parse the constructor body
- require(parseBlock(new Kind.Alloc(tmp)),"}");
- init = _scope._inputs;
- }
- // Check that all fields are initialized
- for( int i=idx; i<init.size(); i++ )
- if( missingInit(init.at(i),new java.util.BitSet()) )
- throw error("'"+tmp._obj._name+"' is not fully initialized, field '" + fs[i-idx]._fname + "' needs to be set in a constructor");
- Node ptr = newStruct(tmp._obj, off(tmp._obj, " len"), idx, init );
- if( hasConstructor )
- _scope.pop();
- return ptr;
+ TypeStruct shape=tmp._obj;
+ TypeFunPtr sig=ALLOCS.get(shape._name);
+ if( sig==null ) throw error("Unknown struct type '"+shape._name+"'");
+ if( !peek('{') ) {
+ // Obvious missing defaults can still be diagnosed here. A user
+ // constructor or a merge needs the later publication check.
+ if( shape.field(shortName(shape._name))==null ) {
+ StructNode defaults=INITS.get(shape._name);
+ Type initial=INITIALIZERS.get(shape._name).expr()._type;
+ for( int i=0; i<shape._fields.length; i++ ) {
+ Field f=shape._fields[i];
+ Field written=initial instanceof TypeMem m && m._t instanceof TypeStruct ts ? ts.field(f._fname) : null;
+ if( missingInit(defaults.in(i),new java.util.BitSet()) && (written==null || written._t==Type.BOTTOM) )
+ throw error("'"+shape._name+"' is not fully initialized, field '"+f._fname+"' needs to be set in a constructor");
+ }
+ }
+ Node fptr=new FunPtrNode(sig,_code.link(sig).ret()).peephole();
+ Ary<Node> args=new Ary<>(Node.class);
+ if( match("(") ) {
+ while( !peek(')') ) {
+ Node arg=parseAsgn();
+ checkPublication(arg);
+ args.add(arg.keep());
+ if( !match(",") ) break;
+ }
+ require(")");
+ }
+ for( Node capture : CAPTURES.get(shape._name) ) {
+ checkPublication(capture);
+ args.add(capture.keep());
+ }
+ if( args.size()!=sig.nargs() ) throw error("Wrong number of constructor arguments for '"+shape._name+"'");
+ for( Node arg : args ) arg.unkeep();
+ return callWithArgs(fptr,args.asAry());
+ }
+ require("{");
+ Node alloc=new NewNode(TypeMemPtr.make(shape),ctrl(),null,off(shape," len")).init();
+ Node ptr=new ProjNode(alloc,0,shape._name).init().keep();
+ Node priv=new ProjNode(alloc,1,"#selfMem").init();
+ Ary<Node> captures=CAPTURES.get(shape._name);
+ Node[] args=new Node[captures.size()+2];
+ args[0]=ptr; args[1]=priv;
+ for( int i=0; i<captures.size(); i++ ) {
+ checkPublication(captures.at(i));
+ args[i+2]=captures.at(i);
+ }
+ CallEndNode end=rawCall(ctrl(),mem(),INITIALIZERS.get(shape._name),args).keep();
+ ctrl(new CProjNode(end,0,ScopeNode.CTRL).peephole());
+ mem(new ProjNode(end,1,ScopeNode.MEM0).peephole());
+ priv=new ProjNode(end,2,"#selfMem").peephole().keep();
+ end.unkeep();
+ int first=_scope.nIns();
+ _scope.push(new Kind.Block());
+ StructNode constants=INITS.get(shape._name);
+ for( int i=0; i<shape._fields.length; i++ ) {
+ Field f=shape._fields[i];
+ Type initial=INITIALIZERS.get(shape._name).expr()._type;
+ Field written=initial instanceof TypeMem m && m._t instanceof TypeStruct ts ? ts.field(f._fname) : null;
+ boolean missing=missingInit(constants.in(i),new java.util.BitSet()) &&
+ (written==null || written._t==Type.BOTTOM);
+ Node value=f._one ? constants.in(i)
+ : missing ? con(Type.BOTTOM)
+ : new LoadNode(loc(),f._fname,f._alias,priv,ptr,off(shape,f._fname),true).peephole();
+ _scope.define(f._fname,f._t,f._final,value,loc());
+ }
+ require(parseBlock(new Kind.Alloc(tmp)),"}");
+ for( int i=0; i<shape._fields.length; i++ ) {
+ Field f=shape._fields[i];
+ if( f._one ) continue;
+ Node value=_scope.in(first+i);
+ checkPublication(value);
+ if( ctrl()._type!=Type.XCONTROL && missingInit(value,new java.util.BitSet()) )
+ throw error("'"+shape._name+"' is not fully initialized, field '"+f._fname+"' needs to be set in a constructor");
+ Node next=fieldStore(f,ctrl(),priv,ptr,off(shape,f._fname),value);
+ priv.unkeep(); priv=next.keep();
+ }
+ _scope.pop();
+ mem(publish(shape,ctrl(),mem(),priv.unkeep(),ptr));
+ return ptr.unkeep();
}
// The old constructor model requires each field on every path. An
@@ -1466,50 +1801,18 @@ public class Parser {
}
- /**
- * Return a NewNode initialized memory.
- * @param obj is the declared type, with GLB fields
- * @param init is a collection of initialized fields
- */
- private Node newStruct( TypeStruct obj, Node size, int idx, Ary<Node> init ) {
- Field[] fs = obj._fields;
- if( fs==null )
- throw error("Unknown struct type '" + obj._name + "'");
- Node prior = mem().keep();
- MemMergeNode input = new MemMergeNode((Node)null);
- for( Field f : fs ) if( !f._one ) input.alias(f._alias,prior);
- // Allocation zeroes its fields; nonzero initializers become explicit Stores.
- Node[] ns = new Node[3];
- ns[0] = ctrl();
- ns[1] = input.peephole();
- ns[2] = size;
- Node nnn = new NewNode(TypeMemPtr.make(obj),ns).peephole().keep();
- Node ptr = new ProjNode(nnn,0,obj._name).peephole().keep();
- Node out = new ProjNode(nnn,1,"$mem").peephole().keep();
- MemMergeNode after = new MemMergeNode(prior);
- for( Field f : fs ) if( !f._one ) after.alias(f._alias,out);
- mem(after.peephole());
- out.unkeep();
- nnn.unkeep();
- prior.unkill();
- for( int i=0; i<fs.length; i++ ) {
- Node val = init.get(idx+i);
- if( !fs[i]._one && val._type != val._type.makeZero() ) {
- Field f = fs[i];
- Node st = new StoreNode(loc(),f._fname,f._alias,f._t,mem(),ptr,off(obj,f._fname),val,true).peephole();
- mem(new MemMergeNode(mem(),f._alias,st).peephole());
- }
- }
- return ptr.unkeep();
- }
-
- private static final Ary<Node> ALTMP = new Ary<>(Node.class);
private Node newArray(TypeStruct ary, Node len) {
- ConFldOffNode base = off(ary, "[]");
- int scale= ary.aryScale();
- Node size = peep(new AddNode(base,peep(new ShlNode(null,len.keep(),con(scale)))));
- ALTMP.clear(); ALTMP.add(len.unkeep()); ALTMP.add(con(ary._fields[1]._t.makeZero()));
- return newStruct(ary,size,0,ALTMP);
+ ConFldOffNode base=off(ary,"[]");
+ Node size=peep(new AddNode(base,peep(new ShlNode(null,len.keep(),con(ary.aryScale())))));
+ Node alloc=new NewNode(TypeMemPtr.make(ary),ctrl(),null,size).init();
+ Node ptr=new ProjNode(alloc,0,ary._name).init().keep();
+ Node priv=new ProjNode(alloc,1,"#selfMem").init();
+ // calloc supplied the zero elements; initialize the length before
+ // either alias becomes visible to the public heap.
+ Field length=ary.field("#");
+ priv=fieldStore(length,ctrl(),priv,ptr,off(ary,"#"),len.unkeep());
+ mem(publish(ary,ctrl(),mem(),priv,ptr));
+ return ptr.unkeep();
}
private Node newString(String s) {
TypeMemPtr tmp = typeAry(TypeInteger.U8,true);
@@ -1532,6 +1835,7 @@ public class Parser {
private void store(String name, int alias, Type glb, Node ptr, Node off, Node val, boolean init, Node ctrl) {
if( !init && ptr._type instanceof TypeMemPtr view && view._ro && ctrl()._type!=Type.XCONTROL )
throw error("Cannot modify final field '"+name+"'");
+ checkPublication(val);
Node prior = mem().keep();
Node st = new StoreNode(loc(),name,alias,glb,prior,ptr,off,val,init);
st.setDef(0,ctrl);
@@ -1574,6 +1878,9 @@ public class Parser {
* </pre>
*/
private Node parsePostfixName(Node expr, String name) {
+ if( privateSelf(expr) ) return parsePrivateField(name);
+ if( !privateSelf(expr) && mayBeSelf(expr,new java.util.BitSet()) )
+ throw error("Cannot access self through a merged alias during construction");
if( expr._type==Type.NIL )
throw error("Accessing unknown field '" + name + "' from 'null'");
@@ -1595,6 +1902,8 @@ public class Parser {
// Get field type and layout offset from base type and field index fidx
Field f = base._fields[fidx]; // Field from field index
Type tf = f._t;
+ if( tf instanceof TypeFunPtr ctor && ctor.ret() instanceof TypeMem )
+ throw error("A constructor is only called by new");
if( tf instanceof TypeMemPtr ftmp && ftmp.isFRef() )
tf = ftmp.makeFrom(((TypeMemPtr)(TYPES.get(ftmp._obj._name)))._obj);
if( base.isAry() && tf instanceof TypeConAry con )
@@ -1671,9 +1980,7 @@ public class Parser {
off.keep();
if( matchOpx('=','=') ) {
Node value = parseAsgn().keep();
- Node st = new StoreNode(loc(),name,1,Type.BOTTOM,mem(),expr,off,value,false);
- st.setDef(0,ctrl());
- mem(st.peephole());
+ unknownStore(name,expr,off,value);
expr.unkill();
off.unkill();
return value.unkeep();
@@ -1684,9 +1991,7 @@ public class Parser {
char ch = _lexer.matchOperAssign();
if( ch!=0 ) {
Node op = opAssign(ch,load,Type.BOTTOM).keep();
- Node st = new StoreNode(loc(),name,1,Type.BOTTOM,mem(),expr,off,op,false);
- st.setDef(0,ctrl());
- mem(st.peephole());
+ unknownStore(name,expr,off,op);
// The field width is not known yet. A compound assignment returns
// the stored value after conversion; postfix returns the old load.
// Reading our own Store folds once its alias and width are known.
@@ -1698,12 +2003,59 @@ public class Parser {
}
load.keep();
op.unkill();
+ } else if( !name.equals("[]") && match("(") ) {
+ Node result=functionCall(load,expr,name);
+ off.unkill();
+ return parsePostfix(require(result,")"));
} else load.keep();
expr.unkill();
off.unkill();
return parsePostfix(load.unkeep());
}
+ private void unknownStore(String name, Node ptr, Node off, Node value) {
+ checkPublication(value);
+ Node st=new StoreNode(loc(),name,1,Type.BOTTOM,mem(),ptr,off,value,false);
+ st.setDef(0,ctrl());
+ mem(st.peephole());
+ }
+
+ // Explicit self.x and an unqualified field name use the same local slot.
+ // The map also keeps self.x distinct from a parameter/local named x.
+ private Node parsePrivateField(String name) {
+ Var v=_fieldVars.get(name);
+ if( v==null ) throw error("Cannot resolve field '"+name+"'");
+ if( v.type() instanceof TypeFunPtr ctor && ctor.ret() instanceof TypeMem )
+ throw error("A constructor is only called by new");
+ if( matchOpx('=','=') ) {
+ checkPrivateWrite(name);
+ Node value=parseAsgn().keep();
+ checkPublication(value);
+ _scope.update(v,liftExpr(value,v.type(),false));
+ return value.unkeep();
+ }
+ Node value=_scope.in(_scope.update(v,null));
+ if( value instanceof ConstantNode && (value._type==Type.BOTTOM || value._type==Type.TOP) )
+ throw error("Cannot read uninitialized field '"+name+"'");
+ char ch=_lexer.matchOperAssign();
+ if( ch!=0 ) {
+ checkPrivateWrite(name);
+ Node op=opAssign(ch,value,v.type());
+ checkPublication(op);
+ _scope.update(v,op);
+ return postfix(ch) ? value.unkeep() : op;
+ }
+ if( value._type instanceof TypeFunPtr && match("(") )
+ return parsePostfix(require(functionCall(value,self()),")"));
+ return parsePostfix(value);
+ }
+
+ private void checkPrivateWrite(String name) {
+ Field field=((TypeMemPtr)TYPES.get(_nestedType))._obj.field(name);
+ if( field!=null && field._one )
+ throw error("Cannot reassign shared function '"+name+"'");
+ }
+
// zero/sign extend. "i" is limited to either classic unsigned (min==0) or
// classic signed (min=minus-power-of-2); max=power-of-2-minus-1.
private Node zsMask(Node val, Type t ) {
@@ -1773,6 +2125,9 @@ public class Parser {
* </pre>
*/
private Node functionCall(Node fcn, Node self) {
+ return functionCall(fcn,self,null);
+ }
+ private Node functionCall(Node fcn, Node self, String method) {
if( fcn._type == Type.NIL )
throw error("Calling a null function pointer");
if( !(fcn instanceof FRefNode) && fcn._type!=Type.BOTTOM && fcn._type!=TypeScalar.BOT && !fcn._type.isa(TypeFunPtr.BOT) )
@@ -1782,11 +2137,13 @@ public class Parser {
Ary<Node> args = new Ary<>( Node.class );
args.push(null); // Space for ctrl,mem
args.push(null);
+ if( self != null ) checkPublication(self);
if( self != null ) // Method call has a self
args.push(self);
while( !peek(')') ) {
Node arg = parseAsgn();
if( arg==null ) break;
+ checkPublication(arg);
args.push(arg.keep());
if( !match(",") ) break;
}
@@ -1806,7 +2163,9 @@ public class Parser {
}
// Into the call
- CallNode call = (CallNode)new CallNode(loc(), args.asAry()).peephole();
+ CallNode call = new CallNode(loc(), args.asAry());
+ call._method=method;
+ call=(CallNode)call.peephole();
// Post-call setup
CallEndNode cend = (CallEndNode)new CallEndNode(call).peephole();
call.peephole(); // Rerun peeps after CallEnd, allows early inlining
@@ -1818,6 +2177,21 @@ public class Parser {
return new ProjNode(cend,2,"#2").peephole();
}
+ private Node callWithArgs(Node fptr, Node[] args) {
+ Node[] inputs=new Node[args.length+3];
+ inputs[0]=ctrl(); inputs[1]=mem();
+ System.arraycopy(args,0,inputs,2,args.length);
+ inputs[inputs.length-1]=fptr;
+ CallNode call=new CallNode(loc(),inputs).init();
+ CallEndNode end=new CallEndNode(call).init().keep();
+ if( fptr._type instanceof TypeFunPtr sig && _code.link(sig)!=null ) call.link(_code.link(sig));
+ ctrl(new CProjNode(end,0,ScopeNode.CTRL).peephole());
+ mem(new ProjNode(end,1,ScopeNode.MEM0).peephole());
+ Node result=new ProjNode(end,2,"#2").peephole();
+ end.unkeep();
+ return result;
+ }
+
// Just after parsing "type foo = " can parse `"C"`
private boolean isExternDecl( ) {
int old = pos();
diff --git a/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java b/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
index b79eb73d..a49299e1 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
+++ b/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
@@ -154,7 +154,6 @@ public class CodeGen {
private int _fidx =0;
public TypeFunPtr makeFun( TypeFunPtr fun ) {
int fidx = _fidx++;
- assert fidx<64; // TODO: need a larger FIDX space
return fun.makeFrom(fidx);
}
// Signature for MAIN
diff --git a/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java b/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
index da45db4f..9be662e8 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
+++ b/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
@@ -251,7 +251,7 @@ public abstract class GlobalCodeMotion {
visit.set(def._nid);
for( Node out : def._outputs )
if( out instanceof MemOpNode ld && ld._isLoad && late[ld._nid]==null ) work.push(ld);
- if( def instanceof MemMergeNode )
+ if( def instanceof MemMergeNode || def instanceof EscapeNode )
for( int i=1; i<def.nIns(); i++ )
if( def.in(i)!=null ) wakeLoads(def.in(i),late,work,visit);
}
diff --git a/src/main/java/com/seaofnodes/simple/codegen/InstSelect.java b/src/main/java/com/seaofnodes/simple/codegen/InstSelect.java
index 5a91cf9f..b0351c98 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/InstSelect.java
+++ b/src/main/java/com/seaofnodes/simple/codegen/InstSelect.java
@@ -89,6 +89,7 @@ class InstSelect {
private Node select(Node n) {
+ if( n instanceof EscapeNode esc ) return new EscapeMach(esc);
if( n instanceof GuardNode guard ) return new GuardMach(guard);
if( n instanceof PtrToIntNode ptr ) return new PtrToIntMach(ptr);
Node op = n;
diff --git a/src/main/java/com/seaofnodes/simple/codegen/Opto.java b/src/main/java/com/seaofnodes/simple/codegen/Opto.java
index c78e732e..82646a25 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/Opto.java
+++ b/src/main/java/com/seaofnodes/simple/codegen/Opto.java
@@ -101,6 +101,7 @@ abstract public class Opto {
// Always true for main
if( fun.sig().fidx() == code._main.fidx() ) return true;
// No escape analysis yet: a surviving address may be called from outside.
+ if( fun._constructor ) return false;
if( fun.hasPtr() ) return true;
// Never true for stdlib or anonymous functions
if( fun._name == null || fun._name.startsWith("sys.") )
@@ -113,8 +114,7 @@ abstract public class Opto {
// into a Call, link the Call and Fun.
private static void linkCG(CodeGen code, TypeFunPtr tfp, CallNode call) {
if( tfp.nargs() != call.nargs() ) return; // Error calls hit this
- for( long fidxs=tfp.fidxs(); fidxs!=0; fidxs=TypeFunPtr.nextFIDX(fidxs) ) {
- int fidx = Long.numberOfTrailingZeros(fidxs);
+ for( int fidx=XInt.next(tfp.fidxs(),-1); fidx>=0; fidx=XInt.next(tfp.fidxs(),fidx) ) {
FunNode fun = code.link(fidx);
// null here means an external function; i.e. this Call
// calls to an outside library and all its arguments escape.
diff --git a/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java b/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java
index bc698e19..7dadb4d2 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java
+++ b/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java
@@ -14,6 +14,11 @@ abstract class TypeCheck {
});
if( errs.isEmpty() ) return;
+ // Publication is a producer check. Report an incomplete object before
+ // its users complain about values or permissions inherited from it.
+ for( Node n : errs )
+ if( n instanceof EscapeNode ) throw n.err();
+
// A bad Load now produces BOTTOM. Report the bad access before a
// Convert or arithmetic use that merely received its failed result.
Node first = errs.at(0);
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; }
diff --git a/src/main/java/com/seaofnodes/simple/type/Field.java b/src/main/java/com/seaofnodes/simple/type/Field.java
index 1396f1de..9bd85ba6 100644
--- a/src/main/java/com/seaofnodes/simple/type/Field.java
+++ b/src/main/java/com/seaofnodes/simple/type/Field.java
@@ -17,10 +17,10 @@ public class Field extends Type {
public Type _t;
// Unique memory alias, not sensibly part of a "type" but very convenient here.
public int _alias;
- // Field must be written to exactly once, no more, no less
+ // Field is immutable after construction; constructor locals may change
public boolean _final;
- // Field is final in the declaration; value is the same for all instances
- // and will be moved to the class object
+ // Shared function declaration, with the same value for every instance.
+ // A final data field is still per-instance, even with a constant default.
public boolean _one;
private static final Ary<Field> FREE = new Ary<>(Field.class);
@@ -133,4 +133,8 @@ public class Field extends Type {
}
@Override public String str() { return (_final?"":"!")+(_one?"$":"")+_fname; }
+
+ @Override Type _upgradeType(java.util.HashMap<String,Type> types) {
+ return makeFrom(_t._upgradeType(types));
+ }
}
diff --git a/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java b/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java
index 602aaaae..9acf5c9f 100644
--- a/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java
+++ b/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java
@@ -23,24 +23,23 @@ public class TypeFunPtr extends TypeNil {
public Type _ret;
// TODO:
boolean _open; // Extra args are true:BOTTOM, false:TOP
- // Cheesy easy implementation for a small set; 1 bit per unique function
- // within the same Type[]. Can be upgraded to a BitSet for larger classes
- // of functions. Negative means "these 63 concrete bits plus infinite
- // unknown more"
- public long _fidxs; // 63 unique functions per signature
+ // Hidden initializers and allocators can exceed a machine word's worth
+ // of functions. Use the expandable sets from the later chapters, without
+ // adding their escape summaries or cross-file identity remapping.
+ public int[] _fidxs;
private static final Ary<TypeFunPtr> FREE = new Ary<>(TypeFunPtr.class);
- private TypeFunPtr(byte nil, boolean open, Type[] sig, Type ret, long fidxs) { super(TFUNPTR,nil); init(nil,open,sig,ret,fidxs); }
- private TypeFunPtr init(byte nil, boolean open, Type[] sig, Type ret, long fidxs ) {
+ private TypeFunPtr(byte nil, boolean open, Type[] sig, Type ret, int[] fidxs) { super(TFUNPTR,nil); init(nil,open,sig,ret,fidxs); }
+ private TypeFunPtr init(byte nil, boolean open, Type[] sig, Type ret, int[] fidxs ) {
assert sig != null;
_nil = nil;
_open = open;
_sig = sig;
_ret = ret;
- _fidxs= fidxs;
+ _fidxs= XInt.intern(fidxs);
return this;
}
- static TypeFunPtr malloc( byte nil, boolean open, Type[] sig, Type ret, long fidxs ) {
+ static TypeFunPtr malloc( byte nil, boolean open, Type[] sig, Type ret, int[] fidxs ) {
return FREE.isEmpty() ? new TypeFunPtr(nil,open,sig,ret,fidxs) : FREE.pop().init(nil,open,sig,ret,fidxs);
}
@Override TypeFunPtr free(Type t) {
@@ -56,7 +55,13 @@ public class TypeFunPtr extends TypeNil {
@Override boolean isFree() { return _sig==null; }
// All fields directly listed
- public static TypeFunPtr make( byte nil, boolean open, Type[] sig, Type ret, long fidxs ) {
+ public static TypeFunPtr make(byte nil, boolean open, Type[] sig, Type ret, long bits) {
+ int[] set=bits<0 ? XInt.FULL : XInt.EMPTY;
+ if( bits>=0 ) for( int bit=0; bit<63; bit++ )
+ if( (bits&(1L<<bit))!=0 ) set=XInt.make(set,bit);
+ return make(nil,open,sig,ret,set);
+ }
+ public static TypeFunPtr make( byte nil, boolean open, Type[] sig, Type ret, int[] fidxs ) {
// Trim trailing args which match the implicit default.
Type def = open ? Type.BOTTOM : Type.TOP;
int nargs = sig.length;
@@ -74,7 +79,7 @@ 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 make((byte)2, _open, _sig,_ret,1L<<fidx ); }
+ public TypeFunPtr makeFrom( int fidx ) { return make((byte)2, _open, _sig,_ret,XInt.make(fidx) ); }
public static final Type[] TEMPTY = new Type[0];
@@ -114,7 +119,7 @@ public class TypeFunPtr extends TypeNil {
// Handle extra args
for( int i=min._sig.length; i<_sig.length; i++ )
args[i] = _sig[i];
- return make(xmeet0(that),_open | that._open, args, _ret.meet(that._ret), _fidxs | that._fidxs);
+ return make(xmeet0(that),_open | that._open, args, _ret.meet(that._ret), XInt.meet(_fidxs,that._fidxs));
}
static Type[] xmeet( Type[] ts0, Type[] ts1) {
if( ts0==ts1 ) return ts0;
@@ -125,7 +130,7 @@ public class TypeFunPtr extends TypeNil {
return ts;
}
- @Override TypeFunPtr xdual() { return malloc(dual0(), !_open, xdual(_sig), _ret.dual(), ~_fidxs); }
+ @Override TypeFunPtr xdual() { return malloc(dual0(), !_open, xdual(_sig), _ret.dual(), XInt.dual(_fidxs)); }
static Type[] xdual(Type[] ts) {
Type[] dual = new Type[ts.length];
for( int i=0; i<ts.length; i++ )
@@ -137,7 +142,7 @@ public class TypeFunPtr extends TypeNil {
if( _dual!=null ) return dual();
assert !_terned;
Type[] sigs = new Type[_sig.length];
- TypeFunPtr d = malloc(dual0(),!_open,sigs,null,~_fidxs);
+ TypeFunPtr d = malloc(dual0(),!_open,sigs,null,XInt.dual(_fidxs));
(_dual = d)._dual = this; // Cross link duals
// Return rdual
d._ret = _ret._terned ? _ret.dual() : _ret.rdual();
@@ -150,9 +155,9 @@ public class TypeFunPtr extends TypeNil {
// RHS is NIL; do not deep-dual when crossing the centerline
@Override public Type meet0() { return _nil==3 ? this : make((byte)3,_open,_sig,_ret,_fidxs); }
- @Override public boolean isHigh() { return _nil <= 1 || (_nil==2 && _fidxs==0); }
+ @Override public boolean isHigh() { return _nil <= 1 || (_nil==2 && _fidxs==XInt.EMPTY); }
- @Override public boolean _isConstant() { return (_nil==2 && Long.bitCount(_fidxs)==1) || (_nil==3 && _fidxs==0); }
+ @Override public boolean _isConstant() { return (_nil==2 && XInt.isConstant(_fidxs)) || (_nil==3 && _fidxs==XInt.EMPTY); }
@Override boolean _isFinal() { return true; }
@Override boolean _isGLB(boolean mem) { return true; }
@@ -171,13 +176,13 @@ public class TypeFunPtr extends TypeNil {
@Override public int log_size() { return 2; } // (1<<2)==4-byte pointers
public Type arg(int i) { return _sig[i]; }
- public long fidxs() { return _fidxs; }
+ public int[] fidxs() { return _fidxs; }
public Type ret() { return _ret; }
public int nargs() { return _sig.length; }
- public int fidx() { assert Long.bitCount(_fidxs)==1; return Long.numberOfTrailingZeros(_fidxs); }
+ public int fidx() { assert XInt.isConstant(_fidxs); return XInt.onlyBit(_fidxs); }
public int nfcns() {
- if( _fidxs < 0 ) return Integer.MAX_VALUE; // Infinite choices
- return Long.bitCount(_fidxs);
+ if( XInt.isHigh(_fidxs) ) return Integer.MAX_VALUE; // Infinite choices
+ return XInt.bitCount(_fidxs);
}
@@ -193,7 +198,7 @@ public class TypeFunPtr extends TypeNil {
@Override
int hash() {
- long hash = _ret.hashCode() ^ _fidxs ^ super.hash();
+ long hash = _ret.hashCode() ^ XInt.hash(_fidxs) ^ super.hash();
for( Type arg : _sig )
hash = hash*19 ^ arg.hashCode();
return Utils.fold(hash);
@@ -255,16 +260,13 @@ public class TypeFunPtr extends TypeNil {
}
String printFIDX() {
String tilde = isHigh() ? "~" : "";
- long fidxs = isHigh() ? ~_fidxs : _fidxs;
- String fidx = fidxs==0 ? ""
- : Long.bitCount(fidxs) == 1 ? ""+Long.numberOfTrailingZeros(fidxs)
- : fidxs == -1 ? "ALL"
- : "b"+Long.toBinaryString(fidxs); // Just some function bits
+ int[] fidxs = isHigh() ? XInt.dual(_fidxs) : _fidxs;
+ String fidx = fidxs==XInt.EMPTY ? "" : XInt.isConstant(fidxs) ? ""+XInt.onlyBit(fidxs)
+ : fidxs==XInt.FULL ? "ALL" : XInt.str(fidxs);
return tilde+fidx;
}
- // Usage: for( long fidxs=fidxs(); fidxs!=0; fidxs=nextFIDX(fidxs) ) { int fidx = Long.numberOfTrailingZeros(fidxs); ... }
- public static long nextFIDX(long fidxs) { return fidxs & (fidxs-1); }
+
@Override Type _upgradeType(java.util.HashMap<String,Type> types) {
diff --git a/src/main/java/com/seaofnodes/simple/type/TypeMem.java b/src/main/java/com/seaofnodes/simple/type/TypeMem.java
index 02cfc57b..5539e1cf 100644
--- a/src/main/java/com/seaofnodes/simple/type/TypeMem.java
+++ b/src/main/java/com/seaofnodes/simple/type/TypeMem.java
@@ -8,21 +8,22 @@ import java.util.BitSet;
/**
- * Represents a slice of memory corresponding to a set of aliases
+ * Public memory holds one field alias; private memory holds one whole object.
*/
public class TypeMem extends Type {
// Which slice of memory?
// 1 and TOP means no slice.
// 1 and BOT means all memory.
+ // 1 and TypeStruct means current fields of the private allocation.
// N means alias slice#N.
public int _alias;
- public Type _t; // Memory contents, some scalar type
+ public Type _t; // Scalar public contents, or private field values.
private static final Ary<TypeMem> FREE = new Ary<>(TypeMem.class);
private TypeMem(int alias, Type t) { super(TMEM); init(alias,t); }
private TypeMem init(int alias, Type t) {
- assert alias>1 || (t==Type.TOP || t==Type.BOTTOM);
+ assert alias>1 || (t==null || t==Type.TOP || t==Type.BOTTOM || t instanceof TypeStruct);
_alias = alias;
_t = t;
return this;
@@ -47,10 +48,34 @@ public class TypeMem extends Type {
return VISIT.isEmpty() ? f2.free(f) : f2.delayFree(f);
}
+ // Private memory describes one allocation. It is passed explicitly to
+ // initializers; it never joins the public heap until EscapeNode.
+ public static TypeMem makePrivate(TypeStruct shape) { return make(1,shape); }
+
+ public static TypeStruct empty(TypeStruct shape) {
+ Field[] fields = new Field[shape._fields.length];
+ for( int i=0; i<fields.length; i++ )
+ fields[i] = shape._fields[i].makeFrom(Type.BOTTOM);
+ return TypeStruct.make(shape._name,shape._open,fields);
+ }
+
+ public static TypeStruct zeroed(TypeStruct shape) {
+ Field[] fields=new Field[shape._fields.length];
+ for( int i=0; i<fields.length; i++ ) {
+ Field f=shape._fields[i];
+ Type zero=f._t.makeZero();
+ fields[i]=f.makeFrom(!f._final && f._t!=Type.BOTTOM && zero.isa(f._t) ? zero : Type.BOTTOM);
+ }
+ return TypeStruct.make(shape._name,shape._open,fields);
+ }
+
public static final TypeMem TOP = make(1, Type.TOP );
public static final TypeMem BOT = make(1, Type.BOTTOM);
- public static void gather(ArrayList<Type> ts) { ts.add(make(2,Type.NIL)); ts.add(make(2,TypeInteger.ZERO)); ts.add(BOT); }
+ public static void gather(ArrayList<Type> ts) {
+ ts.add(make(2,Type.NIL)); ts.add(make(2,TypeInteger.ZERO)); ts.add(BOT);
+ ts.add(makePrivate(TypeStruct.TEST)); ts.add(makePrivate(empty(TypeStruct.TEST)));
+ }
@Override
TypeMem xmeet(Type t) {
@@ -82,8 +107,8 @@ public class TypeMem extends Type {
@Override public boolean isHigh() { return _t.isHigh(); }
@Override public int log_size() { throw Utils.TODO(); }
@Override boolean _isFinal() { return _t._isFinal(); }
- @Override boolean _isGLB(boolean mem) { return _t._isGLB(true); }
- @Override public Type _glb(boolean mem) { return make(_alias,_t._glb(true)); }
+ @Override boolean _isGLB(boolean mem) { return _t instanceof TypeStruct s ? s.isGLB2() : _t._isGLB(true); }
+ @Override public Type _glb(boolean mem) { return make(_alias,_t instanceof TypeStruct s ? s.glb2() : _t._glb(true)); }
@Override int hash() { return 9876543 + _alias + _t.hashCode(); }
@@ -112,6 +137,18 @@ public class TypeMem extends Type {
@Override public String str() { return toString(); }
@Override Type _upgradeType(java.util.HashMap<String,Type> types) {
+ if( _t instanceof TypeStruct old && types.get(old._name) instanceof TypeMemPtr ptr ) {
+ // This struct records current contents, not a forward declaration.
+ // Complete its field list without replacing writes with declared
+ // types (or turning an unwritten field into an initialized one).
+ TypeStruct shape=old.isAry() ? old : ptr._obj;
+ Field[] fields=new Field[shape._fields.length];
+ for( int i=0; i<fields.length; i++ ) {
+ Field field=shape._fields[i], value=old.field(field._fname);
+ fields[i]=field.makeFrom(value==null ? Type.BOTTOM : value._t._upgradeType(types));
+ }
+ return makePrivate(TypeStruct.make(shape._name,shape._open,fields));
+ }
return make(_alias,_t._upgradeType(types));
}
}
diff --git a/src/main/java/com/seaofnodes/simple/type/XInt.java b/src/main/java/com/seaofnodes/simple/type/XInt.java
new file mode 100644
index 00000000..17fc4bd3
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/type/XInt.java
@@ -0,0 +1,241 @@
+package com.seaofnodes.simple.type;
+
+import com.seaofnodes.simple.util.*;
+import java.lang.Integer;
+import java.util.Arrays;
+
+// Stupidly overkill interned immutable int[]'s
+public abstract class XInt {
+ private static final Ary<Ary<int[]>> FREES = new Ary(Ary.class);
+ private static final Ary<Ary<int[]>> INTERNS = new Ary(Ary.class);
+ private static int PROBES, HITS, NEWS;
+
+ public static final int[] EMPTY = intern(new int[]{0, 0});
+ public static final int[] FULL = intern(new int[]{0,-1});
+ public static final int[] SET1 = make(1);
+ public static final int[] SET3 = make(SET1, 2);
+
+ static int idx(int x) { return (x>>5)+1; }
+ static int mask(int x) { return 1<<(x&31); }
+
+ // No hash set, still mutable
+ public static boolean isMutable(int[] xs) { return xs[0]==0; }
+ // Return hash code; must be set hence frozen
+ public static int hash(int[] xs) { return xs[0]; }
+
+ // High bit set (infinite 1's extension)
+ public static boolean isHigh(int[] xs) { return xs[xs.length-1] < 0; }
+
+ // Check for a bit
+ public static boolean bit(int[] xs, int x) {
+ int idx = idx(x);
+ if( idx >= xs.length )
+ return isHigh(xs);
+ return (xs[idx] & mask(x))!=0;
+ }
+
+ // Number of bits set
+ public static int bitCount(int[] xs) {
+ assert !isHigh(xs); // Dont ask if infinite
+ int cnt=0;
+ for( int i=1; i<xs.length; i++ )
+ cnt += Integer.bitCount(xs[i]);
+ return cnt;
+ }
+
+ // Largest bit
+ public static int max(int[] xs) {
+ assert !isHigh(xs);
+ return Integer.numberOfTrailingZeros(xs[xs.length-1])+((xs.length-1-1)<<5);
+ }
+
+ // Has 1 bit set
+ public static boolean isConstant(int[] xs) { return !isHigh(xs) && bitCount(xs)==1; }
+ // Return the 1 set bit or assert
+ public static int onlyBit(int[] xs) {
+ assert isConstant(xs);
+ for( int i=1; i<xs.length; i++ )
+ if( xs[i] != 0 )
+ return Integer.numberOfTrailingZeros(xs[i]) + ((i-1)<<5);
+ throw Utils.TODO("Should not reach here");
+ }
+
+ // Subset check: ys is a subset of xs
+ public static boolean subset(int[] xs, int[] ys) {
+ assert !isMutable(ys);
+ if( xs==ys ) return true;
+ if( xs==FULL ) return true;
+ if( ys==EMPTY ) return true;
+ int min = Math.min(xs.length,ys.length);
+ for( int i=1; i<min; i++ )
+ if( (xs[i] | ys[i])!=xs[i] )
+ return false;
+ if( xs.length == ys.length )
+ return true;
+ if( ys.length < xs.length )
+ return !isHigh(ys); // If ys is infinite, it'll hit one of the xs extra zeros
+ return isHigh(xs);
+ }
+
+
+ // Construct a set with 1 bit set
+ public static int[] make(int x ) { return make(EMPTY,x); }
+
+ // Add a bit
+ public static int[] make(int[] xs, int x ) {
+ assert !isMutable(xs);
+ int idx = idx(x);
+ if( idx >= xs.length && isHigh(xs) )
+ return xs; // Set in the default infinite
+ if( idx < xs.length && (mask(x) & xs[idx])!=0 )
+ return xs; // Already set
+ int len = Math.max(xs.length,idx+1);
+ if( (x&31)==31 && idx+1==len )
+ len++;
+ int[] ys = free(len);
+ System.arraycopy(xs,1,ys,1,xs.length-1);
+ ys[idx] |= mask(x);
+ return intern(ys);
+ }
+
+ // Interning, and life-cycle management
+ // Intern the array
+ public static int[] intern(int[] xs) {
+ if( !isMutable(xs) ) return xs; // Already interned
+ PROBES++;
+
+ // Trim trailing 0s or -1s, use the infinite extend bit
+ int len = xs.length;
+ while( len > 2 &&
+ ((xs[len-1]== 0 && xs[len-2]>=0) ||
+ (xs[len-1]== -1 && xs[len-2]< 0) ) )
+ len--;
+ // Replace with trimmed
+ if( len < xs.length ) {
+ var ys = free(len);
+ System.arraycopy(xs,1,ys,1,len-1);
+ xs = _free(xs,ys);
+ }
+
+ // Compute lame hash code
+ int hash = 0;
+ for( int i=1; i<len; i++ )
+ hash += xs[i];
+ if( hash == 0 ) hash = 0xCAFEBABE;
+ xs[0] = hash; // Set lame hashcode, and mark immutable
+
+ // Get length-specific INTERN table
+ var xss = INTERNS.atX(len);
+ if( xss==null )
+ INTERNS.setX( xs.length, xss = new Ary<>(int[].class));
+
+ // Probe for a hit
+ for( int[] ys : xss )
+ if( Arrays.equals(xs,ys) ) // Hit!
+ // Free new array and return old array
+ { HITS++; return _free(xs,ys); }
+
+ // Intern new array and return
+ return xss.push(xs);
+ }
+
+ // Return Ary of free int[]s of fixed size
+ private static Ary<int[]> _xss(int len) {
+ Ary<int[]> xss = FREES.atX(len);
+ if( xss==null )
+ FREES.setX(len, xss = new Ary<>(int[].class));
+ return xss;
+ }
+ // Pull free array from free list
+ static int[] free(int len) {
+ int[] xs = _xss(len).pop();
+ if( xs==null )
+ { NEWS++; xs = new int[len]; }
+ assert isMutable(xs);
+ return xs;
+ }
+ // Return ys. Put xs on free list
+ static int[] _free(int[] xs, int[] ys) {
+ Arrays.fill(xs,0); // Mutable again
+ _xss(xs.length).push(xs);
+ return ys;
+ }
+
+ // Meet: Set union
+ public static int[] meet(int[] xs, int[] ys) {
+ if( xs==ys ) return xs;
+ boolean isX=true, isY=true;
+ int min = Math.min(xs.length,ys.length);
+ int max = Math.max(xs.length,ys.length);
+ for( int i=1; i<max; i++ ) {
+ int x = i < xs.length ? xs[i] : isHigh(xs) ? -1 : 0;
+ int y = i < ys.length ? ys[i] : isHigh(ys) ? -1 : 0;
+ if( (x | y)!=x ) isX=false;
+ if( (x | y)!=y ) isY=false;
+ }
+ if( isX ) return xs;
+ if( isY ) return ys;
+ // Get a free one
+ int[] ms = free(max);
+ for( int i=1; i<min; i++ )
+ ms[i] = xs[i] | ys[i];
+ for( int i=min; i<ms.length; i++ ) {
+ int x = i < xs.length ? xs[i] : isHigh(xs) ? -1 : 0;
+ int y = i < ys.length ? ys[i] : isHigh(ys) ? -1 : 0;
+ ms[i] = x | y;
+ }
+ return intern(ms);
+ }
+
+ // Dual: flip all the bits
+ public static int[] dual(int[] xs) {
+ int[] ds = free(xs.length);
+ for( int i=1; i<xs.length; i++ )
+ ds[i] = ~xs[i];
+ return intern(ds);
+ }
+
+
+ // Next set bit, or -1
+ // for( int bit = XInt.next(bits,-1); bit >=0; bit = XInt.next(bits,bit) ) {...bit...}
+ public static int next(int[] xs, int x) {
+ assert !isHigh(xs); // No iterating infinite bitsets
+ x++; // Skip current
+ for( ; !bit(xs,x); x++ )
+ if( idx(x)>=xs.length )
+ return -1;
+ return x;
+ }
+
+ public static String str(int[] xs) { return str(new SB(),xs).toString(); }
+ public static SB str(SB sb, int[] xs) {
+ sb.p("[ ");
+ // Printing ranges;
+ // 1,2,3 prints 1-3
+ // 1,2,4 prints 1,2,4
+ //
+ int max = (xs.length-1)<<5;
+ int low = -1; // No low range; function identity 0 is main here.
+ for( int i=0; i<max; i++ ) {
+ if( bit(xs,i) ) { // Set bit, start or extend range
+ if( low==-1 ) low=i; // No range, start one
+ } else { // Break in sequence, ending a range
+ range(sb,low,i-1); // Print prior range, and start new one
+ low=-1; // Reset range
+ }
+ }
+ // Print any partial range
+ range(sb,low,max-1).unchar();
+ if( isHigh(xs) )
+ sb.p(",...");
+ return sb.p("]");
+ }
+
+ private static SB range( SB sb, int low, int mid ) {
+ if( low==-1 ) ;
+ else if( low==mid ) sb.p(low).p(',');
+ else if( low==mid-1 ) sb.p(low).p(',').p(mid).p(',');
+ else sb.p(low).p('-').p(mid).p(',');
+ return sb;
+ }
+}