diff options
| author | Cliff Click <cliffc@acm.org> | 2026-08-10 11:11:18 -0700 |
|---|---|---|
| committer | Cliff Click <cliffc@acm.org> | 2026-10-24 15:56:44 -0700 |
| commit | ecd82ce8ebbefef223cd934cd87802f4d03c1867 (patch) | |
| tree | d7cb8e837ec90a646c0b6e15dcb93dea6b10ffd3 /src/main/java/com/seaofnodes | |
| parent | f5f0580331b4e29a0a0dc43d9885dfe0f0aed865 (diff) | |
| download | Simple-ecd82ce8ebbefef223cd934cd87802f4d03c1867.tar.gz Simple-ecd82ce8ebbefef223cd934cd87802f4d03c1867.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')
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; + } +} |
