summaryrefslogtreecommitdiff
path: root/chapter28b/src/main/java/com/seaofnodes
diff options
context:
space:
mode:
Diffstat (limited to 'chapter28b/src/main/java/com/seaofnodes')
-rw-r--r--chapter28b/src/main/java/com/seaofnodes/simple/Parser.java185
-rw-r--r--chapter28b/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java4
-rw-r--r--chapter28b/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java3
-rw-r--r--chapter28b/src/main/java/com/seaofnodes/simple/codegen/Opto.java3
-rw-r--r--chapter28b/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java13
-rw-r--r--chapter28b/src/main/java/com/seaofnodes/simple/codegen/Serialize.java19
-rw-r--r--chapter28b/src/main/java/com/seaofnodes/simple/node/CallEndNode.java2
-rw-r--r--chapter28b/src/main/java/com/seaofnodes/simple/node/CallNode.java22
-rw-r--r--chapter28b/src/main/java/com/seaofnodes/simple/node/Node.java1
-rw-r--r--chapter28b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java31
-rw-r--r--chapter28b/src/main/java/com/seaofnodes/simple/node/StopCUNode.java9
-rw-r--r--chapter28b/src/main/java/com/seaofnodes/simple/node/StoreNode.java10
12 files changed, 250 insertions, 52 deletions
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(".<clinit>")));
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("<ctor>",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<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));
@@ -2283,6 +2408,7 @@ public class Parser {
}
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();
@@ -2782,6 +2908,7 @@ public class Parser {
_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);
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)+".<clinit>";
+ return Parser.addClzPrefix(_srcName==null ? "Test" : _srcName.replace('/','.'))+".<clinit>";
}
// Verbose printing during compilation
@@ -463,6 +463,8 @@ public class CodeGen {
// ---------------------------
// Parser object
+ public final Ary<CallNode> _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: <new> and <ctor>
+ // 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<nIns(); i++ )
state = (TypeTuple)state.meet(in(i)._type);
diff --git a/chapter28b/src/main/java/com/seaofnodes/simple/node/CallNode.java b/chapter28b/src/main/java/com/seaofnodes/simple/node/CallNode.java
index 71f6d146..bcb756fa 100644
--- a/chapter28b/src/main/java/com/seaofnodes/simple/node/CallNode.java
+++ b/chapter28b/src/main/java/com/seaofnodes/simple/node/CallNode.java
@@ -22,7 +22,7 @@ public class CallNode extends CFGNode {
// Source location for late reported errors
public final Parser.Lexer _loc;
- public CallNode(Parser.Lexer loc, Node... nodes) { super(nodes); _loc=loc; }
+ public CallNode(Parser.Lexer loc, Node... nodes) { super(nodes); _loc=loc; CodeGen.CODE._calls.add(this); }
public CallNode(CallNode call) { super(call); _loc = call._loc; }
@Override public Tag serialTag() { return Tag.Call; }
public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> 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<nIns()-1; i++ ) escaped=escaped.escapes(dep.addDepForwards(arg(i))._type);
+ return TypeMem.make(1,Type.BOTTOM,false,false,false,escaped._escFs,escaped._escAs);
+ }
// Find the Call End from the Call
diff --git a/chapter28b/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter28b/src/main/java/com/seaofnodes/simple/node/Node.java
index 77b43ba8..8718f351 100644
--- a/chapter28b/src/main/java/com/seaofnodes/simple/node/Node.java
+++ b/chapter28b/src/main/java/com/seaofnodes/simple/node/Node.java
@@ -715,6 +715,7 @@ public abstract class Node extends BaseNode<Node> 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<nIns(); i++ )
// For lazy phis on loops we use a sentinel
// that will trigger phi creation on update
- dup.addDef(loop && !var(i)._final ? this : in(i));
+ dup.addDef(loop && loopVar(var(i)) ? this : in(i));
return dup;
}
@@ -431,7 +448,7 @@ public class ScopeNode extends MemMergeNode {
// Fill in the backedge of any inserted Phis
void _endLoop( ScopeNode scope, Node back, Node exit ) {
for( int i=1; i<nIns(); i++ ) {
- if( var(i)._final ) continue; // Final vars did not get modified in the loop
+ if( !loopVar(var(i)) ) continue;
Node n = in(i);
if( var(i).type().isHighOrConst() && !(n instanceof PhiNode phi && phi.inProgress()) ) { // Cannot lift higher than a constant, so no Phi
Node con = ConstantNode.make(var(i).type()).init();
diff --git a/chapter28b/src/main/java/com/seaofnodes/simple/node/StopCUNode.java b/chapter28b/src/main/java/com/seaofnodes/simple/node/StopCUNode.java
index d3a356ed..43acf631 100644
--- a/chapter28b/src/main/java/com/seaofnodes/simple/node/StopCUNode.java
+++ b/chapter28b/src/main/java/com/seaofnodes/simple/node/StopCUNode.java
@@ -79,6 +79,11 @@ public class StopCUNode extends StopNode {
// here as its effects are not visible to the outside world.
addDepForwards(start); // Check again if function escapes
if( pub || start.escapedFIDX(sig.fidx()) ) {
+ // External callers already hold their object arguments, so
+ // writes through those arguments expose addresses too.
+ for( Node use : fun._outputs )
+ if( use instanceof ParmNode p && p._idx>=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;
};
}