diff options
Diffstat (limited to 'src/main/java/com/seaofnodes/simple/Parser.java')
| -rw-r--r-- | src/main/java/com/seaofnodes/simple/Parser.java | 3197 |
1 files changed, 0 insertions, 3197 deletions
diff --git a/src/main/java/com/seaofnodes/simple/Parser.java b/src/main/java/com/seaofnodes/simple/Parser.java deleted file mode 100644 index 83bef882..00000000 --- a/src/main/java/com/seaofnodes/simple/Parser.java +++ /dev/null @@ -1,3197 +0,0 @@ -package com.seaofnodes.simple; - -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.BAOS; -import com.seaofnodes.simple.node.ScopeNode.Kind; - -import static com.seaofnodes.simple.util.Utils.TODO; - -import java.util.*; - -/** - * The Parser converts a Simple source program to the Sea of Nodes intermediate - * representation directly in one pass. There is no intermediate Abstract - * Syntax Tree structure. - * <p> - * This is a simple recursive descent parser. All lexical analysis is done here as well. - */ -public class Parser { - - // 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 - * we handle branching. See {@link #parseIf()}. - * <p> - * Each ScopeNode contains a stack of lexical scopes, each scope is a symbol table that binds - * variable names to Nodes. The top of this stack represents current scope. - * <p> - * We keep a list of all ScopeNodes so that we can show them in graphs. - * @see #parseIf() - * @see #_xScopes - */ - public ScopeNode _scope; - - /** - * List of keywords disallowed as identifiers - */ - public static final HashSet<String> KEYWORDS = new HashSet<>(){{ - add("bool"); - add("break"); - add("byte"); - add("continue"); - add("else"); - add("f32"); - add("f64"); - add("false"); - add("flt"); - add("i16"); - add("i32"); - add("i64"); - add("i8"); - add("if"); - add("int"); - add("new"); - add("null"); - add("return"); - add("struct"); - add("true"); - add("u1"); - add("u16"); - add("u32"); - add("u8"); - add("while"); - }}; - - /** - * We clone ScopeNodes when control flows branch; it is useful to have - * a list of all active ScopeNodes for purposes of visualization of the SoN graph - */ - public final Stack<ScopeNode> _xScopes = new Stack<>(); - - 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 - // Layouts needed by field accesses in this source file. Resolve after - // parsing, since a later local struct declaration can satisfy the use. - public final LinkedHashSet<String> _requiredTypes = new LinkedHashSet<>(); - 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 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); - put("f64" ,TypeFloat .F64); - put("flt" ,TypeFloat .F64); - put("i16" ,TypeInteger.I16); - put("i32" ,TypeInteger.I32); - put("i64" ,TypeInteger.BOT); - put("i8" ,TypeInteger.I8 ); - put("int" ,TypeInteger.BOT); - put("u1" ,TypeInteger.U1 ); - put("u16" ,TypeInteger.U16); - put("u32" ,TypeInteger.U32); - put("u8" ,TypeInteger.U8 ); - 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); } - // 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); - _requiredTypes.clear(); - // 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); - // Track active scopes for Graph display - _xScopes.push(_scope); - ctrl(_code.XCTRL); - mem(con(TypeMem.BOT)); - - // File-level struct declaration - _ref = ref; - _nestedType = ref._cname; - String typeName = addClzPrefix(ref._cname); - - ReturnNode clzret = parseStruct( true, typeName ); - - if( !_lexer.isEOF() ) throw _errorSyntax("unexpected"); - - // 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); - } - - // The class being published - ref._clz = (TypeStruct)TYPES.get(typeName); - - // Clean up and reset - _xScopes.pop(); - _scope.kill(); - _scope = null; - _frefs = null; - - return frefs; - } - - - /** - * Parses a function body, assuming the header is parsed. - */ - private ReturnNode parseFunctionBody( TypeFunPtr sig, Lexer loc, String[] ids) { - return parseFunctionBody(sig, loc, null, ids); - } - - 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( String funName, TypeFunPtr sig, Lexer loc, String[] ids) { - return _parseFunctionBody(funName, sig, loc, null, ids); - } - - 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 - _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; - - 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); - - 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.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); - // 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); - } - } - - // Each initializer establishes its parent before setting its own - // run-once flag. Keep this in the body so every execution strategy - // shares the same ordering, including direct entry into a child. - if( fun.isClz() ) { - if( _ref._par!=null ) { - String pname = addClzPrefix(_ref._par._cname); - TypeStruct parent = (TypeStruct)TYPES.get(pname); - if( parent==null ) parent=TypeStruct.make(pname,true); - Node ptr = con(TypeMemPtr.make((byte)2,parent,true)).keep(); - classInit(loc,ptr); - ptr.unkeep(); - } - 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()) { - // 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); - } - - // 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); - } - } - } - - 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; - } - - // 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; - } - - 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 - * - * <pre> - * '{' statements '}' - * </pre> - * Does not parse the opening or closing '{}' - * @return a {@link Node} or {@code null} - */ - private Node parseBlock(Kind kind) { - // Enter a new scope - _scope.push(kind); - Node last = _code.ZERO; - while (!peek('}') && !_lexer.isEOF()) { - _code.add(last); - last = parseStatement(); - } - // Exit scope - last.keep(); - _scope.pop(); - return last.unkeep(); - } - - /** - * Parses a statement - * - * <pre> - * returnStatement | declStatement | blockStatement | ifStatement | expressionStatement - * </pre> - * @return a {@link Node} or {@code null} - */ - private Node parseStatement() { - if( matchx("return") ) return parseReturn(); - else if (matchx("if") ) return parseIf(); - else if (matchx("while") ) return parseWhile(); - else if (matchx("for") ) return parseFor(); - else if (matchx("break") ) return parseBreak(); - else if (matchx("continue")) return parseContinue(); - else if (matchx("struct") ) return parseStruct(); - 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("{"); - return require(parseBlock(new Kind.Block()),"}"); - } - // Declaration or normal assignment/expression - else return parseDeclarationStatement(); - } - - 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() { - 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("="); - 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,";"); - } - - /** - * Parses a while statement - * - * <pre> - * while ( expression ) statement - * </pre> - * @return a {@link Node}, never {@code null} - */ - private Node parseWhile() { - require("("); - return parseLooping(false); - } - - - /** - * Parses a for statement - * - * <pre> - * for( var x=init; test; incr ) body - * </pre> - * @return a {@link Node}, never {@code null} - */ - private Node parseFor() { - // { var x=init,y=init,...; - // while( test ) { - // body; - // next; - // } - // } - require("("); - _scope.push(new Kind.Block()); // Scope for the index variables - if( !match(";") ) // Can be empty init "for(;test;next) body" - parseDeclarationStatement(); // Non-empty init - Node rez = parseLooping(true); - _scope.pop(); // Exit index variable scope - return rez; - } - - // Shared by `for` and `while` - private Node parseLooping( boolean doFor ) { - - var savedContinueScope = _continueScope; - var savedBreakScope = _breakScope; - - // Loop region has two control inputs, the first is the entry - // point, and second is back edge that is set after loop is parsed - // (see end_loop() call below). Note that the absence of back edge is - // used as an indicator to switch off peepholes of the region and - // associated phis; see {@code inProgress()}. - - 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). - // We create phis eagerly for all the names we find, see dup(). - - // Save the current scope as the loop head - ScopeNode head = _scope.keep(); - // Clone the head Scope to create a new Scope for the body. - // Create phis eagerly as part of cloning - _xScopes.push(_scope = _scope.dup(true)); // The true argument triggers creating phis - - // Parse predicate - var pred = peek(';') ? con(1) : parseAsgn(); - require( doFor ? ";" : ")" ); - - // IfNode takes current control and predicate - Node ifNode = new IfNode(ctrl(), pred.keep()).peephole(); - // Setup projection nodes - 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; - if( doFor ) { - // Skip the next expression and parse it later - nextPos = pos(); - skipAsgn(); - nextEnd = pos(); - require(")"); - } - - // Clone the body Scope to create the break/exit Scope which accounts for any - // side effects in the predicate. The break/exit Scope will be the final - // scope after the loop, and its control input is the False branch of - // the loop predicate. Note that body Scope is still our current scope. - ctrl(ifF); - _xScopes.push(_breakScope = _scope.dup()); - _breakScope.addGuards(ifF,pred,true); // Up-cast predicate - - // No continues yet - _continueScope = null; - - // Parse the true side, which corresponds to loop body - // Our current scope is the body Scope - ctrl(ifT.unkeep()); // set ctrl token to ifTrue projection - _scope.addGuards(ifT,pred.unkeep(),false); // Up-cast predicate - parseStatement().isKill(); // Parse loop body - _scope.removeGuards(ifT); - - // Merge the loop bottom into other continue statements - if (_continueScope != null) { - _continueScope = jumpTo(_continueScope); - _scope.kill(); - _scope = _continueScope; - } - - // Now append the next code onto the body code - if( doFor ) { - int old = pos(nextPos); - if( !peek(')') ) - parseAsgn(); - if( pos() != nextEnd ) - throw errorSyntax( "Unexpected code after expression" ); - pos(old); - } - - // The true branch loops back, so whatever is current _scope.ctrl gets - // added to head loop as input. endLoop() updates the head scope, and - // goes through all the phis that were created earlier. For each phi, - // 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(); - - _xScopes.pop(); // Cleanup - _xScopes.pop(); // Cleanup - - _continueScope = savedContinueScope; - _breakScope = savedBreakScope; - - // At exit the false control is the current control, and - // the scope is the exit scope after the exit test. - // During sys parsing, there is no xscope here. - if( !_xScopes.isEmpty() ) { - _xScopes.pop(); - _xScopes.push( exit ); - } - _scope = exit; - return _code.ZERO; - } - - private ScopeNode jumpTo(ScopeNode toScope) { - ScopeNode cur = _scope.dup(); - 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() ) - cur.pop(); - // If this is a continue then first time the target is null - // So we just use the pruned current scope as the base for the - // "continue" - if( toScope == null ) - return cur; - // toScope is either the break scope, or a scope that was created here - assert toScope.depth() <= _breakScope.depth(); - toScope.ctrl(toScope.mergeScopes(cur,loc()).peephole()); - return toScope; - } - - 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 _code.ZERO; } - private Node parseBreak () { - checkLoopActive(); - // At the time of the break, and loop-exit conditions are only valid if - // they are ALSO valid at the break. It is the intersection of - // conditions here, not the union. - _breakScope.removeGuards(_breakScope.ctrl()); - _breakScope = require(jumpTo(_breakScope ),";"); - _breakScope.addGuards((CFGNode)_breakScope.ctrl(), null, false); - return _code.ZERO; - } - - // Look for an unbalanced `)`, skipping balanced - private void skipAsgn() { - int paren=0; - while( true ) - // Next X char handles skipping complex comments - switch( _lexer.nextXChar() ) { - case Character.MAX_VALUE: - throw TODO(); - case ')': - if( --paren<0 ) { - posT( pos() - 1 ); - return; // Leave the `)` behind - } - break; - case '(': paren ++; break; - default: break; - } - } - - - /** - * Parses a statement - * - * <pre> - * if ( expression ) statement [else statement] - * </pre> - * @return a {@link Node}, never {@code null} - */ - private Node parseIf() { - // Parse predicate - require("("); - var pred = require(parseAsgn(), ")"); - return parseTrinary(pred,"else"); - } - - // Parse a conditional expression, merging results. - private Node parseTrinary( Node pred, String fside ) { - // IfNode takes current control and predicate - pred.keep(); - Node ifNode = new IfNode(ctrl(), pred).peephole(); - // Setup projection nodes - 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 - fScope.ctrl(ifF); // The pending arm already starts at False - _xScopes.push(fScope); // For graph visualization we need all scopes - - // Parse the true side - ctrl(ifT.unkeep()); // set ctrl token to ifTrue projection - _scope.addGuards(ifT,pred,false); // Up-cast predicate - // Parse true-side flavor - Node lhs = switch( fside ) { - case "else" -> parseStatement(); // if( pred ) stmts; - case ":" -> parseAsgn(); // pred ? asgn; - case "&&" -> parseLogical(); // pred && expr - case "||" -> pred; // pred || expr_is_ignored - default -> throw TODO(); - }; - lhs.keep(); - _scope.removeGuards(ifT); - - // See if a one-sided def was made: "if(pred) int x = 1;" and throw. - // See if any forward-refs were made, and copy them to the other side: - // "pred ? n*fact(n-1) : 1" - fScope.balanceIf(_scope); - - ScopeNode tScope = _scope; - - // Parse the false side - _scope = fScope; // Restore scope, then parse else block if any - ifF.unkeep(); // fScope already owns the false control - // Up-cast predicate, even if not else clause, because predicate can - // remain true if the true clause exits: `if( !ptr ) return 0; return ptr.fld;` - _scope.addGuards(ifF,pred,true); - // Parse false-side flavor - boolean doRHS = false; // RHS is optional for if/else and trinary - Node rhs = switch( fside ) { - case "else" -> (doRHS=match(fside)) ? parseStatement() : con(lhs._type.makeZero()); - case ":" -> (doRHS=match(fside)) ? parseAsgn() : con(lhs._type.makeZero()); - 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(); - - // Check the trinary widening int/flt - if( !fside.equals("else") ) { - rhs = widenInt( rhs.unkeep(), lhs._type ).keep(); - lhs = widenInt( lhs.unkeep(), rhs._type ).keep(); - } - - _scope = tScope; - _xScopes.pop(); // Discard pushed from graph display - // See if a one-sided def was made: "if(pred) int x = 1;" and throw. - // See if any forward-refs were made, and copy them to the other side: - // "pred ? n*fact(n-1) : 1" - tScope.balanceIf(fScope); - - // Merge results - RegionNode r = ctrl(tScope.mergeScopes(fScope,loc())); - 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; - ret.keep(); - r.peephole(); - return ret.unkeep(); - } - - /** - * Parses a return statement; "return" already parsed. - * The $ctrl edge is killed. - * - * <pre> - * 'return' expr ; - * </pre> - * @return an expression {@link Node}, never {@code null} - */ - private Node parseReturn() { - var expr = require(parseAsgn(), ";"); - 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(); - // Just a plain expression, no assignment. - // Distinguish `var==expr` from `var=expr` - if( name==null || KEYWORDS.contains(name) || !matchOpx('=','=') ) - { pos(old); return parseExpression(); } - - // Find variable to update - Var def = _scope.lookup(name); - if( def==null ) - def = defineOpenConstructorField(name); - if( def==null ) - throw error("Undefined name '" + 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 - // outside the constructor need to check the final bit. - if( _scope.in(def._idx)._type!=Type.TOP && def._final && - // Inside an allocation, final assign is OK, outside nope. - // The alloc() call added the allocation scope - !_scope.canInit(def) ) - throw error("Cannot reassign final '"+name+"'"); - checkSharedFunction(def); - - // Parse assignment expression - Node expr = parseAsgn(); - - // Lift expression, based on type - 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(); - } - - // Widen ints to floats; narrow wide int types. - // Early error if types do not match variable. - 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)); - // 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; - } - - /** - * Parse declaration or expression statement - * declStmt = type var['=' exprAsgn][, var['=' exprAsgn]]* ';' | exprAsgn ';' - * <p> - * exprAsgn = var '=' exprAsgn | expr - */ - private Node parseDeclarationStatement() { - int old = pos(); - Type t = type(); - if( peek('.') ) // Ambiguity static vars: "type.var", parse as expression - { pos(old); t=null; } - if( t == null ) - return require(parseAsgn(),";"); - - // now parse var['=' asgnexpr] in a loop - Node n = parseDeclaration(t); - while( match(",") ) - n = parseDeclaration(t); - return require(n,";"); - } - - // The binding modifier sits immediately before the name. It never - // changes permissions on the referenced object. - private boolean bindingFinal(Type t) { - if( match("!") ) return false; - if( match("~") ) return true; - return t==Type.TOP || t instanceof TypeNil; - } - - /** Parse final: [!|~]var['=' asgn] - */ - private Node parseDeclaration(Type t) { - assert t!=null; - // Has var/val instead of a user-declared type - boolean inferType = t==Type.TOP || t==Type.BOTTOM; - boolean xfinal = bindingFinal(t); - Lexer loc = loc(); - String name = requireId(); - // Optional initializing expression follows - boolean fld_final = false; // Field is final, but not deeply final - boolean uninit = false; // Required non-null constructor field - Node expr; - 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 = 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 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. - if( (inferType || (t instanceof TypeNil tn && !tn.nullable() )) && !_scope.inConstructor() ) - throw errorSyntax("=expression"); - // Initial value for uninitialized struct fields. - expr = switch( t ) { - // 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); } - }; - // 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 - // 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); - - // Define a new name - if( !_scope.define(name,t,xfinal || fld_final,lift, loc) ) - throw error("Redefining name '" + name + "'", loc); - _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 Node parseStruct( ) { - // keyword "struct" already parsed, so expect the struct name next. - String typeName = requireId(); - require("{"); - - // create type class:NESTED.typeName; no fields. - - String old = _nestedType; - 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("}"); - 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 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; - } - - // <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 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 TypeStruct latestStruct( TypeStruct ts ) { - Type t = TYPES.get(ts._name); - return t instanceof TypeStruct ts2 ? ts2 : ts; - } - - 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 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() { - // 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), t1 = t0; - // No new types as keywords - 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); - - // 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 ) { - if( readonly || !writable ) t1 = tmp.makeRO(); - } else if( writable || readonly ) - return posT(old1); - - // Nest arrays and '?' as needed - Type t2 = t1; - while( true ) { - if( match("?") ) { - 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"); - } else if( match("[~]") ) { - t2 = TypeMemPtr.make(typeAry(t2,true)).makeRO(); - } else if( match("[]") ) { - t2 = TypeMemPtr.make(typeAry(t2,false)); - } else - break; - } - - // Check no forward ref - if( t0 != null ) return t2; - // Check valid forward ref, after parsing all the type extra bits. - // Cannot check earlier, because cannot find required 'id' until after "[]?" syntax - int old2 = pos(); - if( !match("!") ) match("~"); - String id = _lexer.matchId(); - 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 - forwardRefType(tname,(TypeStruct)((TypeMemPtr)t1)._obj); - // Return the (array, final) type - return t2; - } - - private TypeMemPtr forwardRefType( String tname, TypeStruct tself ) { - TYPES.put(tname,tself); - String clzName = addClzPrefix( tname ); - // Only a struct declaration publishes an instance initializer. A - // forward type may name a file with just <clinit>, so do not invent - // a constructor field (or overwrite an already parsed class). - TYPES.putIfAbsent(clzName,TypeStruct.make(clzName,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 }`. - private Type typeFunPtr() { - int old = pos(); // Record lexer position - match("{"); // Skip already-peeked '{' - Type t0 = type(); // Either return or first arg - if( t0==null ) return posT(old); // Not a function - 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("?"),ts.asAry(),ret); - } - Type t1 = type(); - if( t1==null ) return posT(old); // Not a function - ts.push(t1); - } - } - - // True if a TypeFunPtr, without advancing parser - private boolean isTypeFun() { - int old = pos(); - if( typeFunPtr()==null ) return false; - pos(old); - return true; - } - - /** - * Parse an expression of the form: - * - * <pre> - * expr : bitwise [? expr [: expr]] - * </pre> - * @return an expression {@link Node}, never {@code null} - */ - private Node parseExpression() { - Node expr = parseLogical(); - return match("?") ? parseTrinary(expr,":") : expr; - } - - /** - * Parse an bitwise expression - * - * <pre> - * bitwise : compareExpr (('&' | '|' | '^') compareExpr)* - * </pre> - * @return a bitwise expression {@link Node}, never {@code null} - */ - - private Node parseLogical() { - Node lhs = parseBitwise(); - while (true) { - if( false ) ; - else if( match("&&") ) lhs = parseTrinary(lhs, "&&"); - else if( match("||") ) lhs = parseTrinary(lhs, "||"); - else break; - } - return lhs; - } - - private Node parseBitwise() { - Node lhs = parseEquality(); - while( true ) { - if( false ) ; - else if( matchOp('&') ) lhs = new AndNode(loc(),lhs,null); - else if( matchOp('|') ) lhs = new OrNode(loc(),lhs,null); - else if( match ("^") ) lhs = new XorNode(loc(),lhs,null); - else break; - lhs.setDef(2,parseEquality()); - lhs = peep(lhs); - } - return lhs; - } - - - /** - * Parse an eq/ne expression - */ - private Node parseEquality() { - Node lhs = parseComparison(); - while( true ) { - boolean eq = false; - if( match("==") ) eq = true; - else if( !match("!=") ) break; - lhs.keep(); - Node rhs = parseComparison(); - lhs = peep(new BoolNode.EQ(lhs.unkeep(),rhs)); - if( !eq ) lhs = peep(new NotNode(lhs)); - } - return lhs; - } - - /** - * Parse an expression of the form: - * - * <pre> - * expr : shiftExpr < shiftExpr <= shiftExpr... - * expr : shiftExpr > shiftExpr >= shiftExpr... - * </pre> - * @return an comparator expression {@link Node}, never {@code null} - */ - private Node parseComparison() { - Node lhs = parseShift(); - int dir = parseCompDir(); - // No compare - if( dir==0 ) return lhs; - lhs.keep(); - - // Compare - Node rhs = parseShift().keep(); - Node cmp = makeCompBool(dir,lhs.unkeep(),rhs).keep(); // Convert to a bool - - // Stacked compares? - int dir0 = parseCompDir(); - if( dir0 == 0 ) { - rhs.unkill(); - return cmp.unkeep(); - } - - // Keep the middle operand separately from the boolean result. - // The next test compares adjacent operands, not that boolean. - Node ifNode = new IfNode(ctrl(), cmp.unkeep()).peephole(); - Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); - Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole(); - // False side does nothing but capture memory & side-effects - ScopeNode fail = _scope.dup(); - fail.ctrl(ifF); - - // Loop over stacked compares. Each loop moves the old RHS to become - // the new LHS, so e.g. (a < b < c < d) compares (LHS:a < RHS:b) then - // compares (LHS:b < RHS:c) then (LHS:c < RHS:d), etc. - while( dir0 != 0 ) { - if( Math.abs(dir) != Math.abs(dir0) ) - throw error("Mixing relational directions in a chained relational test"); - // True side parses next arm of test - ctrl(ifT.unkeep()); - lhs = rhs; // Reuse the previous RHS exactly once - rhs = parseShift().keep(); - cmp = makeCompBool(dir0,lhs.unkeep(),rhs); // Convert to a bool - ifNode = new IfNode(ctrl(), cmp).peephole(); - ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); - ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole(); - // Merge result into the fail case - ctrl(ifF); - fail.mergeScopes(_scope.dup(), loc()).peephole(); - // Check next stacked compare - dir0 = parseCompDir(); - } - rhs.unkill(); - ctrl(ifT.unkeep()); - RegionNode r = fail.mergeScopes(_scope, loc()).init(); - _scope = fail; - return new PhiNode("", r,con(0),con(1)).peephole(); - } - - private int parseCompDir() { - if( match("<=") ) return -1; // Negative for "equals-or-less" - if( match(">=") ) return -2; // 1 for <, 2 for > - if( match("<" ) ) return 1; // Positive for just "less" - if( match(">" ) ) return 2; - return 0; - } - - private Node makeCompBool( int dir, Node lhs, Node rhs ) { - if( Math.abs(dir)==2 ) - { Node tmp=lhs; lhs=rhs; rhs=tmp; } - Node cmp = dir < 0 ? new BoolNode.LE(lhs,rhs) : new BoolNode.LT(lhs,rhs); - return peep(cmp); - } - - /** - * Parse an additive expression - * - * <pre> - * shiftExpr : additiveExpr (('<<' | '>>' | '>>>') additiveExpr)* - * </pre> - * @return a shift expression {@link Node}, never {@code null} - */ - private Node parseShift() { - Node lhs = parseAddition(); - while( true ) { - if( false ) ; - else if( match("<<") ) lhs = new ShlNode(loc(),lhs,null); - else if( match(">>>")) lhs = new ShrNode(loc(),lhs,null); - else if( match(">>") ) lhs = new SarNode(loc(),lhs,null); - else break; - lhs.setDef(2,parseAddition()); - ParseException err; - if( (err=lhs.err()) != null ) - throw err; - lhs = peep(lhs); - } - return lhs; - } - - /** - * Parse an additive expression - * - * <pre> - * additiveExpr : multiplicativeExpr (('+' | '-') multiplicativeExpr)* - * </pre> - * @return an add expression {@link Node}, never {@code null} - */ - private Node parseAddition() { - Node lhs = parseMultiplication(); - while( true ) { - if( false ) ; - else if( match("+") ) lhs = new AddNode(lhs,null); - else if( match("-") ) lhs = new SubNode(lhs,null); - else break; - lhs.setDef(2,parseMultiplication()); - lhs = peep(lhs); - } - return lhs; - } - - /** - * Parse an multiplicativeExpr expression - * - * <pre> - * multiplicativeExpr : unaryExpr (('*' | '/') unaryExpr)* - * </pre> - * @return a multiply expression {@link Node}, never {@code null} - */ - private Node parseMultiplication() { - var lhs = parseUnary(); - while( true ) { - if( false ) ; - else if( match("*") ) lhs = new MulNode(lhs,null); - else if( match("/") ) lhs = new DivNode(lhs,null); - else break; - lhs.setDef(2,parseUnary()); - lhs = peep(lhs); - } - return lhs; - } - - /** - * Parse a unary minus expression. - * - * <pre> - * unaryExpr : ('-') unaryExpr | '!') unaryExpr | postfixExpr | primaryExpr | '--' Id | '++' Id - * </pre> - * @return a unary expression {@link Node}, never {@code null} - */ - private Node parseUnary() { - // Pre-dec/pre-inc - int old = pos(); - if( match("--") || match("++") ) { - int delta = _lexer.peek(-1)=='+' ? 1 : -1; // Inc vs Dec - int start = pos(); - String name = _lexer.matchId(); - Var var = name==null ? null : _scope.lookup(name); - if( var!=null && !(var.type() instanceof TypeMemPtr) ) { - if( var._extern==0 && var._final && !_scope.canInit(var) ) - throw error("Cannot reassign final '"+var._name+"'"); - // Resolve fields just as a read or postfix update does. Updating - // their declaration's local slot would silently lose the Store. - pos(start); - return parsePrimary(delta); - } - // Reset, try again - pos(old); - } - if (match("-")) return peep(new MinusNode(parseUnary())); - if (match("!")) return peep(new NotNode(parseUnary())); - // Else be a primary expression - return parsePrimary(); - } - - /** - * Parse a primary expression: - * - * <pre> - * primaryExpr : integerLiteral | "string" | 'char' | true | false | null | - * new Type | '(' expression ')' | Id['++','--'] | - * type['.' Id]* - * </pre> - * @return a primary {@link Node}, never {@code null} - */ - private Node parsePrimary() { return parsePrimary(0); } - - // pre is +/-1 for a prefix update of a known name, zero otherwise. - private Node parsePrimary(int pre) { - if( _lexer.isNumber(_lexer.peek()) ) return parseLiteral(); - if( _lexer.peek('"') ) return parsePostfix(newString(parseString())); - if( matchx("true" ) ) return con(1); - 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(),"}")); - - int pos = pos(); - // Expect an identifier now - 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 ) { - // 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; - } - } - - // 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 rvalue = _scope.in(var); - if( var._extern!=0 ) - return parsePostfixName(rvalue,id,pre); - 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,pre); - - // 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,pre ); - //return parsePostfixName(_scope.in(2),id); - } - } - - // Check for assign-update, x += e0; - char ch = pre==0 ? _lexer.matchOperAssign() : (char)pre; - 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+"'"); - checkSharedFunction(var); - Node op = opAssign(ch,rvalue, var.type() ); - _scope.update(var,op); - if( postfix(ch) ) rvalue.unkeep(); - return pre==0 && postfix(ch) ? rvalue : op; - } - - // 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. - private Node opAssign( char ch, Node lhs, Type t ) { - // RHS of the update. - lhs.keep(); // Alive across parseAsgn - Node rhs = - (byte)ch == 1 ? con( 1) : // var++ - (byte)ch == -1 ? con(-1) : // var-- - parseAsgn(); // var op= rhs - if( !postfix(ch) ) lhs.unkeep(); // Allow to die in next peep - // 4 cases: - // int + int ==>> narrow int - // int + flt ==>> error, caller must fail assigning flt into int - // flt + int ==>> use float op, wrap toFloat() - // flt + flt ==>> use float op - Node op = switch(ch) { - case 1, (char)-1, - '+' -> new AddNode(lhs,rhs); - case '-' -> new SubNode(lhs,rhs); - case '*' -> new MulNode(lhs,rhs); - case '/' -> new DivNode(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 TODO(); - }; - // Convert to float ops, or narrow int types; error if not declared type. - // Also, if postfix LHS is still keep() - return convertExpr(peep(op),t); - } - - - /** - Parse an allocation - */ - private Node alloc() { - Type t = 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 are always zero/null filled"); - Node len = parseAsgn().keep(); - require("]"); - 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; - } - 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 - - 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) { - 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", slen, lenAlias, body, elemAlias, true),true); - assert str.isConstant(); - return con(str.makeRO()); - } - - /** - * Parse postfix expression; this can be a field expression, an array - * lookup or a postfix operator like '#' - * - * <pre> - * expr ('.' FIELD)* [ = expr ] // Field reference - * expr '#' // Postfix unary read operator - * expr ['++' | '--' ] // Postfix unary write operator - * expr ('[' expr ']')* = [ = expr ] // Array reference - * 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 = "[]"; - // 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,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)); - } - - /** - * Parse postfix expression; this can be a field expression, an array - * lookup or a postfix operator like '#' - * - * <pre> - * expr ['++' | '--' ] // Postfix unary write operator - * expr [ [op]= expr ] // Field reference - * expr ('[' expr ']')* [ [op]= expr ] // Array reference - * </pre> - */ - - // 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=="[]" - // 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( pre==0 && matchOpx('=','=') ) { - // Field assignment - Node val = parseAsgn().keep(); - // 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(); - - 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 = peep(new LoadNode(loc(),name, alias, ctrl(), mem, expr, off)); - - // Check for assign-update, "ptr.fld += expr" or "ary[idx]++" - char ch = pre==0 ? _lexer.matchOperAssign() : (char)pre; - if( ch!=0 ) { - 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(); - return pre==0 && postfix(ch) ? load : op; - } - off.unkill(); - // Might be a method call, so pass expr as 'self' - return parsePostfixMethod(load,expr.unkeep()); - } - - - // zero/sign extend. "i" is limited to either classic unsigned (min==0) or - // classic signed (min=minus-power-of-2); max=power-of-2-minus-1. - private Node zsMask(Node val, Type t ) { - if( val._type instanceof TypeFloat && t instanceof TypeInteger ) - val = new ToIntegerNode(val).peephole(); - if( !(val._type instanceof TypeInteger tval && t instanceof TypeInteger t0 && !tval.isa(t0)) ) { - if( !(val._type instanceof TypeFloat tval && t instanceof TypeFloat t0 && !tval.isa(t0)) ) - return val; - // Float rounding - return peep(new RoundF32Node(val)); - } - if( t0._min==0 ) // Unsigned - return peep(new AndNode(null,val,con(t0._max))); - // Signed extension - int shift = Long.numberOfLeadingZeros(t0._max)-1; - Node shf = con(shift); - if( shf._type==TypeInteger.ZERO ) - return val; - return peep(new SarNode(null,peep(new ShlNode(null,val,shf.keep())),shf.unkeep())); - } - - /** - * Parse a function body; the caller will parse the surrounding "{}" - * - * <pre> - * { [type arg,]* -> expr } - * { expr } // The no-argument function - * </pre> - */ - private Node func() { - Ary<Type> ts = new Ary<>(Type.class); - Ary<String> ids = new Ary<>(String.class); - java.util.BitSet finals = new java.util.BitSet(); - Type self = implicitSelfType(); - ts.push(self); - ids.push(self == TypePtr.PTR ? "#self" : "self"); - - // Parse other arguments - _lexer.skipWhiteSpace(); - Lexer loc = loc(); // First argument location - while( true ) { - Type t = type(); // Arg type - if( t==null ) break; - finals.set(ids.size(),bindingFinal(t)); - String id = requireId(); - ts .push(t ); // Push type/arg pairs - ids.push(id); - match(","); - } - require("->"); - // Make a concrete function type, with a fidx - 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(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(); - } - - /** - * Parse function call arguments; caller will parse the surrounding "()" - * <pre> - * ( arg* ) - * </pre> - */ - private Node functionCall(Node fcn, Node self) { - Lexer site = loc(); - site._position--; // Opening '('; arguments can contain nested calls. - if( fcn._type == Type.NIL ) - throw error("Calling a null function pointer"); - 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 ) // Method call has a self - args.push(self.keep()); - while( !peek(')') ) { - Node arg = parseAsgn(); - if( arg==null ) break; - args.push(arg.keep()); - if( !match(",") ) break; - } - // Control & memory after parsing args - args.set(0,ctrl().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 ) - if( arg.isUnused() ) - arg.kill(); - return con(Type.TOP); - } - - // Into the call - CallNode call = (CallNode)new CallNode(site, args.asAry()).peephole(); - // Post-call setup - 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()); - // Memory from CallEnd - Node mem = new ProjNode(cend,1,ScopeNode.MEM0).peephole(); - mem(mem); - // Call result - return new ProjNode(cend.unkeep(),2,"#2").peephole(); - } - - // Just after parsing "type foo = " can parse `"C"` - private boolean isExternDecl( ) { - int old = pos(); - String s = parseString(); - if( "C".equals(s) ) return true; - pos(old); - return false; - } - // External linked constant - 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)); - } - - /** - * Parse integer literal - * - * <pre> - * integerLiteral: [1-9][0-9]* | [0] - * floatLiteral: [digits].[digits]?[e [digits]]?[f]? - * </pre> - */ - private ConstantNode parseLiteral() { return con(_lexer.parseNumber()); } - 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 ) { - if( n._type instanceof TypeMemPtr ptr && ptr._obj._fref ) - _requiredTypes.add(ptr._obj._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()); - } - - // Parse a string or null - private String parseString() { - if( !peek('"') ) return null; - _lexer.inc(); - StringBuilder sb = new StringBuilder(); - while( !_lexer.isEOF() ) { - char c = _lexer.nextChar(); - if( c=='"' ) return sb.toString(); - if( c!='\\' ) { - sb.append(c); - continue; - } - if( _lexer.isEOF() ) - throw error("Unclosed string"); - sb.append(escapedChar("string")); - } - throw error("Unclosed string"); - } - - private char escapedChar(String kind) { - char esc = _lexer.nextChar(); - return switch( esc ) { - case '0' -> '\u0000'; - case 'n' -> '\n'; - case 't' -> '\t'; - case 'r' -> '\r'; - case '\\' -> '\\'; - case '"' -> '"'; - case '\'' -> '\''; - default -> throw error("Unknown "+kind+" escape \\\\"+esc); - }; - } - - // Already parsed "'" - private Node parseChar() { - if( _lexer.isEOF() ) throw error("Unclosed character"); - char c = _lexer.nextChar(); - if( c=='\\' ) { - if( _lexer.isEOF() ) throw error("Unclosed character"); - c = escapedChar("character"); - } - 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 - - // Return true and skip if "syntax" is next in the stream. - private boolean match (String syntax) { return _lexer.match (syntax); } - // Match must be "exact", not be followed by more id letters - private boolean matchx(String syntax) { return _lexer.matchx(syntax); } - private boolean matchOp(char c0 ) { return _lexer.matchOp(c0); } - private boolean matchOpx(char c0, char c1) { return _lexer.matchOpx(c0,c1); } - // Return true and do NOT skip if 'ch' is next - private boolean peek(char ch) { return _lexer.peek(ch); } - private boolean peekIsId() { return _lexer.peekIsId(); } - - // ch is +/- 1, means oper++ or oper-- means postfix - private static boolean postfix(char ch) { - return (byte)ch== 1 || (byte)ch== -1; - } - - public int pos() { return _lexer._position; } - private int pos(int pos) { - int old = _lexer._position; - _lexer._position = pos; - return old; - } - private Type posT(int pos) { _lexer._position = pos; return null; } - // Source code location - Lexer loc() { return new Lexer(_lexer); } - - - // Require and return an identifier - private String requireId() { - String id = _lexer.matchId(); - if (id != null && !KEYWORDS.contains(id) ) return id.intern(); - throw error("Expected an identifier, found '"+id+"'"); - } - - private String matchId() { - int old = pos(); - String id = _lexer.matchId(); - if( id==null ) return null; - if( !KEYWORDS.contains(id) ) return id; - pos(old); - return null; - } - - // Require an exact match - private Parser require(String syntax) { require(null, syntax); return this; } - private <N> N require(N n, String syntax) { - if (match(syntax)) return n; - throw errorSyntax(syntax); - } - - //////////////////////////////////// - // Lexer components - - // Lexer provides low level access to the raw file bytes, peeks and matches - // short strings, parses numbers, skips comments and whitespace, tracks - // line numbers, allows the parse position to be saved and restored, and - // serves as a location indicator for errors. - - public static class Lexer { - - // 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; - - // - 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 - */ - public Lexer(String source) { - this(source.getBytes()); - } - - /** - * Direct from disk file source - */ - 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; - } - - - public static void gather(Lexer loc, HashMap<String,Integer> strs) { - if( loc==null || loc._cu==null ) return; - Serialize.gather(strs,loc._cu._cname); - String file=loc._cu.sourceFile(); - if( file!=null ) Serialize.gather(strs,file); - } - - public static void packed(Lexer loc, BAOS out, HashMap<String,Integer> strs) { - if( loc==null || loc._cu==null ) { out.packed4(0); return; } - out.packed4(loc._position+1); - out.packed4(strs.get(loc._cu._cname)); - String file=loc._cu.sourceFile(); - out.packed4(file==null ? 0 : strs.get(file)); - } - - public static Lexer unpack(BAOS in, String[] strs) { - int pos=in.packed4(); - if( pos==0 ) return null; - String owner=strs[in.packed4()]; - String file=strs[in.packed4()]; - CompUnit cu=CodeGen.CODE._compunits.get(owner.replace('.','/')); - assert cu!=null : owner; - if( !file.isEmpty() ) cu._sourceFile=file; - return new Lexer(cu,pos-1); - } - - /** - * 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; - } - - // Very handy in the debugger, shows the unparsed program - @Override - public String toString() { - return _input==null ? _cu._cname+"@"+_position : new String(_input, _position, _input.length - _position); - } - - // True if at EOF - private boolean isEOF() { - return _position >= _input.length; - } - - // Peek next character, or report EOF - private char peek() { - return isEOF() ? Character.MAX_VALUE // Special value that causes parsing to terminate - : (char) _input[_position]; - } - // Just crash if misused - public byte peek(int off) { return _input[_position+off]; } - - private void inc() { - if( _position++ < _input.length && _input[_position-1] == '\n' ) { - _line_number++; - _line_start = _position; - } - } - // Does not honor LF, so caller must roll back position on a LF - private char nextChar() { - char ch = peek(); - inc(); - return ch; - } - - // True if a white space - private boolean isWhiteSpace() { - return peek() <= ' '; // Includes all the use space, tab, newline, CR - } - - /** - * Return the next non-white-space character - */ - private void skipWhiteSpace() { - while( true ) { - if( isWhiteSpace() ) inc(); - // Skip // to end of line - else if( _position+2 < _input.length && - _input[_position ] == '/' && - _input[_position+1] == '/') { - inc(); inc(); - while( !isEOF() && _input[_position] != '\n' ) - inc(); - } else if( _position+2 < _input.length && - _input[_position ] == '/' && - _input[_position+1] == '*') { - // Skip /*comment*/ - while( !isEOF() && !(_input[_position-1] == '*' && _input[_position] == '/')) - inc(); - inc(); - } else break; - } - } - - // Next non-white-space character, or EOF - public char nextXChar() { skipWhiteSpace(); return nextChar(); } - - // Return true, if we find "syntax" after skipping white space; also - // then advance the cursor past syntax. - // Return false otherwise, and do not advance the cursor. - boolean match(String syntax) { - assert syntax.indexOf('\n')==-1; // No newlines in match - skipWhiteSpace(); - int len = syntax.length(); - if (_position + len > _input.length) return false; - for (int i = 0; i < len; i++) - if ((char) _input[_position + i] != syntax.charAt(i)) - return false; - _position += len; - return true; - } - - // Match must be exact and not followed by more ID characters. - // Prevents identifier "ifxy" from matching an "if" statement. - boolean matchx(String syntax) { - if( !match(syntax) ) return false; - if( !isIdLetter(peek()) ) return true; - _position -= syntax.length(); - return false; - } - // Match this char, and the next char must be different. - // Handles '&&' vs '&' - boolean matchOp( char c0 ) { - skipWhiteSpace(); - if( _position+1 >= _input.length || _input[_position]!=c0 || _input[_position+1]==c0 ) - return false; - inc(); - return true; - } - // Match these two characters in a row - boolean matchOpx(char c0, char c1) { - skipWhiteSpace(); - if( _position+1 >= _input.length || _input[_position]!=c0 || _input[_position+1]==c1 ) - return false; - inc(); - return true; - } - - private boolean peek(char ch) { - skipWhiteSpace(); - return peek()==ch; - } - - boolean peekIsId() { - skipWhiteSpace(); - return isIdStart(peek()); - } - - // Return an identifier or null - String matchId() { - return peekIsId() ? parseId() : null; - } - - // Used for errors - String getAnyNextToken() { - if (isEOF()) return "EOF"; - if (isIdStart(peek())) return parseId(); - if (isNumber(peek())) return parseNumberString(); - if (isPunctuation(peek())) return parsePunctuation(); - return String.valueOf(peek()); - } - - - boolean isNumber(char ch) {return Character.isDigit(ch);} - - // Return a constant Type, either TypeInteger or TypeFloat - 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,n))); - } - private String parseNumberString() { - int old = _position; - int len = Math.abs(isLongOrDouble()); - if( peek()=='f' ) { - nextChar(); - len++; - } - return new String(_input,old,len); - } - - // Return +len that ends a long - // Return -len that ends a double - private int isLongOrDouble() { - int old = _position; - char c; - while( Character.isDigit(c=nextChar()) ) ; - if( !(c=='e' || c=='.') ) - return --_position - old; - if( peek() == '-' ) nextChar(); - while( Character.isDigit(c=nextChar()) || c=='e' || c=='.' ) ; - return -(--_position - old); - } - - // First letter of an identifier - private boolean isIdStart(char ch) { - return Character.isAlphabetic(ch) || ch == '_'; - } - - // All characters of an identifier, e.g. "_x123" - private boolean isIdLetter(char ch) { - return Character.isLetterOrDigit(ch) || ch == '_'; - } - - private String parseId() { - int start = _position; - while (isIdLetter(nextChar())) ; - return new String(_input, start, --_position - start).intern(); - } - - // - private boolean isPunctuation(char ch) { - return "=;[]<>()+-/*&|^".indexOf(ch) != -1; - } - - private String parsePunctuation() { - int start = _position; - return new String(_input, start, 1); - } - - // Next oper= character, or 0. - // As a convenience, mark "++" as a char 1 and "--" as char -1 (65535) - // Distinct tags for the multi-character compound operators. - static final char SHL = 2, SAR = 3, SHR = 4; - - public char matchOperAssign() { - skipWhiteSpace(); - if( match("<<=" ) ) return SHL; - if( match(">>>=") ) return SHR; - if( match(">>=" ) ) return SAR; - if( _position+2 >= _input.length ) return 0; - char ch0 = (char)_input[_position]; - if( "+-/*&|^".indexOf(ch0) == -1 ) return 0; - char ch1 = (char)_input[_position+1]; - if( ch1 == '=' ) { _position += 2; return ch0; } - if( isIdLetter((char)_input[_position+2]) ) return 0; - if( ch0 == '+' && ch1 == '+' ) { _position += 2; return (char) 1; } - if( ch0 == '-' && ch1 == '-' ) { _position += 2; return (char)-1; } - return 0; - } - } - - ParseException errorSyntax(String syntax) { return _errorSyntax("expected `"+syntax+"`"); } - private ParseException _errorSyntax(String msg) { - 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); } - - public static class ParseException extends RuntimeException { - public final Lexer _loc; - // file:line:charoff err - //String msg = "src:"+_line_number+":"+(_position-_line_start)+" "+errorMessage; - ParseException( String msg, Lexer loc ) { super(msg); _loc = loc; } - } - -} |
