package com.seaofnodes.simple.parser; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.node.ScopeNode.Kind; import com.seaofnodes.simple.type.*; import com.seaofnodes.simple.util.Ary; import java.util.IdentityHashMap; /** Struct and constructor declarations, field discovery, and initialization checks. */ final class Structs { private final Parser P; // Constructor exit types survive scope teardown until all fields are // known. A null entry denotes a constructor with no returning path. Ary> _ctorExits; // Constructor declaration: new TypeName = { args -> body }; record Constructor(String name, Lexer body) {} Constructor _pendingCtor; Structs(Parser P) { this.P = P; } /** * Parse a struct declaration, and return the following statement. * Structs cannot be redefined, but can be nested. * * @return zero */ Node parseStruct( ) { // keyword "struct" already parsed, so expect the struct name next. String typeName = P.requireId(); P.require("{"); // create type class:NESTED.typeName; no fields. String old = P._nestedType; String fullName = P._scope.depth()==1 && P._nestedType.endsWith(typeName) ? old : (old+"."+typeName).intern(); P._nestedType = fullName; // Recursive structs start with this as their basename ReturnNode ret = parseInitializer( false, fullName ); P._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 = Parser.addClzPrefix(fullName); TypeFunPtr sig = ret.fun().sig(); TypeStruct ts = (TypeStruct)Parser.TYPES.get(clzName); String initName = initFieldName(fullName); Field oldFld = ts==null ? null : ts.field(initName); Field fld = oldFld==null ? Field.make(initName,sig,P._code.alias(P._ref._cname),true) : oldFld.makeFrom(sig); Field ctorFld = ts==null ? null : ts.field(""); // 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 = P.allocations.makeAllocator((TypeStruct)Parser.TYPES.get(fullName),ret,ctorFld); ts = ts.add(Field.make("",alloc,P._code.alias(P._ref._cname),true)); Parser.TYPES.put(clzName,ts); // Insert a field in the containing class with the nested class type. // This is basically sugar for "val CLZNAME = " // and is a nicer version of updateSelfAsFieldsDiscovered. P._scope.define(typeName, sig, true, new FunPtrNode(sig,P._code._start,ret).peephole(), P.loc()); P.require("}"); return P.require(P._code.ZERO,";"); } boolean inStructDeclaration() { return P._scope.klast() instanceof Kind.Func fun && FunNode.isInstance(fun._name); } Node parseConstructorDeclaration() { TypeStruct self = P._scope.constructorSelf(); assert self != null; String name = P.requireId(); String expect = initFieldName(self._name); if( name != expect ) throw P.error("Constructor name '" + name + "' must match type name '" + expect + "'"); P.require("="); if( _pendingCtor!=null ) throw P.error("Redefining constructor '"+name+"'"); _pendingCtor = new Constructor(name,P.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. P.require("{"); P._lexer.skipTo('{','}',"constructor body"); P.require("}"); P.require(";"); return P._code.ZERO; } void finishConstructor() { if( _pendingCtor==null ) return; Constructor ctor = _pendingCtor; _pendingCtor = null; Lexer after = P._lexer; P._lexer = new Lexer(ctor.body()); defineConstructor(ctor.name()); P._lexer = after; } private void defineConstructor(String name) { TypeStruct self = P._scope.constructorSelf(); Node ctor = P.functions.parseConstructor(name); TypeFunPtr sig = (TypeFunPtr)ctor._type; String clzName = Parser.addClzPrefix(self._name); TypeStruct clz = (TypeStruct)Parser.TYPES.get(clzName); Field old = clz==null ? null : clz.field(""); if( old != null ) throw P.error("Redefining constructor '" + name + "'"); Field fld = Field.make("",sig,P._code.alias(P._ref._cname),true); Parser.TYPES.put(clzName,clz==null ? TypeStruct.make(clzName,true,fld) : clz.add(fld)); P.require(ctor,";"); } // Parse a struct declaration (not an allocation); file-level is a // class-init, and scope structs are normal ReturnNode parseInitializer( boolean isClz, String typeName ) { var oldExits = _ctorExits; _ctorExits = new Ary(IdentityHashMap.class); // Record & restore global state set during parsing the code Node oldCtrl= P._scope.ctrl().keep(); Node oldMem = P._scope.mem ().keep(); // Make the future clazz/instance struct. TypeStruct tself = (TypeStruct)Parser.TYPES.get(typeName); assert tself==null || tself._open; // Close an existing open struct, or define a forward ref tself = tself==null ? TypeStruct.make(typeName,true) : tself.defined(); // Class object keeps a runtime flag `$init` to signal the has run. if( isClz ) tself = tself.add(Field.make("$init",TypeInteger.BOOL,P._code.alias(P._ref._cname),false)); Parser.TYPES.put(typeName, tself); // Signature of the default constructor TypeFunPtr sig = structInitSig(isClz,tself); // Function and argument names of default constructor String fname = (typeName + (isClz ? "." : ".")).intern(); String[] ids = new String[]{"self", isClz ? "arg" : "#selfMem"}; // Struct decls look like function bodies. Parse function body normally. ReturnNode ret = P.functions.parseBody(fname,sig,P.loc(),ids); _ctorExits = oldExits; // Unwind global state. P.ctrl(oldCtrl.unkeep()); P.mem (oldMem .unkeep()); return ret; } // 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 order, 2nd in the file just behind the if( isClz ) { assert Parser.startsClzPrefix( clzBare ); clzBare = clzBare.substring(Parser.CLZ.length()).intern(); order = 2; // 2 is always the order, first in file and skipping reserved #1 } return TypeFunPtr.make1((byte)2, true, targs, tret, P._code.fidx(clzBare,order)); } int updateSelfAsFieldsDiscovered( String typeName, int nvar ) { int lex = P._scope.klast()._lexSize + 2/*Skip self and selfMem*/; // Load self type in and out of the TYPES hashtable. TypeStruct tself = (TypeStruct)Parser.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 < P._scope.nIns() ) { Var v = P._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(),P._code.alias(P._ref._cname),v._final) : old.makeFrom(v.type()); tself = tself.addOrUpdate(fld); } Parser.TYPES.put(typeName,tself); return nvar; } void upgradeSelfTypeAndStoreFields( String typeName, boolean isClz, ParmNode self, ParmNode smem, boolean upgrade ) { int lex = P._scope.klast()._lexSize; assert P._scope.var(lex)._name=="self"; TypeStruct tself = (TypeStruct)Parser.TYPES.get(typeName); int base = lex+2; // Skip ctrl, mem if( upgrade ) { // Default / defines the struct and can sharpen fields. // A named constructor only initializes the already-closed struct. for( int i = 0; i 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 ) // returns upgraded private memory smem._declaredType = smem._type = TypeMem.makePrivate(tself); } if( !upgrade ) { IdentityHashMap exit = null; if( P.functions._returnScope!=null && !P.functions._returnScope.ctrl()._type.isHigh() ) { exit = new IdentityHashMap<>(); int first = P._scope._kinds.at(P._scope.depth()-2)._lexSize+2; for( int i = first; i updates the private memory that got // passed in - is treated like a normal argument and not like memory. Node mmm = isClz ? P.functions._returnScope.mem(1) : smem; // Store constructor results into fields for( int i = 0; i return memory. P.functions._returnScope.mem(mmm); //_code.add(mmm); } // Check that all fields are initialized on all constructor exit paths. private void checkInitFields(TypeStruct ts, int base) { if( P.functions._returnScope==null || P.functions._returnScope.ctrl()._type.isHigh() ) return; boolean implicit = _ctorExits.isEmpty(); if( implicit ) _ctorExits.add(new IdentityHashMap<>()); for( IdentityHashMap exit : _ctorExits ) { if( exit==null ) continue; for( Field fld : ts._fields ) { int idx = fieldVarIdx(base,fld._fname); if( idx == -1 ) continue; Var v = P._scope.var(idx); if( !v._uninit || v._fref ) continue; Type t = exit.getOrDefault(v,P.functions._returnScope.in(idx)._type); if( !initialized(t) ) throw Parser.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); } } } // Seed the constructor-local SSA slots from the declaration initializer. void constructorDefaults(String typeName, FunNode fun, Lexer loc) { int lex = P._scope.klast()._lexSize; TypeStruct shape = (TypeStruct)Parser.TYPES.get(typeName); for( Field fld : shape._fields ) { int idx = fieldVarIdxBefore(lex,fld._fname); if( idx<0 ) continue; Var v = P._scope.var(idx); if( v._fref || v._extern!=0 || v._uninit || P._scope.in(v)._type.isConstant() ) continue; Node value = P.conversions.convertExpr( new LoadNode(loc,fld._fname,fld._alias,P.ctrl(),fun.parm(3),fun.parm(2),Parser.off(shape,fld._fname)).peephole(),v.type()); P._scope.update(v,value); } } void beginClassInit(FunNode fun, Lexer loc) { // 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( P._ref._par!=null ) { String pname = Parser.addClzPrefix(P._ref._par._cname); TypeStruct parent = (TypeStruct)Parser.TYPES.get(pname); if( parent==null ) parent = TypeStruct.make(pname,true); Node ptr = P.con(TypeMemPtr.make((byte)2,parent,true)).keep(); P.classInit(loc,ptr); ptr.unkeep(); } Node self = fun.parm(2); Field flag = ((TypeMemPtr)self._type)._obj.field("$init"); Node prior = P.mem().keep(); Node st = P.peep(new StoreNode(loc,"$init",flag._alias,flag._t,P.ctrl(),prior,self, P.fldoff(self,"$init"),P.con(1),false)); P.storeMem(st,flag._alias,prior); prior.unkeep(); } // Validate each user constructor, or an implicit empty one, before the // declaration scope goes away. Later fields use their declaration defaults. static boolean initialized(Type t) { // Can't be BOTTOM, that means explicit not initialized. // Can't be high (initialized to an error) // Can't be left null and declared not-null. return t!=Type.BOTTOM && !t.isHigh() && (!(t instanceof TypeNil tn) || tn.notNull()); } private int fieldVarIdx( int base, String name ) { for( int i = base; i=0; i-- ) if( P._scope.var(i)._name == name ) return i; return -1; } boolean isConstructorDecl(String name) { TypeStruct self = P._scope.constructorSelf(); return self != null && name == initFieldName(self._name); } boolean inExplicitConstructor() { Kind.Func func = (Kind.Func)P._scope._kinds.at(P._scope.enclosingFuncOrDecl()); return isExplicitConstructorName(func._name); } boolean isExplicitConstructorName(String funName) { TypeStruct self = P._scope.constructorSelf(); return self != null && funName == (self._name+"."+initFieldName(self._name)).intern(); } static TypeStruct constructorRecv(TypeStruct self) { Field[] fs = new Field[self._fields.length]; for( int i = 0; i