From feb98e1bb6c973428b94591ed5b0aed5e4137bd0 Mon Sep 17 00:00:00 2001 From: Cliff Click Date: Sat, 24 Oct 2026 15:31:01 -0700 Subject: Finish separate compilation snapshot in chapter 28b --- .../main/java/com/seaofnodes/simple/Parser.java | 185 +++++++++++++++++---- .../com/seaofnodes/simple/codegen/CodeGen.java | 4 +- .../com/seaofnodes/simple/codegen/CompUnit.java | 3 +- .../java/com/seaofnodes/simple/codegen/Opto.java | 3 +- .../com/seaofnodes/simple/codegen/ParseAll.java | 13 +- .../com/seaofnodes/simple/codegen/Serialize.java | 19 ++- .../com/seaofnodes/simple/node/CallEndNode.java | 2 +- .../java/com/seaofnodes/simple/node/CallNode.java | 22 ++- .../main/java/com/seaofnodes/simple/node/Node.java | 1 + .../java/com/seaofnodes/simple/node/ScopeNode.java | 31 +++- .../com/seaofnodes/simple/node/StopCUNode.java | 9 + .../java/com/seaofnodes/simple/node/StoreNode.java | 10 +- 12 files changed, 250 insertions(+), 52 deletions(-) (limited to 'chapter28b/src/main/java') diff --git a/chapter28b/src/main/java/com/seaofnodes/simple/Parser.java b/chapter28b/src/main/java/com/seaofnodes/simple/Parser.java index 5ceda6a9..87a3570a 100644 --- a/chapter28b/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter28b/src/main/java/com/seaofnodes/simple/Parser.java @@ -213,7 +213,6 @@ public class Parser { } - /** * Parses a function body, assuming the header is parsed. */ @@ -255,12 +254,14 @@ public class Parser { } 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)); + _scope.push(new Kind.Func(funName,isCtor && !funName.endsWith("."))); ScopeNode breakScope = _breakScope; _breakScope = null; ScopeNode continueScope = _continueScope; _continueScope = null; ScopeNode returnScope = _returnScope; _returnScope = null; @@ -290,6 +291,20 @@ public class Parser { _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); + } + } + // The initializer owns its run-once flag store. Parent initialization // is a structural requirement, executed before this body by CLInit lowering. if( fun.isClz() ) { @@ -316,6 +331,9 @@ public class Parser { nvar = updateSelfAsFieldsDiscovered( typeName, nvar ); } + if( isInit ) finishConstructor(); + _pendingCtor=outerCtor; + ParmNode self = fun.parm(2); // Last expression is the return if( ctrl()._type==Type.CONTROL ) { @@ -456,6 +474,10 @@ public class Parser { 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)); @@ -597,6 +619,9 @@ public class Parser { } // 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; @@ -605,6 +630,43 @@ public class Parser { 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); @@ -614,7 +676,7 @@ public class Parser { throw error("Redefining constructor '" + name + "'"); Field fld = Field.make("",sig,_code.alias(_ref._cname),true); TYPES.put(clzName,clz==null ? TypeStruct.make(clzName,true,fld) : clz.add(fld)); - return require(ctor,";"); + require(ctor,";"); } /** @@ -1006,9 +1068,9 @@ public class Parser { 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.inConstructor() && def._idx >= _scope.klast()._lexSize) && - !inExplicitConstructor() ) + !_scope.canInit(def) ) throw error("Cannot reassign final '"+name+"'"); + checkSharedFunction(def); // Parse assignment expression Node expr = parseAsgn(); @@ -1082,6 +1144,15 @@ public class Parser { }; } + 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. @@ -1163,6 +1234,11 @@ public class Parser { 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 @@ -1214,7 +1290,6 @@ public class Parser { } - /** * Parse a struct declaration, and return the following statement. * Structs cannot be redefined, but can be nested. @@ -1744,7 +1819,6 @@ public class Parser { } - /** * Parse an eq/ne expression */ @@ -1789,8 +1863,8 @@ public class Parser { return cmp.unkeep(); } - // rhs is keeped() and becomes lhs - // cmp is NOT keeped() and is the last test + // 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(); @@ -1806,9 +1880,9 @@ public class Parser { throw error("Mixing relational directions in a chained relational test"); // True side parses next arm of test ctrl(ifT.unkeep()); - lhs = rhs; // lhs is keeped() and is old RHS + lhs = rhs; // Reuse the previous RHS exactly once rhs = parseShift().keep(); - cmp = makeCompBool(dir,lhs.unkeep(),rhs); // Convert to a bool + 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(); @@ -1822,7 +1896,7 @@ public class Parser { ctrl(ifT.unkeep()); RegionNode r = fail.mergeScopes(_scope, loc()).init(); _scope = fail; - return new PhiNode("", r,_code.ZERO,con(1)).peephole(); + return new PhiNode("", r,con(0),con(1)).peephole(); } private int parseCompDir() { @@ -1833,12 +1907,11 @@ public class Parser { return 0; } - private Node makeCompBool( int dir0, Node lhs, Node rhs ) { - // Convert to a bool - if( Math.abs(dir0)==2 ) // Swap direction - { Node tmp = lhs; lhs = rhs; rhs = tmp; } - lhs = peep(dir0 < 0 ? new BoolNode.LE(lhs,rhs) : new BoolNode.LT(lhs,rhs)); - return peep(lhs); + 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); } /** @@ -1927,9 +2000,10 @@ public class Parser { if( n != null && !(n.type() instanceof TypeMemPtr) ) { if( n._extern!=0 ) return parsePostfixName(_scope.in(n),name,delta); - if( n._final ) + if( n._final && !_scope.canInit(n) ) throw error("Cannot reassign final '"+n._name+"'"); - Node expr = zsMask(peep(new AddNode(_scope.in(n),con(delta))),n.type()); + checkSharedFunction(n); + Node expr = convertExpr(peep(new AddNode(_scope.in(n),con(delta))),n.type()); _scope.update(n,expr); return expr; } @@ -2015,12 +2089,10 @@ public class Parser { return parsePostfixName(rvalue,id,0); if( var._fref ) return parsePostfix(rvalue); - // Required constructor fields begin as their declared pointer type - // widened to include null. Inside the explicit constructor, any read - // before all paths replace that value observes a partially initialized - // object. Ordinary methods run only after construction is complete; - // their enclosing declaration scope still carries the same Var marker. - if( inExplicitConstructor() && var._uninit && rvalue._type instanceof TypeNil tn && tn.nullable() ) + // 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 @@ -2029,13 +2101,13 @@ public class Parser { 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 ) + if( kx+1 == fx && kk instanceof Kind.Func inst && FunNode.isInstance(inst._name) && fk instanceof Kind.Func method && !inExplicitConstructor() ) return parsePostfixName(_scope.in(method._lexSize),id,0); // Check for a function-escaping variable; these require true // closures. Final constants are OK; final vars require a hidden var // argument; not-final fields can just use an explicit struct arg. - if( kx < fx && // Out of scope + 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"); @@ -2062,8 +2134,9 @@ public class Parser { return parsePostfixMethod(rvalue,_scope.in(_scope.update(_scope.var(selfx),null))); } // Assign-update direct into Scope - if( var._final ) + 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); return postfix(ch) ? rvalue.unkeep() : op; @@ -2261,6 +2334,58 @@ public class Parser { 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 strs) { if( loc==null || loc._cu==null ) return; Serialize.gather(strs,loc._cu._cname); diff --git a/chapter28b/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java b/chapter28b/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java index 47ec0112..8302ede6 100644 --- a/chapter28b/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java +++ b/chapter28b/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java @@ -318,7 +318,7 @@ public class CodeGen { } public String entryClinitName() { - return Parser.addClzPrefix(_srcName==null ? "Test" : _srcName)+"."; + return Parser.addClzPrefix(_srcName==null ? "Test" : _srcName.replace('/','.'))+"."; } // Verbose printing during compilation @@ -463,6 +463,8 @@ public class CodeGen { // --------------------------- // Parser object + public final Ary _calls = new Ary<>(CallNode.class); + public final Parser P; // Parse ASCII text into Sea-of-Nodes IR diff --git a/chapter28b/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java b/chapter28b/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java index 128c4136..71126795 100644 --- a/chapter28b/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java +++ b/chapter28b/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java @@ -57,7 +57,8 @@ public class CompUnit { _obj = new File( CODE._buildDir + "/" + fname + ".o" ); boolean mustCompile = _smp.exists() && // Has source to compile - ( !_obj.exists() || // No object file + ( fname.equals(CODE._srcName) || // Explicit input is a compilation request + !_obj.exists() || // No object file _smp.lastModified() > _obj.lastModified() || // Out-of-date with source? checkDependentObjs() ); // Out-of-date with other objs? _src = mustCompile ? new String(Files.readAllBytes(_smp.toPath())) : null; diff --git a/chapter28b/src/main/java/com/seaofnodes/simple/codegen/Opto.java b/chapter28b/src/main/java/com/seaofnodes/simple/codegen/Opto.java index ef222f60..3b3f9f19 100644 --- a/chapter28b/src/main/java/com/seaofnodes/simple/codegen/Opto.java +++ b/chapter28b/src/main/java/com/seaofnodes/simple/codegen/Opto.java @@ -145,8 +145,7 @@ abstract public class Opto { private static void linkStart( CodeGen code, FunNode fun ) { assert !fun.isDead(); StartCUNode start = fun._compunit._start; - if( fun.nIns() < 2 ) { - assert fun.nIns() < 2 || fun.in(1) != start; + if( !fun.unknownCallers() ) { assert !start.isDead(); code.keepInCompUnit(fun); // Function is reachable by any *remote* caller who gets the pointer! diff --git a/chapter28b/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java b/chapter28b/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java index 7ce727fd..c55f9a90 100644 --- a/chapter28b/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java +++ b/chapter28b/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java @@ -81,11 +81,16 @@ public abstract class ParseAll { } } + private static void enqueue(CompUnit cu) { + if( cu._par!=null && (cu._par._src!=null || (cu._par._obj!=null && cu._par._obj.exists())) ) enqueue(cu._par); + if( cu._start==null && WORK.find(cu)==-1 ) WORK.add(cu); + } + private static void parseAll(CodeGen code, CompUnit cunit) { WORK.clear(); // Cleanup from prior tests NEEDS_LOAD.clear(); // Prime the pump with the first CompUnit - WORK.add(cunit); + enqueue(cunit); // Work through all unparsed compilation units and parse while( !WORK.isEmpty() ) { CompUnit cu = WORK.remove(0); @@ -245,7 +250,7 @@ public abstract class ParseAll { if( sub==null ) sub=makeCUnit(code,null,fname,depObj(code,cunit,fname)); // If not seen before or out of date, parse the new nested cunit if( sub._clz == null && WORK.find(sub)==-1 && NEEDS_LOAD.find(sub)==-1 ) - WORK.add(sub); + enqueue(sub); } } catch( IOException ioe ) { throw new RuntimeException(ioe); @@ -370,7 +375,7 @@ public abstract class ParseAll { CompUnit sub = makeCUnit(code,cunit,symbol); // If not seen before or out of date, parse the new nested cunit if( sub._clz == null && WORK.find(sub) == -1 ) - WORK.add(sub); + enqueue(sub); return sub; } @@ -405,7 +410,7 @@ public abstract class ParseAll { Parser.TYPES.put(clzName,clz = TypeStruct.make(clzName,true)); cunit._clz = clz; if( WORK.find(cunit) == -1 && NEEDS_LOAD.find(cunit) == -1 ) - WORK.add(cunit); + enqueue(cunit); return cunit; } } diff --git a/chapter28b/src/main/java/com/seaofnodes/simple/codegen/Serialize.java b/chapter28b/src/main/java/com/seaofnodes/simple/codegen/Serialize.java index 6a23252d..5a74ded2 100644 --- a/chapter28b/src/main/java/com/seaofnodes/simple/codegen/Serialize.java +++ b/chapter28b/src/main/java/com/seaofnodes/simple/codegen/Serialize.java @@ -110,9 +110,12 @@ abstract public class Serialize { stopType.gather(types); startType.gather(types); // A defined class remains importable even when its initializer body dies. + // Nested structs also need their class declaration: and + // can be absent from graph types even while their function bodies live. + // Keep the nearest file owner, so an imported child is not re-exported + // merely because its parent was compiled here. for( TypeStruct ts : pubTypes ) - if( Parser.startsClzPrefix(ts._name) && - Arrays.binarySearch(units,ts._name.substring(Parser.CLZ.length()).replace('.','/'))>=0 ) + if( ownedClass(ts,units) ) ts.gather(types); // Only record canonical declarations already reachable from this @@ -262,6 +265,18 @@ abstract public class Serialize { } // Returned array of *public* strings only. + private static boolean ownedClass(TypeStruct ts, String[] units) { + if( !Parser.startsClzPrefix(ts._name) ) return false; + String name=ts._name.substring(Parser.CLZ.length()).replace('.','/'); + while( true ) { + if( CodeGen.CODE._compunits.containsKey(name) ) + return Arrays.binarySearch(units,name)>=0; + int slash=name.lastIndexOf('/'); + if( slash<0 ) return false; + name=name.substring(0,slash); + } + } + static void read_public_strs(ElfReader elf) { BAOS bais = elf._bais; Type.TAGOFFS(); diff --git a/chapter28b/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/chapter28b/src/main/java/com/seaofnodes/simple/node/CallEndNode.java index 36a012e1..fc6effdc 100644 --- a/chapter28b/src/main/java/com/seaofnodes/simple/node/CallEndNode.java +++ b/chapter28b/src/main/java/com/seaofnodes/simple/node/CallEndNode.java @@ -127,7 +127,7 @@ public class CallEndNode extends CFGNode implements MultiNode { // A linked function for every concrete function TypeTuple state = hasExtern - ? TypeTuple.make(Type.CONTROL,TypeMem.BOT,tfp.ret()) + ? TypeTuple.make(Type.CONTROL,call.exposed(this),tfp.ret()) : TypeTuple.STATE.dual(); for( int i=1; i strs, HashMap types, IdentityHashMap anodes ) { @@ -67,7 +67,25 @@ public class CallNode extends CFGNode { public TypeFunPtr tfp() { return (TypeFunPtr)fptr()._type; } // Call is to an externally supplied code - public boolean external() { return false; } + public boolean external() { + if( !(fptr()._type instanceof TypeFunPtr tfp) ) return false; + if( XInt.isHigh(tfp.fidxs()) ) return true; + for( int fidx=XInt.next(tfp.fidxs(),-1); fidx>=0; fidx=XInt.next(tfp.fidxs(),fidx) ) + if( CodeGen.CODE.externFunc(fidx)!=null ) return true; + return false; + } + + // As in the escape chapter, native code may retain an argument even if + // this path never returns. StopCU reads this independently of CallEnd. + TypeMem exposed(Node dep) { + dep.addDepForwards(this); + dep.addDepForwards(fptr()); + if( _type!=Type.CONTROL || !external() ) return TypeMem.START; + Type input=dep.addDepForwards(mem())._type; + TypeMem escaped=input instanceof TypeMem m ? m : input.isHigh() ? TypeMem.TOP : TypeMem.BOT; + for( int i=2; i implements Cloneable { n._outputs = new Ary<>(Node.class); n._deps = null; n._hash = 0; + if( n instanceof CallNode call ) CODE._calls.add(call); return n; } diff --git a/chapter28b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter28b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java index 21d60d53..694d5dcb 100644 --- a/chapter28b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ b/chapter28b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -39,7 +39,12 @@ public class ScopeNode extends MemMergeNode { public final TypeStruct _self; } // Function scope - public static class Func extends Kind { public Func(String name) { _name=name;} public final String _name; } + public static class Func extends Kind { + public final String _name; + public final boolean _constructor; + public Func(String name) { this(name,false); } + public Func(String name, boolean constructor) { _name=name; _constructor=constructor; } + } public int _lexSize; // Number of Vars in this scope } @@ -184,10 +189,7 @@ public class ScopeNode extends MemMergeNode { } // Which lexical scope contains this Var? - public Kind kind( Var v ) { - int kx = kindx(v); - return kx == -1 ? null : _kinds.at(kx); - } + public int kindFcnx( Var v ) { for( int i=depth()-1; i>=0; i-- ) @@ -202,6 +204,21 @@ public class ScopeNode extends MemMergeNode { return kx == -1 ? null : _kinds.at(kx); } + // Final fields are writable local values inside their own initializer or + // constructor. Final parameters, local variables, and captured bindings + // remain final. Use the same rule when deciding which loop Phis to build. + public boolean canInit(Var v) { + int fx=enclosingFuncOrDecl(); + Kind.Func fun=(Kind.Func)_kinds.at(fx); + if( !fun._constructor || v._extern!=0 ) return false; + int dx=FunNode.isInstance(fun._name) ? fx : fx-1; + return dx>=0 && kindx(v)==dx && v._idx>=_kinds.at(dx)._lexSize+2; + } + + private boolean loopVar(Var v) { + return !v._final || (canInit(v) && !v.type().isHighOrConst()); + } + // Find name in reverse, return an index into _vars or -1. Linear scan // instead of hashtable, but probably doesn't matter until the scan // typically hits many dozens of variables. @@ -346,7 +363,7 @@ public class ScopeNode extends MemMergeNode { for( int i=2; i=2 ) + xmem=publicBottom(xmem.escapes(addDepForwards(p)._type)); if( tret.ctl().isHigh() ) continue; // Return not reachable // Capture (precisely?) all escaping pointer aliases and fidxs. // Escaped fidxs means the linked world can call that function; @@ -88,6 +93,10 @@ public class StopCUNode extends StopNode { xmem = (TypeMem)xmem.meet(publicBottom(tret.mem(),xmem)); } } + // Native retention is shared by the whole program, including calls + // on paths with no Return. A class boundary must not hide it. + for( CallNode call : CodeGen.CODE._calls ) + if( !call.isDead() ) xmem=publicBottom(xmem.escapes(call.exposed(this))); assert xmem._alias==1 && !xmem._one; return TypeTuple.make(Type.CONTROL,xmem,Type.BOTTOM); } diff --git a/chapter28b/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter28b/src/main/java/com/seaofnodes/simple/node/StoreNode.java index cc958348..5fa0b99f 100644 --- a/chapter28b/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter28b/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -184,8 +184,12 @@ public class StoreNode extends MemOpNode { return this; } + // Private constructor memory describes the value that the next helper + // reads. Keep its conversion, even if a machine store could truncate it. + // This is the same rule as in the construction and escape chapters. + boolean priv = mem()._type instanceof TypeMem tm && tm._one; // Value is automatically truncated by narrow store - if( _size!=0 && val() instanceof AndNode and && and.in(2)._type.isConstant() ) { + if( !priv && _size!=0 && val() instanceof AndNode and && and.in(2)._type.isConstant() ) { int log = log_size(); if( log<3 ) { // And-mask vs narrow store long mask = ((TypeInteger)and.in(2)._type).value(); @@ -198,7 +202,7 @@ public class StoreNode extends MemOpNode { } // Store will chop high order bits off; math to change those bits can be dropped. - if( _size!=0 && val() instanceof SarNode shr && + if( !priv && _size!=0 && val() instanceof SarNode shr && shr.in(1) instanceof ShlNode shl && shr.in(2)._type.isConstant() && shl.in(2)._type.isConstant() ) { @@ -253,7 +257,7 @@ public class StoreNode extends MemOpNode { return switch( use ) { case MemOpNode mem -> mem._alias; case MemPhiNode phi -> phi._alias; - case EscapeNode esc -> throw Utils.TODO("Untested"); // esc.pub()==this ? esc.fld()._alias : 0; + case EscapeNode esc -> esc.pub()==this ? esc.fld()._alias : 0; default -> 0; }; } -- cgit v1.2.3