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 | 741d78b6e840c88b7b183010c812ad982b342da1 (patch) | |
| tree | 84a75eb7ad351bffd20cd5fb152bab032b74271c /src/main/java/com/seaofnodes/simple/Parser.java | |
| parent | 8d90d2292bbdb23596849c3c3d1cda2f6cad783e (diff) | |
| download | Simple-linear-chapter28a.tar.gz Simple-linear-chapter28a.zip | |
Chapter 28a: Modules and Lazy Class Initializationlinear-chapter28a
Co-authored-by: Hsiang-Ying Fu <66062782+fuhsnn@users.noreply.github.com>
Co-authored-by: fuhsnn <66062782+fuhsnn@users.noreply.github.com>
Co-authored-by: Symious <14933944+symious@users.noreply.github.com>
Co-authored-by: Jun Kawasaki <jun784@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'src/main/java/com/seaofnodes/simple/Parser.java')
| -rw-r--r-- | src/main/java/com/seaofnodes/simple/Parser.java | 2612 |
1 files changed, 1586 insertions, 1026 deletions
diff --git a/src/main/java/com/seaofnodes/simple/Parser.java b/src/main/java/com/seaofnodes/simple/Parser.java index 145feb15..c3ad0284 100644 --- a/src/main/java/com/seaofnodes/simple/Parser.java +++ b/src/main/java/com/seaofnodes/simple/Parser.java @@ -1,11 +1,13 @@ package com.seaofnodes.simple; -import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; import com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.util.Utils; import com.seaofnodes.simple.node.ScopeNode.Kind; + +import static com.seaofnodes.simple.util.Utils.TODO; + import java.util.*; /** @@ -17,16 +19,32 @@ import java.util.*; */ public class Parser { - public static ConstantNode ZERO; // Very common node, cached here - public static ConstantNode NIL; // Very common node, cached here - public static XCtrlNode XCTRL; // Very common node, cached here - // Compile driver public final CodeGen _code; // The Lexer. Thin wrapper over a byte[] buffer with a cursor. private Lexer _lexer; + // Class prefix string; TODO: use something short: "%" + public static final String CLZ = "class:"; + private static final String clzPrefix = CLZ; + public static String addClzPrefix( String x ) { + assert !x.startsWith(clzPrefix); + return (clzPrefix+x).intern(); + } + public static boolean startsClzPrefix( String s ) { + return s.startsWith(clzPrefix); + } + public static boolean isPublicClassName( String name ) { + if( startsClzPrefix(name) ) name = name.substring(clzPrefix.length()); + return !name.isEmpty() && name.charAt(0)!='_' && name.indexOf("._")==-1; + } + + // Source file for compilation + public CompUnit _ref; + + // Current classname being parsed + private String _nestedType; /** * Current ScopeNode - ScopeNodes change as we parse code, but at any point of time * there is one current ScopeNode. The reason the current ScopeNode can change is to do with how @@ -78,34 +96,20 @@ public class Parser { */ public final Stack<ScopeNode> _xScopes = new Stack<>(); - ScopeNode _continueScope; - ScopeNode _breakScope; // Merge all the while-breaks here - FunNode _fun; // Current function being parsed + ScopeNode _breakScope; // Merge all the while-breaks here + ScopeNode _continueScope; // Merge all the while-continues here + ScopeNode _returnScope; // Merge all the function exits here + Ary<FRefNode> _frefs; // Forward refs created during this parse + TypeStruct _ctorOpenStruct; // Open struct being initialized by an allocation constructor block. + // Constructor exit types survive scope teardown until all fields are known. + // A null entry denotes a constructor with no returning path. + private ArrayList<IdentityHashMap<Var,Type>> _ctorExits; // Mapping from a type name to a Type. The string name matches // `type.str()` call. - public static HashMap<String, Type> TYPES = defaultTypes(); - - // Mapping from a type name to the constructor for a Type. - public final HashMap<String, StructNode> INITS; - - - public Parser(CodeGen code, TypeInteger arg) { - _code = code; - _scope = new ScopeNode(); - _continueScope = _breakScope = null; - ZERO = con(TypeInteger.ZERO).keep(); - NIL = con(Type.NIL).keep(); - XCTRL= new XCtrlNode().peephole().keep(); - TYPES = defaultTypes(); - INITS = new HashMap<>(); - } - - @Override - public String toString() { return _lexer.toString(); } - - public static HashMap<String, Type> defaultTypes() { - return new HashMap<>() {{ + public static final HashMap<String, Type> TYPES = new HashMap<>(); + public static final HashMap<String, Type> INIT_TYPES = + new HashMap<>() {{ put("bool",TypeInteger.U1 ); put("byte",TypeInteger.U8 ); put("f32" ,TypeFloat .F32); @@ -123,242 +127,442 @@ public class Parser { put("val" ,Type.TOP); // Marker type, indicates type inference put("var" ,Type.BOTTOM); // Marker type, indicates type inference }}; + + + public Parser(CodeGen code ) { + _code = code; } + @Override + public String toString() { return _lexer==null ? "" : _lexer.toString(); } + // Debugging utility to find a Node by index public Node f(int nid) { return _code.f(nid); } + // Read and set CTRL private Node ctrl() { return _scope.ctrl(); } private <N extends Node> N ctrl(N n) { return _scope.ctrl(n); } - - public void parse() { - // Graph callbacks during scope setup need a source position. - _lexer = new Lexer(sys.SYS); - + // Read and set Memory + private Node mem() { return _scope.mem(1); } + private void mem(Node mem) { _scope.mem(mem); } + // Structural label for precise memory Phis. The '$' prefix cannot be + // referenced in Simple source. + public static String memName(int alias) { return ("$"+alias).intern(); } + + // Ordinary parsing carries one whole-memory value. Alias partitioning is + // graph optimization, not parser state. + private void storeMem( Node st, int alias, Node prior ) { + mem(alias==1 ? st : mergeAlias(prior,alias,st)); + } + + // A semantic graph partition representing the complete memory after a + // precise alias update. ScopeNode treats this as one opaque value. + private Node mergeAlias(Node bulk, int alias, Node precise) { + assert bulk != null : "Missing bulk memory for alias #"+alias; + MemMergeNode merge = new MemMergeNode(null,bulk); + merge.alias(alias,precise); + return peep(merge); + } + + + public Ary<FRefNode> parse( CompUnit ref ) { + assert _scope == null && _breakScope == null && _continueScope == null && _returnScope == null; + assert _frefs == null; + _lexer = new Lexer(ref); + _frefs = new Ary<>(FRefNode.class); + // Starting Scope has control, memory, initial arguments + _scope = new ScopeNode(); _scope.define(ScopeNode.CTRL, Type.CONTROL , false, null, _lexer); _scope.define(ScopeNode.MEM0, TypeMem.BOT , false, null, _lexer); - _scope.define(ScopeNode.ARG0, TypeInteger.BOT, false, null, _lexer); - - ctrl(XCTRL); - _scope.mem(con(TypeMem.BOT)); + // Track active scopes for Graph display + _xScopes.push(_scope); + ctrl(_code.XCTRL); + mem(con(TypeMem.BOT)); - // Parse the sys import - while( !_lexer.isEOF() ) { - parseStatement(); - _lexer.skipWhiteSpace(); - } + // File-level struct declaration + _ref = ref; + _nestedType = ref._cname; + String typeName = addClzPrefix(ref._cname); - // Reset lexer for program text - _lexer = new Lexer(_code._src); - _xScopes.push(_scope); + ReturnNode clzret = parseStruct( true, typeName ); - // Parse whole program, as-if function header "{ int arg -> body }" - parseFunctionBody( _code._main,loc(),"arg"); - - // Kill an empty default main. Keep only if it was explicitly defined - // (programmer asked for a "main") or it has stuff (i.e. beginner - // default main). - FunNode main = _code.link(_code._main); - StopNode stop = _code._stop; - // Gather an explicit main - FunNode xmain = null; - for( Node n : stop._inputs ) - if( n instanceof ReturnNode ret && "main".equals(ret.fun()._name) ) - if( xmain != null ) throw Utils.TODO(); - else xmain = ret.fun(); - - if( main.ret().expr()._type==Type.TOP && main.uctrl()==null ) { - // Kill an empty default main; so it does not attempt to put a - // "main" in any final ELF file - main.setDef(1,XCTRL); // Delete default start input - stop.delDef(stop._inputs.find(main.ret())); - if( xmain != null ) - _code.setMain(xmain); + if( !_lexer.isEOF() ) throw _errorSyntax("unexpected"); - } else { - // We have a non-empty default main. - if( xmain != null ) - // Found an explicit "main" AND we have a default "main" - throw error("Cannot define both an explicit main and a default main"); - main._name = "main"; + // All still-unresolved forward refs + // should immediately resolve to a file-based name which is a Simple + // class type. + Ary<FRefNode> frefs = new Ary<>(FRefNode.class); + for( FRefNode fref : _frefs ) { + if( fref.nIns() != 1 && fref._con == FRefNode.FREF_TYPE ) continue; + CompUnit cu = ParseAll.findCompUnitOrThrow(_code,_ref,fref._name); + fref._con = TypeMemPtr.make((byte)2,TypeStruct.make(addClzPrefix(cu._cname),true),true); + frefs.push(fref); } - if( !_lexer.isEOF() ) throw _errorSyntax("unexpected"); + // The class being published + ref._clz = (TypeStruct)TYPES.get(typeName); // Clean up and reset _xScopes.pop(); _scope.kill(); - 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; }); + _scope = null; + _frefs = null; + + return frefs; } + /** * Parses a function body, assuming the header is parsed. */ - private ReturnNode parseFunctionBody( TypeFunPtr sig, Lexer loc, String... ids) { + private ReturnNode parseFunctionBody( TypeFunPtr sig, Lexer loc, String[] ids) { return parseFunctionBody(sig, loc, null, 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) { + // Record & restore the existing scope vars. The function parse might + // update fields, so when the parse is done reset back to the pre-parse + // state. + Node[] preParse = _scope.save(); + + // Parse function body normally + ReturnNode ret = _parseFunctionBody(null, sig,loc,finals,ids); + + // Reset all fields to pre-parse days + _scope.restore(preParse); + return ret; + } + + + // Parse a function body + + // Stack the {break,continue,return} scopes and a new lexical scope. + // Build a FunNode and parameters, extending scope by parm names. + // Parse the function body + // - while( not end-of-function ) + // - - parseStatement + // - - If isInit, also incrementally extend the self-type. + // If isInit + // - upgrade and close the self-type + // - Gather fields and store values + // - <init> returns self, not last + // Unstack the {break,continue,return} scopes. - private ReturnNode parseFunctionBody(TypeFunPtr sig, Lexer loc, java.util.BitSet finals, String... ids) { - return parseFunctionBody(sig,loc,finals,false,ids); + private ReturnNode _parseFunctionBody( String funName, TypeFunPtr sig, Lexer loc, String[] ids) { + return _parseFunctionBody(funName, sig, loc, null, 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 - // pre-function-parse values. - Node[] preParse = new Node[_scope.nIns()]; - for( int i=0; i<_scope.nIns(); i++ ) - preParse[i] = _scope.in(i)==null ? null : _scope.in(i).keep(); + private ReturnNode _parseFunctionBody( String funName, TypeFunPtr sig, Lexer loc, java.util.BitSet finals, String[] ids) { + Constructor outerCtor=_pendingCtor; + _pendingCtor=null; + boolean isInit = FunNode.isInit(funName); + boolean isCtor = isInit || isExplicitConstructorName(funName); + String typeName = isCtor ? ((TypeMemPtr)sig._sig[0])._obj._name : null; // 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; + _scope.push(new Kind.Func(funName,isCtor && !funName.endsWith(".<clinit>"))); + ScopeNode breakScope = _breakScope; _breakScope = null; ScopeNode continueScope = _continueScope; _continueScope = null; + ScopeNode returnScope = _returnScope; _returnScope = null; - FunNode fun = _fun = (FunNode)peep(new FunNode(loc(),sig,_nestedType,null,_code._start)); + int oldUID = _code.UID(); // Used to approximate function size + FunNode fun = (FunNode)peep(new FunNode(loc(),sig, funName, _ref, null, _ref._start)); // Once the function header is available, install in linker table - // allowing recursive functions. Linker matches on declared args and // 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(); - - // Build a multi-exit return point for all function returns - RegionNode r = new RegionNode( null,null,null).init(); - assert r.inProgress(); - PhiNode rmem = new BulkMemPhiNode(ScopeNode.MEM0,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(); - _code._stop.addDef(ret); + Node rpc = new ParmNode("$rpc",0,TypeRPC.BOT,fun,ConstantNode.seed(TypeRPC.BOT).peephole()).peephole(); // 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(_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,new ProjNode(_code._start,1,ScopeNode.MEM0).peephole()).peephole()); + _scope.ctrl(fun); // Scope control from function + // Private mem alias tracking per function + Node defaultMem = new ProjNode(_ref._start,1,ScopeNode.MEM0).peephole(); + Node privMem = new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,defaultMem).peephole(); + mem(privMem); // All args, "as-if" called externally for( int i=0; i<ids.length; i++ ) { Type t = sig.arg(i); - _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); - } - - // 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); - } + // Args take a default input if called from Start/external-world + _scope.define(ids[i], t, (i==0 && ids[0]=="self") || (finals!=null && finals.get(i)), new ParmNode(ids[i],i+2,t,fun,ConstantNode.seed(t).peephole()).peephole(), loc); + } + + if( isCtor && !isInit ) { + int lex=_scope.klast()._lexSize; + TypeStruct shape=(TypeStruct)TYPES.get(typeName); + for( Field fld : shape._fields ) { + int idx=fieldVarIdxBefore(lex,fld._fname); + if( idx<0 ) continue; + Var v=_scope.var(idx); + if( v._fref || v._extern!=0 || v._uninit || _scope.in(v)._type.isConstant() ) continue; + Node value=convertExpr( + new LoadNode(loc,fld._fname,fld._alias,ctrl(),fun.parm(3),fun.parm(2),off(shape,fld._fname)).peephole(),v.type()); + _scope.update(v,value); } } - Node last=ZERO; + + // The initializer owns its run-once flag store. Parent initialization + // is a structural requirement, executed before this body by CLInit lowering. + if( fun.isClz() ) { + Node self = fun.parm(2); + Field flag = ((TypeMemPtr)self._type)._obj.field("$init"); + Node prior = mem().keep(); + Node st = peep(new StoreNode(loc,"$init",flag._alias,flag._t,ctrl(),prior,self, + fldoff(self,"$init"),con(1),false)); + storeMem(st,flag._alias,prior); + prior.unkeep(); + } + + // Parse the function body. + Node last=_code.ZERO; // Last statement as the default result + + int nvar = 0; 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); + // Parse a statement; record last statement as default return + last = _code.add(parseStatement()); + // For <init> and <clinit>, local vars are really struct fields and + // get exposed as discovered. This allows late local functions to + // use early declared fields. + if( isInit ) + nvar = updateSelfAsFieldsDiscovered( typeName, nvar ); + } + + if( isInit ) finishConstructor(); + _pendingCtor=outerCtor; + + ParmNode self = fun.parm(2); + // Last expression is the return + if( ctrl()._type==Type.CONTROL ) { + // Constructors get special return types + if( fun.isClz() ) { + assert last!=null; + } else if( isCtor ) { + last = fun.parm(3); + } + addReturn(last); + // exit path is now dead (nothing live after a "return") + } + if( isCtor ) + upgradeSelfTypeAndStoreFields( typeName, fun.isClz(), self, fun.parm(3), isInit ); + + // Build a return from the _returnScope. + // Can be no returns for never-exit functions + Node rctl = _returnScope==null ? _code.XCTRL : _returnScope.ctrl().peephole(); + Node rmem = rctl==_code.XCTRL ? con(TypeMem.TOP) : _returnScope.mem(); + Node expr = rctl==_code.XCTRL ? con(Type.TOP) : _returnScope._inputs.last().peephole(); + ReturnNode ret = (ReturnNode)peep(new ReturnNode(rctl, rmem, expr, rpc, fun)); + fun.setRet(ret); + // Approximate function size, for inlining heuristics + fun._approxUIDs = _code.UID() - oldUID; + for( Node cend : ret.outs() ) + if( cend instanceof CallEndNode ) + throw TODO(); //_code.add(cend); // have call ends ALREADY? just barely made the Return + + // Export (and keep-alive) functions until prove dead + _ref.addFun(_code,fun); + + // Unstack parser state + if( _returnScope != null ) _returnScope.kill(); + _returnScope = returnScope; + _continueScope = continueScope; + _breakScope = breakScope; + _scope.pop(); + + return ret; + } + + private int updateSelfAsFieldsDiscovered( String typeName, int nvar ) { + int lex = _scope.klast()._lexSize + 2/*Skip self and selfMem*/; + // Load self type in and out of the TYPES hashtable. + TypeStruct tself = (TypeStruct)TYPES.get(typeName); + // Start looking for newly declared vars at the lexical scope start, + // skipping two ("self" and "selfmem") and fields seen already. + while( lex + nvar < _scope.nIns() ) { + Var v = _scope.var(lex + nvar++); + // Field with sharper type + Field old = tself.field(v._name); + Field fld = v._extern!=0 + ? Field.make(v._name,v.type(),v._extern,false,true) + : old == null + ? Field.make(v._name,v.type(),_code.alias(_ref._cname),v._final) + : old.makeFrom(v.type()); + tself = tself.addOrUpdate(fld); + } + TYPES.put(typeName,tself); + return nvar; + } + + + void upgradeSelfTypeAndStoreFields( String typeName, boolean isClz, ParmNode self, ParmNode smem, boolean upgrade ) { + int lex = _scope.klast()._lexSize; + assert _scope.var(lex)._name=="self"; + TypeStruct tself = (TypeStruct)TYPES.get(typeName); + int base = lex+2; // Skip ctrl, mem + if( upgrade ) { + // Default <init>/<clinit> defines the struct and can sharpen fields. + // A named constructor only initializes the already-closed struct. + for( int i=0; i<tself.nkids(); i++ ) { + Field tfld = tself._fields[i]; + int vidx = fieldVarIdx(base,tfld._fname); + if( vidx == -1 ) + continue; + Var v = _scope.var(vidx); + // Forward-ref fields where placed in tself but will get promoted + // to an outer scope - and should not be in tself. + if( v._fref ) + tself = tself.remove(i--); + else if( v.type() != tfld._t ) { + assert v.type().isa(tfld._t); + tself = tself.replace( tfld.makeFrom( v.type() ) ); + } + } + + // Close Struct type after parsing struct body. + TYPES.put( tself._name, tself = tself.close() ); + // Improve self, selfMem types in scope. + // Class self-type is a singleton pointer. Instance <init> receives the + // broad allocation pointer, while private memory tracks initialized fields. + self._declaredType = self._type = isClz ? TypeMemPtr.make((byte)2,tself,true) : TypeMemPtr.make(constructorRecv(tself)); + FunNode fun = self.fun(); + fun.setSig(fun.sig().makeFrom(self._type,0)); + if( !isClz ) // <init> returns upgraded private memory + smem._declaredType = smem._type = TypeMem.makePrivate(tself); + } + + if( !upgrade ) { + IdentityHashMap<Var,Type> exit = null; + if( _returnScope!=null && !_returnScope.ctrl()._type.isHigh() ) { + exit = new IdentityHashMap<>(); + int first = _scope._kinds.at(_scope.depth()-2)._lexSize+2; + for( int i=first; i<lex; i++ ) + exit.put(_scope.var(i),_returnScope.in(i)._type); } + _ctorExits.add(exit); + } else if( !isClz ) { + checkInitFields(tself,base); } - if( _privateFun==fun ) last = storeFields(initializer); - // Last expression is the return except for the top-level main - if( ctrl()._type==Type.CONTROL ) - if( fun.sig() != _code._main ) - fun.addReturn(ctrl(), _scope.mem(), last); - else { - fun.setDef(1,XCTRL); // Kill default main - _code.addAll(fun._outputs); + // When can _returnScope be null here? A never-exit constructor will + // not have any returns, and thus no need to gather values and store + // them into the (never) constructed object + if( _returnScope==null ) + return; + + // A MemMerge to gather field updates as stores. Classes update the + // normal public memory. <init> updates the private memory that got + // passed in - is treated like a normal argument and not like memory. + Node mmm = isClz ? _returnScope.mem(1) : smem; + + + // Store constructor results into fields + for( int i=0; i<tself.nkids(); i++ ) { + Field fld = tself._fields[i]; + int vidx = upgrade + ? fieldVarIdx(base,fld._fname) + : fieldVarIdxBefore(lex,fld._fname); + if( vidx == -1 ) + continue; + Var v = _scope.var(vidx); + if( v._fref || v._extern!=0 ) continue; // C storage has no Simple initializer + // NewNode's TOP field is the zero-code poison for a required + // field. Leave it alone unless the declaration body supplied a + // value; otherwise a user constructor must initialize it. + Node val = _returnScope.in(vidx); + if( upgrade && v._uninit && + !initialized(val._type) ) continue; + // A numeric merge can still be untyped while parsing. Retain its + // declared conversion at the helper boundary, as assignments do. + if( fld._t instanceof TypeInteger || fld._t instanceof TypeFloat ) + val=convertExpr(val,fld._t); + // Store value into extended struct + Node prior = mmm.keep(); + Node st = peep(new StoreNode(null, fld._fname, fld._alias, fld._t, null, prior, self, off(tself,fld._fname), val, fld._final)); + mmm = mergeAlias(prior,fld._alias,st); + prior.unkeep(); + } + mmm = mmm.peephole(); + + if( !isClz ) + // Stuff private mem into normal expression return value. + _returnScope.setDef(_returnScope.nIns()-1,mmm); + else + // Parser memory is one opaque whole-memory edge. Publish the + // completed class-field aggregate as the <clinit> return memory. + _returnScope.mem(mmm); + //_code.add(mmm); + } + + // Validate each user constructor, or an implicit empty one, before the + // declaration scope goes away. Later fields use their declaration defaults. + private static boolean initialized(Type t) { + return t!=Type.BOTTOM && !t.isHigh() && (!(t instanceof TypeNil tn) || tn.notNull()); + } + + private void checkInitFields(TypeStruct ts, int base) { + if( _returnScope==null || _returnScope.ctrl()._type.isHigh() ) return; + boolean implicit = _ctorExits.isEmpty(); + if( implicit ) _ctorExits.add(new IdentityHashMap<>()); + for( IdentityHashMap<Var,Type> exit : _ctorExits ) { + if( exit==null ) continue; + for( Field fld : ts._fields ) { + int idx = fieldVarIdx(base,fld._fname); + if( idx == -1 ) continue; + Var v = _scope.var(idx); + if( !v._uninit || v._fref ) continue; + Type t = exit.getOrDefault(v,_returnScope.in(idx)._type); + if( !initialized(t) ) + throw error("'"+ts._name+"' is not fully initialized, field '"+fld._fname+ + (implicit ? "' needs to be set in a constructor" : + "' is only partially set in the constructor"),v._loc); } + } + } - // Pop off the inProgress node on the multi-exit Region merge - assert r.inProgress(); - r ._inputs.pop(); - rmem._inputs.pop(); - rrez._inputs.pop(); - r._loc = loc(); // Final position - assert !r.inProgress(); - - // Force peeps, which have been avoided due to inProgress - ret.setDef(1,rmem.peephole()); - ret.setDef(2,rrez.peephole()); - ret.setDef(0,r.peephole()); - 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; + private int fieldVarIdx( int base, String name ) { + for( int i=base; i<_scope.nIns(); i++ ) + if( _scope.var(i)._name == name ) + return i; + return -1; + } - // Reset all fields to pre-parse days - for( int i=0; i<preParse.length; i++ ) - _scope.setDef(i,preParse[i]==null ? null : preParse[i].unkeep()); + // Named constructors update field Vars from the enclosing initializer + // scope. Constructor arguments and locals begin at limit and must not + // shadow the field state gathered into private memory. + private int fieldVarIdxBefore( int limit, String name ) { + for( int i=limit-1; i>=0; i-- ) + if( _scope.var(i)._name == name ) + return i; + return -1; + } - return ret; + private boolean isConstructorDecl(String name) { + TypeStruct self = _scope.constructorSelf(); + return self != null && name == initFieldName(self._name); + } + + private boolean inExplicitConstructor() { + Kind.Func func = (Kind.Func)_scope._kinds.at(_scope.enclosingFuncOrDecl()); + return isExplicitConstructorName(func._name); } + private boolean isExplicitConstructorName(String funName) { + TypeStruct self = _scope.constructorSelf(); + return self != null && funName == (self._name+"."+initFieldName(self._name)).intern(); + } + + private static TypeStruct constructorRecv(TypeStruct self) { + Field[] fs = new Field[self._fields.length]; + for( int i=0; i<fs.length; i++ ) { + Field fld = self._fields[i]; + fs[i] = fld.makeFrom(fld._t.makeStorage()); + } + return TypeStruct.make(self._name,self._open,fs); + } + + /** * Parses a block * @@ -371,9 +575,11 @@ public class Parser { private Node parseBlock(Kind kind) { // Enter a new scope _scope.push(kind); - Node last = ZERO; - while (!peek('}') && !_lexer.isEOF()) + Node last = _code.ZERO; + while (!peek('}') && !_lexer.isEOF()) { + _code.add(last); last = parseStatement(); + } // Exit scope last.keep(); _scope.pop(); @@ -396,8 +602,8 @@ 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 + else if (inStructDeclaration() && matchx("new")) return parseConstructorDeclaration(); + else if (matchx(";") ) return _code.ZERO; // Empty statement // Break ambiguity around leading function types and starting a block else if (peek('{') && !isTypeFun() ) { match("{"); @@ -407,22 +613,69 @@ 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 boolean inStructDeclaration() { + return _scope.klast() instanceof Kind.Func fun && FunNode.isInstance(fun._name); + } + + // Constructor declaration: new TypeName = { args -> body }; + private record Constructor(String name, Lexer body) {} + private Constructor _pendingCtor; + 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+"'"); + TypeStruct self = _scope.constructorSelf(); + assert self != null; + String name = requireId(); + String expect = initFieldName(self._name); + if( name != 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,";"); + if( _pendingCtor!=null ) throw error("Redefining constructor '"+name+"'"); + _pendingCtor=new Constructor(name,loc()); + // Parse the body after all field declarations are known, so self.x + // and x share a local slot even when x is declared below this body. + require("{"); + int depth=1; + while( depth>0 ) { + _lexer.skipWhiteSpace(); + if( _lexer.isEOF() ) throw error("Unclosed constructor body"); + char ch=_lexer.nextChar(); + if( ch=='{' ) depth++; + else if( ch=='}' ) depth--; + else if( ch=='"' || ch=='\'' ) { + char quote=ch; + while( !_lexer.isEOF() ) { + ch=_lexer.nextChar(); + if( ch=='\\' ) _lexer.nextChar(); + else if( ch==quote ) break; + } + } + } + require(";"); + return _code.ZERO; + } + + private void finishConstructor() { + if( _pendingCtor==null ) return; + Constructor ctor=_pendingCtor; + _pendingCtor=null; + Lexer after=_lexer; + _lexer=new Lexer(ctor.body()); + defineConstructor(ctor.name()); + _lexer=after; + } + + private void defineConstructor(String name) { + TypeStruct self=_scope.constructorSelf(); + Node ctor = constructorFunc(name); + TypeFunPtr sig = (TypeFunPtr)ctor._type; + String clzName = addClzPrefix(self._name); + TypeStruct clz = (TypeStruct)TYPES.get(clzName); + Field old = clz==null ? null : clz.field("<ctor>"); + if( old != null ) + throw error("Redefining constructor '" + name + "'"); + Field fld = Field.make("<ctor>",sig,_code.alias(_ref._cname),true); + TYPES.put(clzName,clz==null ? TypeStruct.make(clzName,true,fld) : clz.add(fld)); + require(ctor,";"); } /** @@ -475,7 +728,7 @@ public class Parser { // used as an indicator to switch off peepholes of the region and // associated phis; see {@code inProgress()}. - ctrl(new LoopNode(loc(),ctrl()).peephole()); // Note we set back edge to null here + ctrl(new LoopNode(loc(),null,ctrl()).peephole()); // Note we set back edge to null here // At loop head, we clone the current Scope (this includes all // names in every nesting level within the Scope). @@ -494,8 +747,8 @@ public class Parser { // IfNode takes current control and predicate Node ifNode = new IfNode(ctrl(), pred.keep()).peephole(); // Setup projection nodes - Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); - Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole(); + CFGNode ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); + CFGNode ifF = (CFGNode)new CProjNode(ifNode.unkeep(), 1, "False").peephole(); // for( ;;next ) body int nextPos = -1, nextEnd = -1; @@ -548,6 +801,8 @@ public class Parser { // it sets the second input to the corresponding input from the back // edge. If the phi is redundant, it is replaced by its sole input. var exit = _breakScope; + head.balanceLoopFRefs(_scope); + exit.balanceLoopFRefs(_scope); head.endLoop(_scope, exit); head.unkeep().kill(); @@ -565,12 +820,12 @@ public class Parser { _xScopes.push( exit ); } _scope = exit; - return ZERO; + return _code.ZERO; } private ScopeNode jumpTo(ScopeNode toScope) { ScopeNode cur = _scope.dup(); - ctrl(XCTRL); // Kill current scope + ctrl(_code.XCTRL); // Kill current scope // Prune nested lexical scopes that have depth > than the loop head // We use _breakScope as a proxy for the loop head scope to obtain the depth while( cur.depth() > _breakScope.depth() ) @@ -588,7 +843,7 @@ public class Parser { private void checkLoopActive() { if (_breakScope == null) throw error("No active loop for a break or continue"); } - private Node parseContinue() { checkLoopActive(); _continueScope = require(jumpTo( _continueScope ),";"); return ZERO; } + private Node parseContinue() { checkLoopActive(); _continueScope = require(jumpTo( _continueScope ),";"); return _code.ZERO; } private Node parseBreak () { checkLoopActive(); // At the time of the break, and loop-exit conditions are only valid if @@ -596,8 +851,8 @@ public class Parser { // conditions here, not the union. _breakScope.removeGuards(_breakScope.ctrl()); _breakScope = require(jumpTo(_breakScope ),";"); - _breakScope.addGuards(_breakScope.ctrl(), null, false); - return ZERO; + _breakScope.addGuards((CFGNode)_breakScope.ctrl(), null, false); + return _code.ZERO; } // Look for an unbalanced `)`, skipping balanced @@ -607,7 +862,7 @@ public class Parser { // Next X char handles skipping complex comments switch( _lexer.nextXChar() ) { case Character.MAX_VALUE: - throw Utils.TODO(); + throw TODO(); case ')': if( --paren<0 ) { posT( pos() - 1 ); @@ -641,8 +896,8 @@ public class Parser { pred.keep(); Node ifNode = new IfNode(ctrl(), pred).peephole(); // Setup projection nodes - Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); - Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole().keep(); + CFGNode ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); + CFGNode ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole().keep(); // In if true branch, the ifT proj node becomes the ctrl // But first clone the scope and set it as current ScopeNode fScope = _scope.dup(); // Duplicate current scope @@ -658,7 +913,7 @@ public class Parser { case ":" -> parseAsgn(); // pred ? asgn; case "&&" -> parseLogical(); // pred && expr case "||" -> pred; // pred || expr_is_ignored - default -> throw Utils.TODO(); + default -> throw TODO(); }; lhs.keep(); _scope.removeGuards(ifT); @@ -681,12 +936,14 @@ public class Parser { Node rhs = switch( fside ) { case "else" -> (doRHS=match(fside)) ? parseStatement() : con(lhs._type.makeZero()); case ":" -> (doRHS=match(fside)) ? parseAsgn() : con(lhs._type.makeZero()); - case "&&" -> rhs = pred; - case "||" -> rhs = parseLogical(); - default -> throw Utils.TODO(); + case "&&" -> con(lhs._type.makeZero()); // pred && expr_is_ignored + case "||" -> parseLogical(); // pred || expr + default -> throw TODO(); }; rhs.keep(); _scope.removeGuards(ifF); + // The RHS of `||` is parsed on the false side, so retain its updated + // control, memory and locals just as an explicit else arm does. if( doRHS || fside.equals("||") ) fScope = _scope; pred.unkeep(); @@ -709,9 +966,10 @@ public class Parser { Node ret = peep(new PhiNode("",r,lhs.unkeep(),rhs.unkeep())); // Immediately fail e.g. `arg ? 7 : ptr` ParseException err; - if( !fside.equals("else") && (err=ret.err()) !=null ) throw err; + if( !fside.equals("else") && (err=ret.err()) !=null ) throw err; + ret.keep(); r.peephole(); - return ret; + return ret.unkeep(); } /** @@ -725,22 +983,69 @@ 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; + return addReturn(expr); } + private Node addReturn(Node expr) { + expr.keep(); + // Need default memory, since it can be lazy, need to force + // a non-lazy Phi + mem(); // Force a pending lazy bulk-memory Phi + // For <init> and <clinit> - all fields in scope will be live-on-exit + // and stored into `self` and need to have lazy-phi's inserted. + int lexN = _scope.enclosingFuncOrDecl(); + Kind.Func k = (Kind.Func)_scope._kinds.at(lexN); + if( FunNode.isInit(k._name) ) { + int nestedLexSize = lexN+1 < _scope.depth() ? _scope._kinds.at(lexN+1)._lexSize : _scope.nIns(); + for( int i=k._lexSize; i<nestedLexSize; i++ ) + _scope.update(_scope.var(i),null); + if( _returnScope != null ) { + Var vexpr = _returnScope._vars.pop(); + Node oldX = _returnScope.removeLast(); + int rlen = _returnScope.nIns(); + while( rlen < nestedLexSize ) { + _returnScope._vars.add(_scope.var(rlen)); + _returnScope.addDef(con(_scope.var(rlen++).type().makeZero())); + } + _returnScope._vars.add(vexpr); + _returnScope.addDef(oldX); + } + } else if( isExplicitConstructorName(k._name) ) { + // Field bindings live in the enclosing declaration, not among the + // constructor's arguments/locals. Force lazy Phis on every exit. + int first = _scope._kinds.at(lexN-1)._lexSize+2; + for( int i=first; i<k._lexSize; i++ ) + _scope.update(_scope.var(i),null); + } + // No prior merge point? Just clone and hang on to it + if( _returnScope == null ) { + _returnScope = _scope.dup(); + while( lexN+1 < _returnScope.depth() ) + _returnScope._pop(); // Pop a nested block scope until we hit the function scope + _returnScope.define("$expr", inferredReturnType(expr._type), true, expr, null); + } else { + // For <init> and <clinit> - ALL FIELDS IN LAST SCOPE are live + // and need to have lazy-phi's inserted. + + // And fields might not match.... will need matching Var/define with default values + int rlen = _returnScope.nIns()-1; + RegionNode r = ctrl(new RegionNode(null, null,_returnScope.ctrl(), _scope.ctrl()).init().keep()); + _returnScope._merge(_scope, r, rlen); + Node oldExpr = _returnScope.in(rlen); + _returnScope.setDef(rlen,new PhiNode("$expr", r, oldExpr, expr).peephole()); + _code.add(r); + _returnScope.ctrl(r.unkeep()); + } + ctrl(_code.XCTRL); // Kill control + return expr.unkeep(); + } /** Parse: [name '='] expr */ private Node parseAsgn() { int old = pos(); String name = _lexer.matchId(); - int old_pre_equals = pos(); // Just a plain expression, no assignment. // Distinguish `var==expr` from `var=expr` if( name==null || KEYWORDS.contains(name) || !matchOpx('=','=') ) @@ -749,16 +1054,12 @@ public class Parser { // Find variable to update Var def = _scope.lookup(name); if( def==null ) + def = defineOpenConstructorField(name); + if( def==null ) throw error("Undefined name '" + name + "'"); - // If a method var, allow nested blocks and methods, but not fcns or other structs. - // Else safety check for accessing fields out of function scope. - if( instance(def) ) { - pos(old_pre_equals); // setup for `self.name = expr` - Var selfVar = _scope.lookup("self"); - Node self = liftExpr(_scope.in(selfVar),selfVar.type(),true); - return parsePostfixName(self,name); - } + if( def._extern!=0 ) // Parse the C binding as a memory lvalue + { pos(old); return parseExpression(); } // TOP fields are for late-initialized fields; these have never // been written to, and this must be the final write. Other writes @@ -768,34 +1069,100 @@ public class Parser { // The alloc() call added the allocation scope !_scope.canInit(def) ) throw error("Cannot reassign final '"+name+"'"); - checkSharedField(def); + checkSharedFunction(def); // Parse assignment expression - checkCaptureWrite(def); Node expr = parseAsgn(); // Lift expression, based on type - Node lift = liftExpr(expr.keep(), def.type(), true); + Type decl = def.type(); // Declared type + Node lift = convertExpr(expr.keep(), decl).keep(); // Update _scope.update(name,lift); + + // Check for storing into an instance field instead of a local var + int kx = _scope.kindx(def); // Declaration scope + int fx = _scope.enclosingFunction(); // Enclosing function scope + Kind kk = _scope._kinds.at(kx); + Kind fk = _scope._kinds.at(fx); + // Access instance field from 'self' + if( kx+1 == fx && kk instanceof Kind.Func inst && FunNode.isInstance(inst._name) && + fk instanceof Kind.Func method && !inExplicitConstructor() ) { + // Named field offset on an unknown struct type + TypeMemPtr self = (TypeMemPtr)_scope.var(fk._lexSize).type(); + TypeStruct sobj = (TypeStruct)TYPES.get(self._obj._name); + Field fld = sobj.field(name); + int alias = fld._alias; + Node ptr = _scope.in(method._lexSize); + Node off = fldoff(ptr,name); + Node prior = mem().keep(); + Node st = new StoreNode(loc(), name, alias, decl, ctrl(), prior, ptr, off, lift, inExplicitConstructor()).peephole().keep(); + storeMem(st,alias,prior); + st.unkeep(); + prior.unkeep(); + } + lift.unkill(); + + // Return un-lifted expr return expr.unkeep(); } - // Apply declared access permissions; widen floats and narrow integers. + // Widen ints to floats; narrow wide int types. // Early error if types do not match variable. - private Node liftExpr(Node expr, Type t, boolean isLoad) { - if( t==Type.BOTTOM || t==Type.TOP ) return expr; - // Constructor sentinels record missing initialization, not a value - // needing conversion. Keep them visible to the completion check. - if( expr instanceof ConstantNode && !(expr instanceof FRefNode) && - (expr._type==Type.TOP || expr._type==Type.BOTTOM) ) return expr; + private Node convertExpr( Node expr, Type t ) { + if( !expr._type.accessISA(t) ) + throw error("Type "+expr._type.str()+" is not of declared type "+t.str()); if( t instanceof TypeMemPtr tmp && tmp.isFinal() ) expr = peep(new ReadOnlyNode(expr)); - if( expr instanceof FunPtrNode && t instanceof TypeFunPtr ) return expr; + // A FunPtr carries the semantic edge to its function's Return. Do not + // hide that edge behind a Convert which can fold to a plain constant. + if( expr instanceof FunPtrNode && t instanceof TypeFunPtr ) + return expr; + // Auto-widen array to i64 (cast ptr to raw int bits) + if( t == TypeInteger.BOT && expr._type instanceof TypeMemPtr tmp && tmp._obj.isAry() ) + return peep(new AddNode(peep(new PtrToIntNode(expr)),off(tmp._obj,"[]"))); + // Widen ints to floats, narrow wide types return peep(new ConvertNode(t,expr)); } + // Inferred `var` declarations describe writable slots, not the exact + // value used to initialize them. Explicitly typed locals retain the + // user's nullability and numeric-width guarantees; `val` retains the + // initializer's precise type. + private static Type mutableInferredType(Type t) { + return switch( t ) { + case TypeInteger ti -> TypeInteger.BOT; + case TypeFloat tf -> TypeFloat.F64; + case TypeMemPtr tmp -> tmp.makeVar(); + case TypeFunPtr tfp -> tfp; + case TypeConAry<?> a -> a; + case TypePtr ptr -> TypePtr.PTR; + case TypeScalar ts -> TypeScalar.BOT; + default -> t; + }; + } + + private Type constructorFieldType(Type t) { + if( t instanceof TypeMemPtr ptr && ptr._obj.isConAry() ) { + TypeStruct declared=(TypeStruct)TYPES.get(ptr._obj._name); + TypeStruct shape=typeAry(declared._fields[1]._t,ptr._obj._fields[1]._final); + return TypeMemPtr.make((byte)2,shape,false).withAccess(ptr._ro); + } + return mutableInferredType(t); + } + + // Seed an inferred return merge with the declared numeric family instead + // of a transient constant/range. Pointer nullability remains part of the + // inferred function result and is combined by the later Phi. + private static Type inferredReturnType(Type t) { + return switch( t ) { + case TypeInteger ti -> TypeInteger.BOT; + case TypeFloat tf -> TypeFloat.F64; + default -> t; + }; + } + private Node widenInt( Node expr, Type t ) { return (expr._type instanceof TypeInteger || expr._type==Type.NIL) && t instanceof TypeFloat ? peep(new ToFloatNode(expr)) : expr; @@ -822,23 +1189,6 @@ public class Parser { return require(n,";"); } - // Widen a reassignable binding without discarding the initializer's - // permissions. Lattice glb includes readonly memory and is too broad here. - private Type inferredType(Type t) { - if( t instanceof TypeMemPtr tmp ) { - if( tmp._obj.isAry() ) { - Type elem = tmp._obj._fields[1]._t; - if( elem instanceof TypeConAry ) - elem = ((TypeMemPtr)TYPES.get(tmp._obj._name))._obj._fields[1]._t; - return typeAry(elem,tmp._obj._fields[1]._final).withAccess(tmp._ro).makeNullable(); - } - Type base = TYPES.get(tmp._obj._name); - TypeMemPtr ptr = (TypeMemPtr)base; - return ptr.withAccess(tmp._ro).makeNullable(); - } - return t.glb(false); - } - // The binding modifier sits immediately before the name. It never // changes permissions on the referenced object. private boolean bindingFinal(Type t) { @@ -857,35 +1207,50 @@ public class Parser { Lexer loc = loc(); String name = requireId(); // Optional initializing expression follows - boolean fld_final = false; // Fixed field independent of pointee access - boolean initialized = match("="); + boolean fld_final = false; // Field is final, but not deeply final + boolean uninit = false; // Required non-null constructor field Node expr; - if( initialized ) { - expr = isExternDecl() ? externDecl(name,t) - : _scope.inConstructor() && name.equals(shortName(_nestedType)) - ? constructorFunc() : parseAsgn(); + if( match("=") ) { + if( isExternDecl() ) { + if( !(t instanceof TypeFunPtr) ) { + Node ptr = externData(name,t); + // The lexical binding is a fixed address; its contents are mutable. + if( !_scope.define(name,t,true,ptr,loc) ) + throw error("Redefining name '"+name+"'",loc); + _scope.lookup(name)._extern = _code.externAlias(name); + return _code.ZERO; + } + expr = externDecl(name,t); + t = expr._type; // Upgrade declared type to the exact extern decl type + } else if( isConstructorDecl(name) ) { + expr = constructorFunc(name); + } else { + expr = 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; - } + if( !xfinal ) t = mutableInferredType(t); + // Instance fields may change until the constructor finishes. + // Keep the declared family/access, not the default's constant + // value or string contents. Class constants remain static. + else if( _scope.klast() instanceof Kind.Func fun && FunNode.isInstance(fun._name) && !(t instanceof TypeFunPtr) ) + t = constructorFieldType(t); } + // 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 if( _code._link(tfp)!=null ) _code.link(tfp).setName(name); // Assign debug name to Simple function + if( t instanceof TypeFunPtr decl && expr._type instanceof TypeFunPtr tfp && tfp.isConstant() ) { + t = decl.makeFrom(tfp.fidx()); + FunNode fun = _code.link(tfp); + if( fun != null ) + fun.setName(name); // Assign debug name to Simple function } } else { + // Since no initializer, not-final but might need an initializer + // Need an expression to infer the type. // Also, if not-null then need an initializing expression. // Allowed in a class def, because type/init will happen in the constructor. @@ -893,281 +1258,393 @@ public class Parser { throw errorSyntax("=expression"); // Initial value for uninitialized struct fields. expr = switch( t ) { - // Nullable pointers get a NIL; not-null get a TOP which - // signals that they *must* be initialized in the constructor. - case TypeNil tn -> tn.nullable() ? NIL : con(Type.TOP); - case TypeInteger ti -> xfinal ? con(Type.TOP) : ZERO; - case TypeFloat tf -> con(xfinal ? Type.TOP : TypeFloat.FZERO); + // Required pointers retain their declared shape, but include + // null until every constructor path assigns them. + case TypeNil tn -> tn.nullable() ? con(Type.NIL) : con(t.meet(Type.NIL)); + case TypeInteger ti -> xfinal ? con(Type.BOTTOM) : _code.ZERO; + case TypeFloat tf -> con(xfinal ? Type.BOTTOM : TypeFloat.FZERO); // Bottom signals type inference: they must be initialized in // the constructor and that's when we'll discover the type. case Type tt -> { assert tt==Type.BOTTOM; yield con(tt); } }; - // Nullable fields are set in the constructor, but remain shallow final. - // e.g. final pointer to a r/w array - if( t instanceof TypeNil tn && !tn.nullable() && xfinal ) - fld_final = true; + // Every non-null pointer without a default is constructor-required. + // Without `!` it is also shallow-final; mutability and definite + // initialization are independent properties. + if( (xfinal && !(t instanceof TypeNil)) || (t instanceof TypeNil tn && !tn.nullable()) ) { + uninit = true; + if( xfinal ) fld_final = true; + } } // Lift expression, based on type - Node lift = liftExpr(expr, t, true); + // Do not let an authoritative Convert hide the nullable + // not-yet-initialized state from constructor-local type flow. + Node lift = uninit ? expr : convertExpr(expr, t); - // Lift type to the declaration. This will report as an error later if - // we cannot lift the type. // Define a new name if( !_scope.define(name,t,xfinal || fld_final,lift, loc) ) throw error("Redefining name '" + name + "'", loc); - _scope.lookup(name)._init = initialized; + _scope.lookup(name)._uninit = uninit; return lift; } - /** * Parse a struct declaration, and return the following statement. * Structs cannot be redefined, but can be nested. * * @return zero */ - private String _nestedType; - private static String shortName(String name) { return name.substring(name.lastIndexOf('.')+1); } + private Node parseStruct( ) { + // keyword "struct" already parsed, so expect the struct name next. + String typeName = requireId(); + require("{"); + + // create type class:NESTED.typeName; no fields. - private Node parseStruct() { String old = _nestedType; - 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("{"); - 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; + String fullName = _scope.depth()==1 && _nestedType.endsWith(typeName) ? old : (old+"."+typeName).intern(); + _nestedType = fullName; // Recursive structs start with this as their basename + + ReturnNode ret = parseStruct( false, fullName ); + + _nestedType = old; + + // Insert a field into class:NESTED.typeName of "val typeName = fcn-ptr-to-init" + // Function does malloc internally. + // Future self: means subtyping has to deal with "who does the malloc" + String clzName = addClzPrefix(fullName); + TypeFunPtr sig = ret.fun().sig(); + TypeStruct ts = (TypeStruct)TYPES.get(clzName); + String initName = initFieldName(fullName); + Field oldFld = ts==null ? null : ts.field(initName); + Field fld = oldFld==null + ? Field.make(initName,sig,_code.alias(_ref._cname),true) + : oldFld.makeFrom(sig); + Field ctorFld = ts==null ? null : ts.field("<ctor>"); + // A same-named struct shares its file's class object and flag. + Field flag = ts==null ? null : ts.field("$init"); + ts = ctorFld==null + ? TypeStruct.make(clzName,false,fld) + : TypeStruct.make(clzName,false,fld,ctorFld); + if( flag!=null ) ts=ts.add(flag); + TypeFunPtr alloc = makeAllocator((TypeStruct)TYPES.get(fullName),ret,ctorFld); + ts = ts.add(Field.make("<new>",alloc,_code.alias(_ref._cname),true)); + TYPES.put(clzName,ts); + + // Insert a field in the containing class with the nested class type. + // This is basically sugar for "val CLZNAME = <init>" + // and is a nicer version of updateSelfAsFieldsDiscovered. + _scope.define(typeName, sig, true, new FunPtrNode(sig,_code._start,ret).peephole(), loc()); + require("}"); - require(";"); - return ZERO; + return require(_code.ZERO,";"); + } + + // Build the declaration-side allocation helper. Allocation sites only + // call this function; all layout, initialization and publication choices + // are made here, where the complete struct and constructor are known. + private TypeFunPtr makeAllocator(TypeStruct ts, ReturnNode initRet, Field ctorFld) { + TypeFunPtr ctor = ctorFld==null ? null : (TypeFunPtr)ctorFld._t; + int nargs = ctor==null ? 0 : ctor.nargs()-2; // Hide self and selfMem + Type[] atypes = new Type[nargs]; + for( int i=0; i<nargs; i++ ) atypes[i] = ctor.arg(i+2); + TypeMemPtr ptr = TypeMemPtr.make(ts); + TypeFunPtr sig = TypeFunPtr.make1((byte)2,true,atypes,ptr,_code.fidx(_ref._cname)); + String name = (ts._name+".<new>").intern(); + + int oldUID = _code.UID(); + FunNode fun = (FunNode)new FunNode(loc(),sig,name,_ref,null,_ref._start).peephole(); + _code.link(fun); + Node rpc = new ParmNode("$rpc",0,TypeRPC.BOT,fun,ConstantNode.seed(TypeRPC.BOT).peephole()).peephole(); + Node pub = new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun, + new ProjNode(_ref._start,1,ScopeNode.MEM0).peephole()).peephole(); + Node[] parms = new Node[nargs]; + for( int i=0; i<nargs; i++ ) + parms[i] = new ParmNode("arg"+i,i+2,atypes[i],fun,ConstantNode.seed(atypes[i]).peephole()).peephole(); + + NewNode nnn = new NewNode(ts,fun,off(ts," len")).init(); + Node self = new ProjNode(nnn,0,ts.str()).init(); + Node priv = new ProjNode(nnn,1,"#selfMem").init(); + Node[] initArgs = new Node[]{fun,pub,self,priv, + new FunPtrNode(initRet.fun().sig(),_code._start,initRet).peephole()}; + CallEndNode cend = rawCall(initArgs,initRet.fun()); + Node ctl = new CProjNode(cend,0,ScopeNode.CTRL).peephole(); + pub = new ProjNode(cend,1,ScopeNode.MEM0).peephole(); + priv = new ProjNode(cend,2,"#selfMem").peephole(); + + if( ctor != null ) { + Node[] cargs = new Node[nargs+5]; + cargs[0]=ctl; cargs[1]=pub; cargs[2]=self; cargs[3]=priv; + for( int i=0; i<nargs; i++ ) cargs[i+4]=parms[i]; + FunNode cfun = _code.link(ctor); + cargs[cargs.length-1] = new FunPtrNode(ctor,_code._start,cfun.ret()).peephole(); + cend = rawCall(cargs,cfun); + ctl = new CProjNode(cend,0,ScopeNode.CTRL).peephole(); + pub = new ProjNode(cend,1,ScopeNode.MEM0).peephole(); + priv = new ProjNode(cend,2,"#selfMem").peephole(); + } + + priv.keep(); + for( Field field : ts._fields ) { + Type storage = field._t.makeStorage(); + Field escaped = field._final ? field : field.makeFrom(storage).makeFrom(true); + pub.keep(); + Node esc = new EscapeNode(escaped,self,priv,pub).peephole(); + MemMergeNode merge = new MemMergeNode(null,pub); + merge.alias(field._alias,esc); + Node next = merge.peephole(); + pub.unkeep(); + pub = next; + } + priv.unkill(); + ReturnNode ret = (ReturnNode)new ReturnNode(ctl,pub,self,rpc,fun).peephole(); + fun.setRet(ret); + fun._approxUIDs = _code.UID() - oldUID; + _ref.addFun(_code,fun); + return sig; } - 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 CallEndNode rawCall(Node[] args, FunNode target) { + // Establish this known declaration-side edge immediately, but do not + // peephole or inline it while the surrounding factory is being built. + // IterPeeps therefore starts with the complete initializer caller set. + CallNode call = new CallNode(loc(),args).init(); + CallEndNode cend = new CallEndNode(call,TypeRPC.constant(_code.rpc(_ref._cname))).init(); + call.link(target); + return cend; + } + + // Parse a struct declaration (not an allocation); file-level is a class-init, and scope structs are normal + private ReturnNode parseStruct( boolean isClz, String typeName ) { + var oldExits = _ctorExits; + _ctorExits = new ArrayList<>(); + // Record & restore global state set during parsing the <init> code + Node oldCtrl= _scope.ctrl().keep(); + Node oldMem = _scope.mem ().keep(); + + // Make the future clazz/instance struct. + TypeStruct tself = (TypeStruct)TYPES.get(typeName); + if( tself==null ) + TYPES.put(typeName, tself = TypeStruct.make(typeName,true)); + else { + assert tself._open; + TYPES.put(typeName,tself = tself.defined()); + } + + // Class object keeps a runtime flag to signal the <clinit> has run. + if( isClz ) + TYPES.put(typeName,tself=tself.add(Field.make("$init",TypeInteger.BOOL,_code.alias(_ref._cname),false))); + + // Signature of the default constructor + TypeFunPtr sig = structInitSig(isClz,tself); + // Function and argument names of default constructor + String fname = (typeName + (isClz ? ".<clinit>" : ".<init>")).intern(); + String[] ids = new String[]{"self", isClz ? "arg" : "#selfMem"}; + + // Struct decls look like function bodies. Parse function body normally. + ReturnNode ret = _parseFunctionBody(fname,sig,loc(),ids); + _ctorExits = oldExits; + // Unwind global state. + ctrl(oldCtrl.unkeep()); + mem (oldMem .unkeep()); + return ret; } - 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()); + // <cl/init> signature: { self arg/selfMem -> BOT/selfMem } + // Self-memory is the rare *private* (never aliased) memory for the new object. + private TypeFunPtr structInitSig( boolean isClz, TypeStruct tself ) { + // Private uninitialized memory from new object, no escaped values. + TypeMem privMem = isClz ? null : TypeMem.make(1,tself,true,isClz,false,null,null); + Type targ = isClz ? TypeInteger.BOT : privMem; + // Return for private memory has to name all escaped fields, which are not known yet. + // Escape ALL fields, and sharpen later. + Type tret = isClz ? Type.BOTTOM : TypeMem.makePrivate(tself); + Type[] targs = new Type[]{ TypeMemPtr.make((byte)2,tself,isClz), targ }; + // FIDX for FREFs is in the global namespace, needs the correct clz name. + String clzBare = tself._name; + int order = 3; // 3 is always the <init> order, 2nd in the file just behind the <clinit> + if( isClz ) { + assert startsClzPrefix( clzBare ); + clzBare = clzBare.substring(clzPrefix.length()).intern(); + order = 2; // 2 is always the <clinit> order, first in file and skipping reserved #1 + } + return TypeFunPtr.make1((byte)2, true, targs, tret, _code.fidx(clzBare,order)); } - 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; + // A class type carries the constructor function as a required final field. + private TypeStruct classStruct( String typeName, TypeFunPtr init, boolean open ) { + return TypeStruct.make(addClzPrefix(typeName),open,Field.make(initFieldName(typeName),init,_code.alias(_ref._cname),true)); } - 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 static String initFieldName( String typeName ) { + if( startsClzPrefix(typeName) ) + typeName = typeName.substring(clzPrefix.length()); + int idx = typeName.lastIndexOf('.'); + return (idx == -1 ? typeName : typeName.substring(idx+1)).intern(); } - 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 TypeStruct latestStruct( TypeStruct ts ) { + Type t = TYPES.get(ts._name); + return t instanceof TypeStruct ts2 ? ts2 : ts; } - 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 Field fieldOrOpen( TypeStruct ts, String name, boolean xfinal ) { + ts = latestStruct(ts); + Field fld = ts.field(name); + if( fld != null ) return fld; + if( !ts._open || !ts._fref ) return null; + fld = Field.make(name,Type.BOTTOM,_code.alias(_ref._cname),xfinal); + TYPES.put(ts._name,ts = ts.add(fld)); + if( _ctorOpenStruct != null && _ctorOpenStruct._name == ts._name ) + _ctorOpenStruct = ts; + return fld; } - 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,new ProjNode(_code._start,1,ScopeNode.MEM0).peephole()).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; + private Var defineOpenConstructorField( String name ) { + if( _ctorOpenStruct==null ) return null; + Field fld = fieldOrOpen(_ctorOpenStruct,name,true); + if( fld == null ) return null; + _scope.define(name,fld._t,fld._final,con(Type.TOP),loc()); + return _scope.lookup(name); } + // 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 // types (which ARE valid here) from local vars in an (optional) forward // ref type position. // t = int|i8|i16|i32|i64|u8|u16|u32|u64|byte|bool | flt|f32|f64 | val | var | struct[?] + + // Structs have nested names, and can be discovered with a partial name + // match. Example, searching for a type name "C.D" in the namespace "A.B". + // This will match "A.B.C.D", then "A.C.D", then "C.D". + + // Will also match external names like "sys.io" or "externModule.subtype.subtype" + private Type type() { - int old1 = pos(); - boolean writable = match("!"); - boolean readonly = !writable && match("~"); // Only type with a leading `{` is a function pointer... if( peek('{') ) return typeFunPtr(); // Otherwise you get a type name + int old1 = pos(); + boolean writable = match("!"); + boolean readonly = !writable && match("~"); String tname = _lexer.matchId(); if( tname==null ) return posT(old1); // Convert the type name to a type. - Type t0 = TYPES.get(tname); + Type t0 = TYPES.get(tname), t1 = t0; // No new types as keywords - if( t0 == null && KEYWORDS.contains(tname) ) - return posT(old1); - if( t0 == Type.BOTTOM || t0 == Type.TOP ) return t0; // var/val type inference + if( KEYWORDS.contains(tname) ) { + if( t0 == null ) return posT(old1); + // Something like "int" or "f64" or "var". + // Can be "int[]" so still need to check array-ness + } else { + if( t0 == Type.BOTTOM || t0 == Type.TOP ) return t0; // var/val type inference + + if( t0 != null ) { + while( true ) { + int old2 = pos(); + if( !match(".") ) break; + String sname = matchId(); + if( sname==null ) { pos(old2); break; } + sname = tname+"."+sname; + Type t = TYPES.get(sname); + if( t==null ) { pos(old2); break; } + tname = sname; + t0 = t; + } + } else { + // Type A.B.C starts with just A. Search our nested type path, + // e.g. path "X.Y.Z", in order: + + // If A==X its now a local type. + // If another dot, repeat for B==Y then C==Z + // Else if AX.B exists, done + // Else AX.B is a local forward ref. + // + // Else its a short cut form, one of: + // X.Y.Z.A + // X.Y.A + // X.A + // Else this all failed, assume a forward ref with A as an external symbol: + // A + + String root = _nestedType; + int ridx = root.indexOf('.'); + if( ridx != -1 ) root = root.substring(0,ridx); + if( tname.equals(root) ) { + boolean localForward = false; + while( true ) { + int old2 = pos(); + if( !match(".") ) break; + String sname = matchId(); + if( sname==null ) { pos(old2); break; } + sname = tname+"."+sname; + Type t = TYPES.get(sname); + if( t==null && !(_nestedType.equals(tname) || _nestedType.startsWith(tname+".")) && !localForward ) { + pos(old2); + break; + } + tname = sname; + t0 = t; + localForward |= t==null; + } + } else { + String nest = _nestedType, fullq = tname; + while( true ) { + fullq = nest+"."+tname; // Full qualified name + t0 = TYPES.get(fullq); + if( t0 != null ) + { tname = fullq; break; } // Search succeeded + // Subtract a layer from nested typenames + int idx = nest.lastIndexOf('.'); + if( idx== -1 ) break; // Search failed; leave as an external symbol + nest = nest.substring(0,idx); + } + if( t0==null ) { + CompUnit cu = ParseAll.findCompUnit(_code,_ref,tname); + if( cu != null ) { + tname = cu._cname; + t0 = TYPES.get(tname); + } + while( true ) { + int old2 = pos(); + if( !match(".") ) break; + String sname = matchId(); + if( sname==null ) { pos(old2); break; } + tname = tname+"."+sname; + t0 = TYPES.get(tname); + } + } else { + while( true ) { + int old2 = pos(); + if( !match(".") ) break; + String sname = matchId(); + if( sname==null ) { pos(old2); break; } + sname = tname+"."+sname; + Type t = TYPES.get(sname); + if( t==null ) { pos(old2); break; } + tname = sname; + t0 = t; + } + } + } + } + + tname = tname.intern(); + // Still no type found? Assume forward reference + t1 = t0 == null + ? TypeStruct.forward(tname) // Null: assume a forward ref type + : t0; + } + + // Structs are always actually references + if( t1 instanceof TypeStruct ts1 ) + t1 = TypeMemPtr.make(ts1); - // Check for subtype. - while( true ) { - int old2 = pos(); - if( !match(".") ) break; - String sname = _lexer.matchId(); - if( sname==null ) { pos(old2); break; } - String tsname = tname+"."+sname; - t0 = TYPES.get(tsname); - if( t0==null ) { pos(old2); t0 = TYPES.get(tname); break; } - tname = tsname; - } - - // Still no type found? Assume forward reference - Type t1 = t0 == null ? TypeMemPtr.make(TypeStruct.open(tname)) :t0; // Null: assume a forward ref type // A qualifier before the name applies to the struct reference, not // to an enclosing array. Arrays have their own [] / [~] qualifier. if( t1 instanceof TypeMemPtr tmp ) { @@ -1179,14 +1656,15 @@ public class Parser { Type t2 = t1; while( true ) { if( match("?") ) { - if( !(t2 instanceof TypeMemPtr tmp) ) + if( t2 instanceof TypeMemPtr tmp ) { + if( tmp.nullable() ) throw error("Type "+t2+" already allows null"); + t2 = tmp.makeNullable(); + } else throw error("Type "+t0+" cannot be null"); - if( tmp.nullable() ) throw error("Type "+t2+" already allows null"); - t2 = tmp.makeNullable(); } else if( match("[~]") ) { - t2 = typeAry(t2,true).makeRO(); + t2 = TypeMemPtr.make(typeAry(t2,true)).makeRO(); } else if( match("[]") ) { - t2 = typeAry(t2,false); + t2 = TypeMemPtr.make(typeAry(t2,false)); } else break; } @@ -1198,27 +1676,64 @@ public class Parser { int old2 = pos(); if( !match("!") ) match("~"); String id = _lexer.matchId(); - if( !(peek(',') || peek(';') || match("->")) || id==null ) + if( !(peek(',') || peek(';') || peek('=') || match("->")) || id==null ) return posT(old1); // Reset lexer to reparse pos(old2); // Reset lexer to reparse // Yes a forward ref, so declare it - TYPES.put(tname,((TypeMemPtr)t1).withAccess(false)); + forwardRefType(tname,(TypeStruct)((TypeMemPtr)t1)._obj); // Return the (array, final) type return t2; } - // Make an array type of t - private TypeMemPtr typeAry( Type t, boolean efinal ) { - if( t instanceof TypeMemPtr tmp && tmp.notNull() ) - throw error("Arrays of reference types must always be nullable"); - String tname = "[]"+t.str(); - TypeMemPtr ta = (TypeMemPtr)TYPES.get(tname); - if( ta==null ) - // Remember final version - TYPES.put(tname, ta = TypeMemPtr.make( TypeStruct.makeAry(tname,TypeInteger.U32,_code.getALIAS(),t,_code.getALIAS(),true) )); - // Reuse aliases, but preserve this occurrence's element permissions. - TypeStruct ary = ta._obj; - return TypeMemPtr.make(TypeStruct.makeAry(tname,TypeInteger.U32,ary._fields[0]._alias,t,ary._fields[1]._alias,efinal)); + private TypeMemPtr forwardRefType( String tname, TypeStruct tself ) { + TYPES.put(tname,tself); + String clzName = addClzPrefix( tname ); + TYPES.put(clzName, classStruct(tname,structInitSig(false,tself),true)); + return TypeMemPtr.make(tself); + } + + private TypeMemPtr forwardRefType( String tname ) { + tname = fullTypeName(tname); + return forwardRefType(tname,TypeStruct.forward(tname)); + } + + private String fullTypeName( String tname ) { + String nest = _nestedType, fullq; + while( true ) { + fullq = nest+"."+tname; + if( TYPES.get(fullq) != null ) + return fullq.intern(); + int idx = nest.lastIndexOf('.'); + if( idx== -1 ) return fullq.intern(); + nest = nest.substring(0,idx); + } + } + + // Make an array type of t. Always record a mutable version, + // but return the requested version. + private TypeStruct typeAry( Type t, boolean efinal ) { + // Non-null reference arrays are valid types, even though an ordinary + // zero-filled `new T[len]` cannot construct one. Allocation retains + // that check in alloc(); trusted runtimes (notably argv construction) + // may fill the private array completely before publishing it. + String tname = ("[]"+t.str()).intern(); + TypeStruct ta = (TypeStruct)TYPES.get(tname); + if( ta==null ) { + // Primitive arrays use the null compunit + String rcname = TYPES.get(t) instanceof TypeStruct ? _ref._cname : ("_."+t).intern(); + ta = TypeStruct.makeAry(tname,TypeInteger.U32,_code.alias(rcname),t,_code.alias(rcname), false ); + TYPES.put(tname,ta); + } + // Already have the aliases, just efinal is wrong + return TypeStruct.makeAry(tname,TypeInteger.U32,ta._fields[0]._alias,t,ta._fields[1]._alias,efinal ); + } + + private Type implicitSelfType() { + TypeStruct self = _scope.constructorSelf(); + // Ordinary instance methods accept the declared object shape. The + // constructor-only receiver starts at the low/default field values, + // and is built explicitly by constructorFunc(). + return self == null ? TypePtr.PTR : TypeMemPtr.make(self); } // A function type is `{ type... -> type }` or `{ type }`. @@ -1227,16 +1742,19 @@ public class Parser { match("{"); // Skip already-peeked '{' Type t0 = type(); // Either return or first arg if( t0==null ) return posT(old); // Not a function - if( match("}") ) // No-arg function { -> type } - return TypeFunPtr.make(match("?"),false,TypeFunPtr.TEMPTY,t0); Ary<Type> ts = new Ary<>(Type.class); + // Always reserve a receiver slot. Outside constructor scopes it is + // broad and unused, so call sites can uniformly pass their self. + ts.push(implicitSelfType()); + if( match("}") ) // No-arg function { -> type } + return TypeFunPtr.make(match("?"),ts.asAry(),t0); ts.push(t0); // First argument while( true ) { if( match("->") ) { // End of arguments, parse return Type ret = type(); if( ret==null || !match("}") ) return posT(old); // Not a function - return TypeFunPtr.make(match("?"),false,ts.asAry(),ret); + return TypeFunPtr.make(match("?"),ts.asAry(),ret); } Type t1 = type(); if( t1==null ) return posT(old); // Not a function @@ -1300,7 +1818,6 @@ public class Parser { } - /** * Parse an eq/ne expression */ @@ -1475,15 +1992,17 @@ public class Parser { // Pre-dec/pre-inc int old = pos(); if( match("--") || match("++") ) { - int delta = _lexer.peek(-1)=='+' ? 1 : -1; // Pre vs post + int delta = _lexer.peek(-1)=='+' ? 1 : -1; // Inc vs Dec String name = _lexer.matchId(); if( name!=null ) { Var n = _scope.lookup(name); if( n != null && !(n.type() instanceof TypeMemPtr) ) { + if( n._extern!=0 ) + return parsePostfixName(_scope.in(n),name,delta); 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); + checkSharedFunction(n); + Node expr = convertExpr(peep(new AddNode(_scope.in(n),con(delta))),n.type()); _scope.update(n,expr); return expr; } @@ -1503,151 +2022,125 @@ public class Parser { * <pre> * primaryExpr : integerLiteral | "string" | 'char' | true | false | null | * new Type | '(' expression ')' | Id['++','--'] | - * [static.]* Id + * type['.' Id]* * </pre> * @return a primary {@link Node}, never {@code null} */ private Node parsePrimary() { if( _lexer.isNumber(_lexer.peek()) ) return parseLiteral(); - if( _lexer.peek('"') ) return newString(parseString()); + if( _lexer.peek('"') ) return parsePostfix(newString(parseString())); if( matchx("true" ) ) return con(1); - if( matchx("false") ) return ZERO; - if( matchx("null" ) ) return NIL; + if( matchx("false") ) return _code.ZERO; + if( matchx("null" ) ) return con(Type.NIL); if( match ("'" ) ) return parseChar(); if( match ("(" ) ) return parsePostfix(require(parseAsgn(), ")")); if( matchx("new" ) ) return parsePostfix(alloc()); if( match ("{" ) ) return parsePostfix(require(func(),"}")); - // Parse static variable lookup: `type.fld` in the type namespace - // CNC: not a full type parse, just a typename parse int pos = pos(); - Type t = type(); - if( t!=null ) { - if( peek('.') ) - // A namespace lookup is not a reference declaration. - return parsePostfix(con(TYPES.get(((TypeMemPtr)t)._obj._name))); - //pos(pos); // TODO: not a `type.fld` - //return null; - throw Utils.TODO(); - } - // Expect an identifier now - - // - if null, make fref, normal var handling - // - if final constant, normal var handling - // - if method var, scan skipping blocks & methods, must hit correct struct no fcns - // - if normal var, scan skipping blocks, allowing last fcn - // - NO fcns nested in methods, NO methods nested in fcns - String id = _lexer.matchId(); if( id == null || KEYWORDS.contains(id) ) throw errorSyntax("an identifier or expression"); + int pos2 = pos(); + // Attempt a local var lookup first Var var = _scope.lookup(id); - if( var==null ) { - // If missing, assume a forward reference - _scope.define(id, FRefNode.FREF_TYPE, true, XCTRL, loc()); - var = _scope.lookup(id); - } + // Not a local var, try again as a type name + pos(pos); + Type t = type(); + if( t!=null ) { + // Static <clinit> typename lookup + if( peek('.') ) // type.fld, which is really a field lookup in the class object + return parsePostfix(con(t)); + // Direct reference to a <clinit> type. Convert the instance + // type to a class type name + if( !(t instanceof TypeMemPtr tmp) ) { + //return new FRefNode(addClzPrefix(tmp._obj._name), loc()).peephole(); + // Not a local var, but yes a bare type... disallow a local + // FREF and return null, not-a-primary parse. + pos(pos); + return null; + } + } - // If a method var, allow nested blocks and methods, but not fcns or other structs. - // Else safety check for accessing fields out of function scope. - if( instance(var) ) { - Var selfVar = _scope.lookup("self"); - Node self = liftExpr(_scope.in(selfVar),selfVar.type(),true); - return parsePostfixName(self,id); + // Insert FREF outside enclosing Kind.Func scope. + pos(pos2); + t = FRefNode.FREF_TYPE; + // Check if this is a class. + CompUnit cu = ParseAll.findCompUnit(_code,_ref,id); + if( cu!=null ) { + // Must be a final class-type + String clzName = addClzPrefix(cu._cname); + TypeStruct clz = (TypeStruct)TYPES.get(clzName); + if( clz == null ) + TYPES.put(clzName,clz = TypeStruct.make(clzName,true)); + t = TypeMemPtr.make((byte)2,clz,true); + } + // Define the FRef + var = _scope.defineFRef(id,t,true,loc()); + _frefs.push((FRefNode)_scope.in(var)); } // Load local value - Node oldValue = _scope.in(var); - if( oldValue instanceof ConstantNode && !(oldValue instanceof FRefNode) && oldValue._type==Type.BOTTOM ) - throw error("Cannot read uninitialized field '"+id+"'"); - Node rvalue = liftExpr(oldValue,var.type(),true); - if( rvalue!=oldValue ) _scope.update(var,rvalue); - if( rvalue instanceof ConstantNode && rvalue._type == Type.BOTTOM ) - if( rvalue instanceof FRefNode ) - return parsePostfix(rvalue); - else throw error("Cannot read uninitialized field '"+id+"'"); + Node rvalue = _scope.in(var); + if( var._extern!=0 ) + return parsePostfixName(rvalue,id,0); + if( var._fref ) + return parsePostfix(rvalue); + // A constructor may read the unfinished object, but each field it + // reads must already be initialized on every path. Read the current + // local value; other fields may still be waiting for their first write. + if( inExplicitConstructor() && var._uninit && missingInit(rvalue,new BitSet()) ) + throw error("Field '"+id+"' might not be initialized",var._loc); + + // Check for an instance field load + int kx = _scope.kindx(var); // Declaration scope + int fx = _scope.enclosingFunction(); // Enclosing function scope + Kind kk = _scope._kinds.at(kx); + Kind fk = _scope._kinds.at(fx); + // Access instance field from 'self' + if( kx+1 == fx && kk instanceof Kind.Func inst && FunNode.isInstance(inst._name) && fk instanceof Kind.Func method && !inExplicitConstructor() ) + return parsePostfixName(_scope.in(method._lexSize),id,0); + + // Check for a function-escaping variable; these require true + // closures. Final constants are OK; final vars require a hidden var + // argument; not-final fields can just use an explicit struct arg. + if( kx < fx && !inExplicitConstructor() && // Out of scope + !(var._final && rvalue._type.isConstant()) ) { // Not a final constant + if( kx > 0 ) // Global scope is OK, only ever one of these (not one per function invoke) + throw error("Variable '"+var._name+"' is out of function scope and must be a final constant"); + + // var.idx==2 is self argument, 3 is args, 4 or more are now static globals + if( var._idx >= 4 ) { + // Read from global scope. + // Surely a nicer way to get the top-level scope compile name? + Var clzptr = _scope.var( 2 ); + // Get the most recent sharpen global + TypeStruct clz = (TypeStruct) TYPES.get( ((TypeMemPtr) clzptr.type())._obj._name ); + TypeMemPtr clztmp = TypeMemPtr.make( (byte) 2, clz, true ); + return parsePostfixName( con( clztmp ), id,0 ); + //return parsePostfixName(_scope.in(2),id); + } + } // Check for assign-update, x += e0; char ch = _lexer.matchOperAssign(); - if( ch==0 ) // Normal primary, check for postfix updates - return parsePostfix(rvalue); + if( ch==0 ) { // Normal primary, check for postfix updates + int selfx = _scope.kindFcn(var)._lexSize; + // Might be a method call from inside a method, so no explicit "self". + // Pass the in-scope "self" + return parsePostfixMethod(rvalue,_scope.in(_scope.update(_scope.var(selfx),null))); + } // Assign-update direct into Scope if( var._final && !_scope.canInit(var) ) throw error("Cannot reassign final '"+var._name+"'"); - checkSharedField(var); - checkCaptureWrite(var); + checkSharedFunction(var); Node op = opAssign(ch,rvalue, var.type() ); _scope.update(var,op); return postfix(ch) ? rvalue.unkeep() : op; } - // true if instance variable lookup (var is in any number of nested blocks, - // then an instance function then inside a struct declaration). - private boolean instance(Var var ) { - // 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; - if( xkind instanceof Kind.Func ) { - if( _scope._kinds.at(i-1)==kind ) - return true; // Instance variable: lookup in a method in a decl - } - throw Utils.TODO(); - } - } else { - // 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()) ) - throw error("Variable '"+var._name+"' is out of function scope and must be a final constant"); - } - 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. @@ -1670,17 +2163,17 @@ public class Parser { case '-' -> new SubNode(lhs,rhs); case '*' -> new MulNode(lhs,rhs); case '/' -> new DivNode(lhs,rhs); - case '&' -> new AndNode(loc(),lhs,rhs); - case '|' -> new OrNode(loc(),lhs,rhs); + case '|' -> new OrNode(null,lhs,rhs); + case '&' -> new AndNode(null,lhs,rhs); case '^' -> new XorNode(loc(),lhs,rhs); case Lexer.SHL -> new ShlNode(loc(),lhs,rhs); case Lexer.SAR -> new SarNode(loc(),lhs,rhs); case Lexer.SHR -> new ShrNode(loc(),lhs,rhs); - default -> throw Utils.TODO(); + default -> throw TODO(); }; // Convert to float ops, or narrow int types; error if not declared type. // Also, if postfix LHS is still keep() - return liftExpr(peep(op),t,true); + return convertExpr(peep(op),t); } @@ -1689,161 +2182,119 @@ public class Parser { */ private Node alloc() { Type t = type(); - if( t==null ) throw error("Expected a type"); + if( t==null ) { + String tname = _lexer.matchId(); + if( tname==null || KEYWORDS.contains(tname) ) + throw error("Expected a type"); + t = forwardRefType(tname); + } // Parse ary[ length_expr ] if( match("[") ) { if( !t.makeZero().isa(t) ) - throw error("Cannot allocate a non-nullable, since arrays always zero/null fill"); - Node len = parseAsgn(); - if( !(len._type instanceof TypeInteger) ) - throw error("Cannot allocate an array with length "+len._type); + throw error("Cannot allocate a non-nullable, since arrays are always zero/null filled"); + Node len = parseAsgn().keep(); require("]"); - TypeMemPtr tmp = typeAry(t,false); - return newArray(tmp._obj,len); - } - - if( !(t instanceof TypeMemPtr tmp) ) - throw error("Cannot allocate a "+t.str()); - - 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(); + return allocArray(typeAry(t,false),len); + } else { + if( !(t instanceof TypeMemPtr tmp) ) + throw error("Cannot allocate a "+t.str()); + return allocStruct(tmp._obj); + } + } + + private Node allocStruct(TypeStruct ts) { + Ary<Node> ctorArgs = constructorArgs(); + // A field declaration can leave a short forward type name in TYPES. + // Allocation happens in lexical context, so resolve that short name + // to the completed nested type before selecting its class helper. + String typeName = ts._name.indexOf('.') == -1 ? fullTypeName(ts._name) : ts._name; + Type resolved = TYPES.get(typeName); + if( resolved instanceof TypeStruct rts ) ts = rts; + TypeStruct clz = (TypeStruct)TYPES.get(addClzPrefix(typeName)); + if( clz==null ) + throw error("Unknown struct type '" + ts._name + "'"); + Field afld = clz.field("<new>"); + Node alloc; + if( afld != null ) { + TypeFunPtr tfp = (TypeFunPtr)afld._t; + FunNode fun = _code.link(tfp); + alloc = fun==null + ? new FRefNode((addClzPrefix(typeName)+".<new>").intern(),loc()) + : new FunPtrNode(tfp,_code._start,fun.ret()).peephole(); + } else { + if( !ts._fref ) + throw error("Constructor arguments for '" + ts._name + "' but no constructor is defined"); + Type[] sig = new Type[ctorArgs._len]; + for( int i=0; i<ctorArgs._len; i++ ) + sig[i] = ctorArgs.at(i)._type; + alloc = new FRefNode((addClzPrefix(typeName)+".<new>").intern(),loc(), + TypeFunPtr.make(false,sig,TypeMemPtr.make(ts))); + } + Ary<Node> args = new Ary<>(Node.class); + args.add(ctrl()); args.add(mem()); + for( Node arg : ctorArgs ) args.add(arg.unkeep()); + args.add(alloc); + return functionCall(args); + } + + private Ary<Node> constructorArgs() { + Ary<Node> args = new Ary<>(Node.class); + if( !match("(") ) + return args; + while( !peek(')') ) { + Node arg = parseAsgn(); + if( arg==null ) break; + args.push(arg.keep()); + if( !match(",") ) break; } - _scope.pop(); - mem(publish(shape,ctrl(),mem(),priv.unkeep(),ptr)); - return ptr.unkeep(); - } - - // The old constructor model requires each field on every path. An - // unresolved expression also has BOTTOM now, so inspect the actual - // sentinel and merges instead of mistaking an unknown type for no write. - private boolean missingInit(Node n, java.util.BitSet visit) { - if( n==null || visit.get(n._nid) || n instanceof FRefNode ) return false; - visit.set(n._nid); - if( n instanceof ConstantNode ) return n._type==Type.BOTTOM || n._type==Type.TOP; - if( n instanceof ConvertNode || n instanceof ReadOnlyNode ) return missingInit(n.in(1),visit); - if( n instanceof PhiNode phi ) - for( int i=1; i<n.nIns(); i++ ) - if( phi.region().in(i)!=null && phi.region().in(i)._type!=Type.XCONTROL && missingInit(n.in(i),visit) ) return true; - return false; + require(")"); + return args; } + private Node allocArray(TypeStruct ts, Node len) { + Field lenFld = ts.field("#" ); + Field bodyFld = ts.field("[]"); + + ConFldOffNode base = off(ts, "[]"); + int scale = ts.aryScale(); + Node size = peep(new AddNode(base,peep(new ShlNode(null,len,con(scale))))); + + // Build a NewNode; takes in and puts out all aliases. + NewNode nnn = new NewNode(ts, ctrl(), size ).init(); + ProjNode self = new ProjNode(nnn,0,ts.str()).init().keep(); + ProjNode smem = new ProjNode(nnn,1,"#selfMem").init().keep(); + + // Store length. Rest of array is zero'd via CALLOC during CodeGen. + // Length is casted to sanity. + // TODO: Needs runtime check. + Node st = peep(new StoreNode(null, "#", 1, TypeInteger.U32, null, smem, self, off(ts,"#"), len.unkeep(), true)); + + // TODO: Allow add-on "constructor" to init the array elements - private Node newArray(TypeStruct ary, Node len) { - 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(); + Node prior = mem(); + Node esc = peep(new EscapeNode(lenFld,self,st,prior)); + mem(mergeAlias(prior,lenFld._alias,esc)); + prior = mem(); + esc = peep(new EscapeNode(bodyFld,self,smem,prior)); + mem(mergeAlias(prior,bodyFld._alias,esc)); + smem.unkeep(); + return self.unkeep(); } + private Node newString(String s) { - TypeMemPtr tmp = typeAry(TypeInteger.U8,true); - int lenAlias = tmp._obj.field("#" )._alias; - int elemAlias = tmp._obj.field("[]")._alias; - TypeConAryB con = TypeConAryB.make(s); - Type elem = con.elem(); + TypeStruct ts = typeAry(TypeInteger.U8,true); + int lenAlias = ts.field("#" )._alias; + int elemAlias = ts.field("[]")._alias; + TypeConAryB body = TypeConAryB.make(s); + TypeInteger slen = TypeInteger.constant(s.length()); // Make a TMP, not-null (byte)2, singleton (true) (requires text // strings are hash-interned), with a TypeStruct having a constant // array body. - TypeMemPtr str = TypeMemPtr.make((byte)2,TypeStruct.makeAry("[]u8", TypeInteger.constant(s.length()), lenAlias, con, elemAlias, true),true); + TypeMemPtr str = TypeMemPtr.make((byte)2,TypeStruct.makeAry("[]u8", slen, lenAlias, body, elemAlias, true),true); assert str.isConstant(); return con(str.makeRO()); } - // Memory is one hidden SSA variable, including across branches and loops. - private Node mem() { return _scope.mem(); } - private void mem(Node n) { _scope.mem(n); } - - 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); - st = st.peephole(); - mem(new MemMergeNode(prior,alias,st).peephole()); - prior.unkill(); - } - /** * Parse postfix expression; this can be a field expression, an array * lookup or a postfix operator like '#' @@ -1856,15 +2307,87 @@ public class Parser { * expr '(' [args,]* ')' // Function call * </pre> */ + private Node parsePostfixMethod(Node expr, Node self) { + // TODO: This is too broad because the parser lacks strong types here. + // Some normal functions can end up with a "self" argument. + + // Self method call? Self is a TMP-not-CLZ, then assume and pass self. + if( match("(") ) { + expr = parsePostfix(require(functionCall(expr,self),")")); + } else if( expr != self ) + self.isKill(); // Not a method call, no need for self + return parsePostfix(expr); + } private Node parsePostfix(Node expr) { + _lexer.skipWhiteSpace(); + Lexer site = loc(); String name; if( match(".") ) name = requireId(); else if( match("#") ) name = "#"; else if( match("[") ) name = "[]"; - else if( match("(") ) return parsePostfix(require(functionCall(expr,null),")")); + // Can get here without an explicit receiver: "f()" or + // "self.method()(next)(next)(next)". Pass the current function self. + else if( match("(") ) return parsePostfix(require(functionCall(expr,defaultSelf()),")")); else return expr; // No postfix - return parsePostfixName(expr,name); + return parsePostfixName(expr,name,0,site); + } + + private boolean privateSelf(Node ptr) { + while( ptr instanceof ConvertNode || ptr instanceof ReadOnlyNode || ptr instanceof GuardNode ) ptr=ptr.in(1); + Node self=defaultSelf(); + while( self instanceof ConvertNode || self instanceof ReadOnlyNode || self instanceof GuardNode ) self=self.in(1); + return ptr==self; + } + + // An explicit self.x read sees the same current value as bare x. Loading + // the allocation here would miss updates still held in local SSA values. + private Node privateField(String name, int pre) { + int idx=fieldVarIdxBefore(_scope._kinds.at(_scope.enclosingFuncOrDecl())._lexSize,name); + if( idx<0 ) throw error("Cannot resolve field '"+name+"'"); + Var v=_scope.var(idx); + if( pre==0 && matchOpx('=','=') ) { + checkSharedFunction(v); + Node value=parseAsgn().keep(); + _scope.update(v,convertExpr(value,v.type())); + return value.unkeep(); + } + Node value=_scope.in(_scope.update(v,null)); + if( v._uninit && missingInit(value,new BitSet()) ) + throw error("Field '"+name+"' might not be initialized",v._loc); + char ch=pre==0 ? _lexer.matchOperAssign() : (char)pre; + if( ch!=0 ) { + checkSharedFunction(v); + Node op=opAssign(ch,value,v.type()); + _scope.update(v,op); + if( pre!=0 && postfix(ch) ) value.unkeep(); + return pre==0 && postfix(ch) ? value.unkeep() : op; + } + return parsePostfixMethod(value,defaultSelf()); + } + + private void checkSharedFunction(Var v) { + if( _scope.canInit(v) && v._final && v.type() instanceof TypeFunPtr t && t.isConstant() ) + throw error("Cannot reassign shared function '"+v._name+"'"); + } + + // A loop Phi is temporarily bottom before its backedge is parsed. That is + // not an unwritten field: follow its live inputs to the actual defaults. + private boolean missingInit(Node value, BitSet seen) { + if( value==null || seen.get(value._nid) ) return false; + seen.set(value._nid); + if( value instanceof PhiNode phi && !(value instanceof ParmNode) ) { + for( int i=1; i<phi.nIns(); i++ ) + if( phi.region().in(i)!=null && phi.region().in(i)._type!=Type.XCONTROL && missingInit(phi.in(i),seen) ) + return true; + return false; + } + return !initialized(value._type); + } + + private Node defaultSelf() { + int selfx = _scope._kinds.at(_scope.enclosingFuncOrDecl())._lexSize; + return _scope.in(_scope.update(_scope.var(selfx),null)); } /** @@ -1877,184 +2400,127 @@ public class Parser { * expr ('[' expr ']')* [ [op]= expr ] // Array reference * </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'"); - - // Sanity check expr for being a reference - if( !(expr._type instanceof TypeMemPtr ptr) ) - return parseUnknownField(expr,name); - - // Find the field from the Type. Lookup in the base object field names. - TypeMemPtr ptr2 = (TypeMemPtr)TYPES.get(ptr._obj._name); - if( !ptr._obj.isAry() ) - ptr = ptr.makeFrom(ptr2._obj); // The declaration fixes the field shape; retain this view's access. - TypeStruct base = ptr._obj; - int fidx = base.find(name); - if( fidx==-1 && base._open ) return parseUnknownField(expr,name); - if( fidx == -1 ) - throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'"); - - // 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 ) - tf = con.elem(); + // pre is +/-1 for a prefix update, otherwise parse a postfix operation. + private Node parsePostfixName(Node expr, String name, int pre) { + return parsePostfixName(expr,name,pre,loc()); + } + + private Node parsePostfixName(Node expr, String name, int pre, Lexer site) { + if( inExplicitConstructor() && privateSelf(expr) ) return privateField(name,pre); + // Keep expr across possible updates expr.keep(); + + // Get the best known TypeStruct from a pointer type. + TypeStruct ts = null, ts2; + if( expr._type instanceof TypeMemPtr tmp && (ts2=((TypeStruct)TYPES.get(tmp._obj._name))) != null ) + ts = (TypeStruct)tmp._obj.join(ts2); + // A Load's flow type is intentionally not promised during parsing, + // but its declaring field type is still known. Recover that static + // type for chained accesses such as `_grow(1).buf[len++]`; otherwise + // the array Store is parsed as alias #1 and can hide the independent + // side effect of the postfix `len++`. + if( ts==null && expr instanceof LoadNode load ) { + TypeStruct ots = load.off() instanceof ConFldOffNode coff ? coff._ts : null; + if( ots==null && load.ptr()._type instanceof TypeMemPtr owner ) { + Type owner0 = TYPES.get(owner._obj._name); + ots = owner0 instanceof TypeStruct obj ? obj : owner._obj; + } + if( ots != null ) { + Field loaded = ots.field(load._name); + if( loaded != null && loaded._t instanceof TypeMemPtr ltmp ) { + Type loaded0 = TYPES.get(ltmp._obj._name); + ts = loaded0 instanceof TypeStruct obj ? obj : ltmp._obj; + } + } + } + + Field fld = ts==null ? null : ts.field(name); + if( fld==null && ts!=null && !startsClzPrefix(ts._name) ) { + Type clz0 = TYPES.get(addClzPrefix(ts._name)); + if( clz0 instanceof TypeStruct clz && (fld = latestStruct(clz).field(name)) != null ) { + expr.unkeep(); + ts = latestStruct(clz); + expr = con(TypeMemPtr.make((byte)2,ts,true)).keep(); + } + } + if( fld==null && ts!=null ) + fld = fieldOrOpen(ts,name,false); + if( fld==null && ts!=null && !latestStruct(ts)._open ) + throw error("Accessing unknown field '"+name+"' from '*"+ts._name+"'"); + + // We have to record "active touches" of a new class, to track when we + // need to force class initialization. Basically, we insert an CLInit, + // and if it does not fold away it'll turn into a runtime check. + Kind.Func func = (Kind.Func)_scope._kinds.at(_scope.enclosingFuncOrDecl()); + if( (ts==null || ts==TypeStruct.BOT || ts.isHigh() || startsClzPrefix(ts._name)) && + !(ts!=null && (ts._name+".<clinit>").equals(func._name)) ) + classInit(site,expr); + + // With no field, we will update the bulk memory + int alias = fld==null ? 1 : fld._alias; + Type decl = fld==null ? Type.BOTTOM : fld._t; + // Field offset; fixed for structs, computed for arrays - Node off = (name.equals("[]") // If field is an array body - // Array index math - ? peep(new AddNode(off(base,"[]"),peep(new ShlNode(null,require(parseAsgn(),"]"),con(base.aryScale()))))) - // Struct field offsets are hardwired - : off(base,name)).keep(); + Node off = (name=="[]" + // Array element math, with unknown base offset and shift amount. + ? peep(new AddNode(fldoff(expr,"[]"),peep(new ShlNode(null,require(parseAsgn(),"]"),fldoff(expr,"<<"))))) + // Named field offset on an unknown struct type + : fldoff(expr,name)).keep(); // Disambiguate "obj.fld==x" boolean test from "obj.fld=x" field assignment - if( matchOpx('=','=') ) { + if( pre==0 && matchOpx('=','=') ) { + // Field assignment Node val = parseAsgn().keep(); - Node lift = liftExpr( val, tf, false ); + // Lift value for store + Node lift = fld==null ? val : new ConvertNode(fld._t,val).peephole(); + // Memory for store, post assignment expression + Node mem = mem(); + // Store to field + mem.keep(); + if( expr._type instanceof TypeMemPtr view && view._ro && ctrl()._type!=Type.XCONTROL ) + throw error("Cannot modify final field '"+name+"'"); + Node st = new StoreNode(loc(), name, alias, decl, ctrl(), mem, expr.unkeep(), off.unkeep(), lift, false).peephole().keep(); + storeMem(st,alias,mem); + st.unkeep(); + mem.unkeep(); - store(name,f._alias,tf,expr.unkeep(),off.unkeep(),lift,false,base.isAry() ? ctrl() : null); return val.unkeep(); // "obj.a = expr" returns the expression while updating memory } + // Memory for load + Node mem = mem(); + // Loading from a constant array, the declared type is the + // meet-over-elements and not the array itself. + if( name=="[]" && decl instanceof TypeConAry conary ) + decl = conary.elem(); + // Load field - Node load = f._one && INITS.containsKey( base._name ) - ? INITS.get(base._name).in(base.find(name)) - : new LoadNode(loc(),name, f._alias, mem(), expr, off); - // Arrays include control, as a proxy for a safety range check - // Structs don't need this; they only need a NPE check which is - // done via the type system. - if( base.isAry() && !name.equals("#") ) load.setDef(0,ctrl()); - load = peep(load); + Node load = peep(new LoadNode(loc(),name, alias, ctrl(), mem, expr, off)); // Check for assign-update, "ptr.fld += expr" or "ary[idx]++" - char ch = _lexer.matchOperAssign(); + char ch = pre==0 ? _lexer.matchOperAssign() : (char)pre; if( ch!=0 ) { - Node op = opAssign(ch,load,tf); - store(name,f._alias,tf,expr.unkeep(),off.unkeep(),op,false,base.isAry() ? ctrl() : null); - load = postfix(ch) ? load.unkeep() : op; - // And use the original loaded value as the result - return load; - //throw Utils.TODO(); // NO POSTFIX AFTER STORE, JUST RETURN - } else { - off.unkill(); - // Method call: Loaded a function from a named field, AND the base - // field type is final. - if( load._type instanceof TypeFunPtr && name!="[]" && - _code.externFunc((TypeFunPtr)load._type)==null && - ((TypeMemPtr)TYPES.get(base._name))._obj.field(name)._final && - // And calling a function - match("(") ) { - return parsePostfix(require(functionCall(load,expr),")")); - } - - // Drop expr, keeping load alive - load.keep(); - expr.unkill(); - load.unkeep(); - } - - return parsePostfix(load); - } - - - // Syntax fixes the access even when a forward definition or a loop - // leaves the receiver untyped. Resolve its alias and layout in the graph. - private Node parseUnknownField(Node expr, String name) { - expr.keep(); - Node off = new FldOffNode(expr,name).peephole(); - if( name.equals("[]") ) { - Node index = require(parseAsgn(),"]"); - Node scale = new FldOffNode(expr,"<<").peephole(); - off = peep(new AddNode(off,peep(new ShlNode(loc(),index,scale)))); - } - off.keep(); - if( matchOpx('=','=') ) { - Node value = parseAsgn().keep(); - unknownStore(name,expr,off,value); - expr.unkill(); - off.unkill(); - return value.unkeep(); - } - Node load = new LoadNode(loc(),name,1,mem(),expr,off); - load.setDef(0,ctrl()); - load = peep(load); - char ch = _lexer.matchOperAssign(); - if( ch!=0 ) { - Node op = opAssign(ch,load,Type.BOTTOM).keep(); - 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. + if( fld!=null && decl==Type.BOTTOM ) + throw error( "'" + ts._name + "' is not fully initialized, field '" + fld._fname + "' needs to be set in a constructor" ); + Node op = opAssign(ch,load, decl ).keep(); + mem.keep(); + if( expr._type instanceof TypeMemPtr view && view._ro && ctrl()._type!=Type.XCONTROL ) + throw error("Cannot modify final field '"+name+"'"); + Node st = new StoreNode(loc(), name, alias, decl, ctrl(), mem, expr.unkeep(), off.unkeep(), op, false).peephole().keep(); + storeMem(st,alias,mem); + st.unkeep(); + mem.unkeep(); + + op.unkeep(); // A narrow Store may have folded away the expression's conversion. if( postfix(ch) ) load.unkeep(); - else { - load = new LoadNode(loc(),name,1,mem(),expr,off); - load.setDef(0,ctrl()); - load = peep(load); - } - 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(); + return pre==0 && postfix(ch) ? load : op; } - 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); + off.unkill(); + // Might be a method call, so pass expr as 'self' + return parsePostfixMethod(load,expr.unkeep()); } - 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. @@ -2089,15 +2555,9 @@ public class Parser { Ary<Type> ts = new Ary<>(Type.class); Ary<String> ids = new Ary<>(String.class); java.util.BitSet finals = new java.util.BitSet(); - - // Defined in constructor? Add `self` argument. "static" calls still - // add a `self` they just ignore it. - if( _scope._kinds.last() instanceof Kind.Define define ) { - // Upgrade defined struct to latest fields - TypeMemPtr tmp = (TypeMemPtr)TYPES.get(define._tmp._obj._name); - ts .push(tmp); - ids.push("self"); - } + Type self = implicitSelfType(); + ts.push(self); + ids.push(self == TypePtr.PTR ? "#self" : "self"); // Parse other arguments _lexer.skipWhiteSpace(); @@ -2113,9 +2573,44 @@ public class Parser { } require("->"); // Make a concrete function type, with a fidx - TypeFunPtr tfp = _code.makeFun(TypeFunPtr.make(false,false,ts.asAry(),Type.BOTTOM)); + TypeFunPtr tfp = TypeFunPtr.make1((byte)2,true,ts.asAry(),Type.BOTTOM,_code.fidx(_ref._cname)); ReturnNode ret = parseFunctionBody(tfp,loc,finals,ids.asAry()); - return new FunPtrNode(ret._fun.sig(),ret).peephole(); + return new FunPtrNode(tfp,_code._start,ret).peephole(); + } + + private Node constructorFunc(String name) { + require("{"); + Ary<Type> ts = new Ary<>(Type.class); + Ary<String> ids = new Ary<>(String.class); + java.util.BitSet finals = new java.util.BitSet(); + TypeStruct self = _scope.constructorSelf(); + assert self != null; + ts.push(TypeMemPtr.make(constructorRecv(self))); + ids.push("self"); + // Explicit constructors use the same hidden allocation arguments as + // <init>: public memory is the call memory edge (index 1), followed by + // self (index 2) and the private, unescaped object memory (index 3). + ts.push(TypeMem.makePrivate(self)); + ids.push("#selfMem"); + + _lexer.skipWhiteSpace(); + Lexer loc = loc(); + while( true ) { + Type t = type(); + if( t==null ) break; + finals.set(ids.size(),bindingFinal(t)); + String id = requireId(); + ts .push(t ); + ids.push(id); + match(","); + } + require("->"); + TypeFunPtr tfp = TypeFunPtr.make1((byte)2,true,ts.asAry(),TypeMem.makePrivate(self),_code.fidx(_ref._cname)); + Node[] preParse = _scope.save(); + ReturnNode ret = _parseFunctionBody((self._name+"."+name).intern(),tfp,loc,finals,ids.asAry()); + _scope.restore(preParse); + require("}"); + return new FunPtrNode(tfp,_code._start,ret).peephole(); } /** @@ -2125,35 +2620,38 @@ 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) { + Lexer site = loc(); + site._position--; // Opening '('; arguments can contain nested calls. 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) ) - throw error("Expected a function but got "+fcn._type.glb(false).str()); fcn.keep(); // Keep while parsing args 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); + args.push(self.keep()); while( !peek(')') ) { Node arg = parseAsgn(); if( arg==null ) break; - checkPublication(arg); args.push(arg.keep()); if( !match(",") ) break; } // Control & memory after parsing args args.set(0,ctrl().keep()); - args.set(1,_scope.mem().keep()); + args.set(1,mem().keep()); args.push(fcn); // Function pointer // Unkeep them all for( Node arg : args ) arg.unkeep(); + return functionCall(args,site); + } + + private Node functionCall( Ary<Node> args ) { + return functionCall(args,loc()); + } + + private Node functionCall( Ary<Node> args, Lexer site ) { // Dead into the call? Skip all the node gen if( ctrl()._type == Type.XCONTROL ) { for( Node arg : args ) @@ -2163,33 +2661,18 @@ public class Parser { } // Into the call - CallNode call = new CallNode(loc(), args.asAry()); - call._method=method; - call=(CallNode)call.peephole(); + CallNode call = (CallNode)new CallNode(site, args.asAry()).peephole(); // Post-call setup - CallEndNode cend = (CallEndNode)new CallEndNode(call).peephole(); + CallEndNode cend = (CallEndNode)new CallEndNode(call,TypeRPC.constant(_code.rpc(_ref._cname))).peephole(); call.peephole(); // Rerun peeps after CallEnd, allows early inlining + cend = (CallEndNode)cend.keep().peephole(); // TODO: Might inline // Control from CallEnd ctrl(new CProjNode(cend,0,ScopeNode.CTRL).peephole()); - // A call may update any alias, so its memory result is the new whole state. - _scope.mem(new ProjNode(cend,1,ScopeNode.MEM0).peephole()); + // Memory from CallEnd + Node mem = new ProjNode(cend,1,ScopeNode.MEM0).peephole(); + mem(mem); // Call result - 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; + return new ProjNode(cend.unkeep(),2,"#2").peephole(); } // Just after parsing "type foo = " can parse `"C"` @@ -2201,10 +2684,27 @@ public class Parser { return false; } // External linked constant - private Node externDecl( String ex, Type t ) { - if( t instanceof TypeFunPtr tfp ) // Generic TFP from type parse - t = _code.makeFun(tfp); // Get a FIDX, becomes a constant - return new ExternNode(t,ex).peephole(); + private ConstantNode externDecl( String ex, Type t ) { + if( t instanceof TypeFunPtr tfp ) { // Generic TFP from type parse + int fidx = _code.fidx(_ref._cname); // Get a FIDX + tfp = tfp.makeFrom(fidx); // Become a constant + _code.externFunc(fidx,ex); // Map fidx to extern name + t = tfp; + } + return (ExternNode)(new ExternNode(t,ex).peephole()); + } + + // A lexical C binding has a singleton namespace with one external field. + // The namespace occupies no storage; its field offset supplies the address. + private Node externData(String name, Type t) { + if( !(t instanceof TypeInteger) && !(t instanceof TypeFloat) ) + throw error("C data binding requires an integer or float type"); + String cname = ("C:"+name).intern(); + TypeStruct ts = TypeStruct.make(cname,false,Field.make(name,t,_code.externAlias(name),false,true)); + Type old = TYPES.putIfAbsent(cname,ts); + if( old!=null && old!=ts ) + throw error("Conflicting types for C data '"+name+"'"); + return con(TypeMemPtr.make((byte)2,ts,true)); } /** @@ -2212,13 +2712,36 @@ public class Parser { * * <pre> * integerLiteral: [1-9][0-9]* | [0] - * floatLiteral: [digits].[digits]?[e [digits]]? + * floatLiteral: [digits].[digits]?[e [digits]]?[f]? * </pre> */ private ConstantNode parseLiteral() { return con(_lexer.parseNumber()); } - public static Node con( long con ) { return con==0 ? ZERO : con(TypeInteger.constant(con)); } - public static ConstantNode con( Type t ) { return (ConstantNode)new ConstantNode(t).peephole(); } - public static ConFldOffNode off( TypeStruct base, String fname ) { return (ConFldOffNode)(new ConFldOffNode(base._name,fname).peephole()); } + ConstantNode con( long con ) { return _code.con(con); } + ConstantNode con( Type t ) { return _code.con(t ); } + private void classInit(Lexer site, Node ptr) { + // Ordinary load machinery resolves the flag alias and offset when + // the receiver is still a forward reference. + Field flag = ptr._type instanceof TypeMemPtr tmp ? tmp._obj.field("$init") : null; + int alias = flag==null ? 1 : flag._alias; + Node ready = new LoadNode(site,"$init",alias,null,mem(),ptr,fldoff(ptr,"$init")).peephole(); + CLInitNode init = new CLInitNode(site,ctrl(),mem(),ptr,ready).peephole().keep(); + ctrl(new CProjNode(init,0,ScopeNode.CTRL).peephole()); + Node bulk = new ProjNode(init,1,ScopeNode.MEM0).peephole().keep(); + flag = init.flag(); + mem(flag==null ? bulk : mergeAlias(bulk,flag._alias, + new ProjNode(init,2,memName(flag._alias)).peephole())); + bulk.unkeep(); + init.unkeep(); + } + + // Field offset for a known type (but unknown offset) + public static ConFldOffNode off( TypeStruct ts, String fname ) { + return (ConFldOffNode)(new ConFldOffNode(ts,fname).peephole()); + } + // Field offset for an unknown type + public Node fldoff( Node n, String name ) { + return new FldOffNode(n,name,loc()).peephole(); + } public Node peep( Node n ) { // Peephole, then improve with lexically scoped guards return _scope.upcastGuard(n.peephole()); @@ -2268,6 +2791,14 @@ public class Parser { return require(con(TypeInteger.constant(c)),"'"); } + private boolean noLowerCase(String s) { + for( int i=0; i<s.length(); i++ ) + if( Character.isLowerCase(s.charAt(i)) ) + return false; + return true; + } + + ////////////////////////////////// // Utilities for lexical analysis @@ -2332,18 +2863,23 @@ public class Parser { // Input buffer; an array of text bytes read from a file or a string private final byte[] _input; + public final CompUnit _cu; // Original source, retained across inlining. // Tracks current position in input buffer private int _position = 0; - public boolean before(Lexer that) { - return that!=null && _input==that._input && _position<that._position; - } - // private int _line_number = 1; // Start of current line private int _line_start = 0; + /** True when this location precedes {@code that} in the same source. */ + public boolean before(Lexer that) { + return that != null && (_cu!=null ? _cu==that._cu : _input==that._input) && _position < that._position; + } + + public String position() { return _line_number+":"+(_position-_line_start+1); } + public int pos() { return _position; } + /** * Record the source text for lexing */ @@ -2356,13 +2892,28 @@ public class Parser { */ public Lexer(byte[] buf) { _input = buf; + _cu = null; } + private Lexer(CompUnit cu) { + _input = cu._src.getBytes(); + _cu = cu; + } + + // Imported location: source text is only needed when reporting an error. + private Lexer(CompUnit cu, int pos) { + _input = null; + _cu = cu; + _position = pos; + } + + /** * Copy a lexer from a lexer */ private Lexer(Lexer l) { _input = l._input; + _cu = l._cu; _position = l._position; _line_number = l._line_number; _line_start = l._line_start; @@ -2371,7 +2922,7 @@ public class Parser { // Very handy in the debugger, shows the unparsed program @Override public String toString() { - return new String(_input, _position, _input.length - _position); + return _input==null ? _cu._cname+"@"+_position : new String(_input, _position, _input.length - _position); } // True if at EOF @@ -2490,7 +3041,7 @@ public class Parser { // Used for errors String getAnyNextToken() { - if (isEOF()) return ""; + if (isEOF()) return "EOF"; if (isIdStart(peek())) return parseId(); if (isNumber(peek())) return parseNumberString(); if (isPunctuation(peek())) return parsePunctuation(); @@ -2504,17 +3055,26 @@ public class Parser { private Type parseNumber() { int old = _position; int len = isLongOrDouble(); + boolean f32 = peek()=='f'; + if( f32 ) nextChar(); + int n = Math.abs(len); + if( f32 ) + return TypeFloat.constant(Float.parseFloat(new String(_input,old,n))); if( len > 0 ) { if( len > 1 && _input[old]=='0' ) throw error("Syntax error: integer values cannot start with '0'",this); long i = Long.parseLong(new String(_input,old,len)); return TypeInteger.constant(i); } - return TypeFloat.constant(Double.parseDouble(new String(_input,old,-len))); + return TypeFloat.constant(Double.parseDouble(new String(_input,old,n))); } private String parseNumberString() { int old = _position; int len = Math.abs(isLongOrDouble()); + if( peek()=='f' ) { + nextChar(); + len++; + } return new String(_input,old,len); } @@ -2544,7 +3104,7 @@ public class Parser { private String parseId() { int start = _position; while (isIdLetter(nextChar())) ; - return new String(_input, start, --_position - start); + return new String(_input, start, --_position - start).intern(); } // @@ -2579,9 +3139,9 @@ public class Parser { } } - ParseException errorSyntax(String syntax) { return _errorSyntax("expected "+syntax); } + ParseException errorSyntax(String syntax) { return _errorSyntax("expected `"+syntax+"`"); } private ParseException _errorSyntax(String msg) { - return error("Syntax error, "+msg+": " + _lexer.getAnyNextToken()); + return error("Syntax error, "+msg+" but found `" + _lexer.getAnyNextToken()+"`"); } ParseException error(String msg) { return error(msg,_lexer); } public static ParseException error(String msg, Lexer loc) { return new ParseException(msg,loc); } |
