From ecd82ce8ebbefef223cd934cd87802f4d03c1867 Mon Sep 17 00:00:00 2001 From: Cliff Click Date: Mon, 10 Aug 2026 11:11:18 -0700 Subject: Chapter 26: Construction and Private Memory 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 Co-authored-by: Claude Opus 5 --- src/main/java/com/seaofnodes/simple/Parser.java | 652 ++++++++++++++++----- .../com/seaofnodes/simple/codegen/CodeGen.java | 1 - .../simple/codegen/GlobalCodeMotion.java | 2 +- .../com/seaofnodes/simple/codegen/InstSelect.java | 1 + .../java/com/seaofnodes/simple/codegen/Opto.java | 4 +- .../com/seaofnodes/simple/codegen/TypeCheck.java | 5 + .../com/seaofnodes/simple/node/BulkMemPhiNode.java | 2 + .../com/seaofnodes/simple/node/CallEndNode.java | 6 +- .../java/com/seaofnodes/simple/node/CallNode.java | 26 +- .../com/seaofnodes/simple/node/EscapeMach.java | 12 + .../com/seaofnodes/simple/node/EscapeNode.java | 58 ++ .../java/com/seaofnodes/simple/node/FunNode.java | 15 +- .../java/com/seaofnodes/simple/node/IfNode.java | 2 +- .../java/com/seaofnodes/simple/node/LoadNode.java | 50 +- .../com/seaofnodes/simple/node/MemMergeNode.java | 15 + .../java/com/seaofnodes/simple/node/MemOpNode.java | 4 +- .../java/com/seaofnodes/simple/node/NewNode.java | 11 +- .../com/seaofnodes/simple/node/ReturnNode.java | 8 +- .../java/com/seaofnodes/simple/node/ScopeNode.java | 27 +- .../java/com/seaofnodes/simple/node/StoreNode.java | 14 +- .../com/seaofnodes/simple/node/cpus/arm/arm.java | 3 +- .../seaofnodes/simple/node/cpus/riscv/riscv.java | 3 +- .../simple/node/cpus/x86_64_v2/x86_64_v2.java | 3 +- .../java/com/seaofnodes/simple/type/Field.java | 10 +- .../com/seaofnodes/simple/type/TypeFunPtr.java | 58 +- .../java/com/seaofnodes/simple/type/TypeMem.java | 49 +- src/main/java/com/seaofnodes/simple/type/XInt.java | 241 ++++++++ 27 files changed, 1068 insertions(+), 214 deletions(-) create mode 100644 src/main/java/com/seaofnodes/simple/node/EscapeMach.java create mode 100644 src/main/java/com/seaofnodes/simple/node/EscapeNode.java create mode 100644 src/main/java/com/seaofnodes/simple/type/XInt.java (limited to 'src/main') 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 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 _fieldVars; + private record Constructor(TypeFunPtr sig, Lexer loc, int body, String[] ids, java.util.BitSet finals) {} + private Constructor _constructor; + private final HashMap ALLOCS = new HashMap<>(); + private final HashMap INITIALIZERS = new HashMap<>(); + private final HashMap> CAPTURES = new HashMap<>(); + private Ary _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 oldFields = _fieldVars; + Constructor oldConstructor = _constructor; + _fieldVars = new HashMap<>(); + _constructor = null; + Ary 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=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"; + 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 types = new Ary<>(Type.class); + Ary 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 captures=CAPTURES.get(shape._name); + Type[] types=new Type[nargs+captures.size()]; + for( int i=0; i"; 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=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 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 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 captures=CAPTURES.get(shape._name); + Node[] args=new Node[captures.size()+2]; + args[0]=ptr; args[1]=priv; + for( int i=0; i 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 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 { * */ 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 { * */ 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 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=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; iTINYBODY.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 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