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 +++++++++++++++++++----- 1 file changed, 513 insertions(+), 139 deletions(-) (limited to 'src/main/java/com/seaofnodes/simple/Parser.java') 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(); -- cgit v1.2.3