summaryrefslogtreecommitdiff
path: root/chapter-escape/src/main/java/com/seaofnodes
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-10-24 09:38:56 -0700
committerCliff Click <cliffc@acm.org>2026-10-24 09:38:56 -0700
commitac525dc59de1ae195c40f8e21b09485d0e5d9330 (patch)
treed1af8c53dbcdf64526d0d5b5d49c8fdc53489af1 /chapter-escape/src/main/java/com/seaofnodes
parent2a7ed6bda01f028cf13f803d89b10908d7ea7937 (diff)
downloadSimple-main.tar.gz
Simple-main.zip
module splitmain
Diffstat (limited to 'chapter-escape/src/main/java/com/seaofnodes')
-rw-r--r--chapter-escape/src/main/java/com/seaofnodes/simple/Parser.java48
-rw-r--r--chapter-escape/src/main/java/com/seaofnodes/simple/node/ScopeNode.java27
-rw-r--r--chapter-escape/src/main/java/com/seaofnodes/simple/type/Field.java6
3 files changed, 67 insertions, 14 deletions
diff --git a/chapter-escape/src/main/java/com/seaofnodes/simple/Parser.java b/chapter-escape/src/main/java/com/seaofnodes/simple/Parser.java
index f22b73f1..19dd1c29 100644
--- a/chapter-escape/src/main/java/com/seaofnodes/simple/Parser.java
+++ b/chapter-escape/src/main/java/com/seaofnodes/simple/Parser.java
@@ -267,7 +267,7 @@ public class Parser {
// Pre-call the function from Start, with worse-case arguments. This
// represents all the future, yet-to-be-parsed functions calls and
// external calls.
- _scope.push(new Kind.Func());
+ _scope.push(new Kind.Func(_privateFun!=null));
ctrl(fun); // Scope control from function
// Unknown callers supply whole memory; field aliases are discovered lazily.
_scope.mem(new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,new ProjNode(_code._start,1,ScopeNode.MEM0).peephole()).peephole());
@@ -768,6 +768,7 @@ public class Parser {
// The alloc() call added the allocation scope
!_scope.canInit(def) )
throw error("Cannot reassign final '"+name+"'");
+ checkSharedField(def);
// Parse assignment expression
checkCaptureWrite(def);
@@ -869,6 +870,14 @@ public class Parser {
throw error("a not-null/non-zero expression");
t = expr._type;
if( !xfinal ) t = inferredType(t); // Widen if not final
+ // A final field is still a local variable during construction.
+ // Its constant default is a value, not a one-value declaration
+ // type: later assignments and loop Phis may change it.
+ else if( _scope.inConstructor() && !(t instanceof TypeFunPtr) ) {
+ Type wide=inferredType(t);
+ t = t instanceof TypeMemPtr ptr && ptr.notNull()
+ ? TypeMemPtr.make((byte)2,((TypeMemPtr)wide)._obj,false).withAccess(ptr._ro) : wide;
+ }
}
// expr is a constant function
if( t instanceof TypeFunPtr && expr._type instanceof TypeFunPtr tfp && tfp.isConstant() ) {
@@ -948,7 +957,10 @@ public class Parser {
Var v = _scope.var(index++);
if( v.isFRef() ) continue;
Node value = _scope.in(v);
- boolean constant = v._final && v._init && value._type.isConstant();
+ // Only function declarations are shared across instances. A final
+ // data field can change in a named or inline constructor, so a
+ // constant default does not justify folding every future read.
+ boolean constant = v._final && v._init && value._type instanceof TypeFunPtr && value._type.isConstant();
Field field = Field.make(v._name,v.type(),_code.getALIAS(),v._final,constant);
shape=shape.add(field);
TYPES.put(_nestedType,TypeMemPtr.make(shape));
@@ -1452,8 +1464,9 @@ public class Parser {
if( name!=null ) {
Var n = _scope.lookup(name);
if( n != null && !(n.type() instanceof TypeMemPtr) ) {
- if( n._final )
+ if( n._final && !_scope.canInit(n) )
throw error("Cannot reassign final '"+n._name+"'");
+ checkSharedField(n);
Node expr = liftExpr(peep(new AddNode(_scope.in(n),con(delta))),n.type(),true);
_scope.update(n,expr);
return expr;
@@ -1545,8 +1558,9 @@ public class Parser {
if( ch==0 ) // Normal primary, check for postfix updates
return parsePostfix(rvalue);
// Assign-update direct into Scope
- if( var._final )
+ if( var._final && !_scope.canInit(var) )
throw error("Cannot reassign final '"+var._name+"'");
+ checkSharedField(var);
checkCaptureWrite(var);
Node op = opAssign(ch,rvalue, var.type() );
_scope.update(var,op);
@@ -1600,6 +1614,24 @@ public class Parser {
throw error("Cannot assign captured variable '"+var._name+"' in an initializer");
}
+ // Inline constructors have local field slots too, but shared function
+ // declarations have no instance slot to publish. Do not silently discard
+ // an assignment to one of those declarations.
+ private void checkSharedField(Var var) {
+ if( !_scope.canInit(var) ) return;
+ if( _scope.kind(var) instanceof Kind.Define ) {
+ checkPrivateWrite(var._name);
+ return;
+ }
+ for( int i=_scope.depth()-1; i>0; i-- )
+ if( _scope._kinds.at(i) instanceof Kind.Alloc alloc ) {
+ Field field=alloc._tmp._obj.field(var._name);
+ if( field!=null && field._one )
+ throw error("Cannot reassign shared function '"+var._name+"'");
+ return;
+ }
+ }
+
// Check for assign-update, "x += e0".
// Returns "x + e0"; the caller assigns value.
// if ch is postfix, then lhs is kept and caller will unkeep and return it.
@@ -1980,6 +2012,7 @@ public class Parser {
if( v.type() instanceof TypeFunPtr ctor && ctor.ret() instanceof TypeMem )
throw error("A constructor is only called by new");
if( matchOpx('=','=') ) {
+ checkPrivateWrite(name);
Node value=parseAsgn().keep();
checkPublication(value);
_scope.update(v,liftExpr(value,v.type(),false));
@@ -1990,6 +2023,7 @@ public class Parser {
throw error("Cannot read uninitialized field '"+name+"'");
char ch=_lexer.matchOperAssign();
if( ch!=0 ) {
+ checkPrivateWrite(name);
Node op=opAssign(ch,value,v.type());
checkPublication(op);
_scope.update(v,op);
@@ -2000,6 +2034,12 @@ public class Parser {
return parsePostfix(value);
}
+ private void checkPrivateWrite(String name) {
+ Field field=((TypeMemPtr)TYPES.get(_nestedType))._obj.field(name);
+ if( field!=null && field._one )
+ throw error("Cannot reassign shared function '"+name+"'");
+ }
+
// zero/sign extend. "i" is limited to either classic unsigned (min==0) or
// classic signed (min=minus-power-of-2); max=power-of-2-minus-1.
private Node zsMask(Node val, Type t ) {
diff --git a/chapter-escape/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter-escape/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
index 3c1e9776..f65a822d 100644
--- a/chapter-escape/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
+++ b/chapter-escape/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
@@ -39,7 +39,10 @@ public class ScopeNode extends Node {
// Basic block scoping
public static class Block extends Kind { }
// Function scope
- public static class Func extends Kind { }
+ public static class Func extends Kind {
+ public final boolean _constructor;
+ public Func(boolean constructor) { _constructor=constructor; }
+ }
// Struct definition, mapping types to fields
public static class Define extends Kind { public Define(TypeMemPtr tmp) { _tmp=tmp; } public final TypeMemPtr _tmp; }
// Struct allocation, mapping values to fields
@@ -137,15 +140,25 @@ public class ScopeNode extends Node {
}
- // Allocation fields live in the scope immediately outside the constructor
- // block. Nested blocks retain that permission; surrounding bindings do not.
+ // Fields are writable local values until construction ends, even when the
+ // published field is final. Ordinary methods and final local variables do
+ // not inherit that permission. This also decides which loop Phis we need.
public boolean canInit(Var v) {
- for( int i=_kinds._len-1; i>0; i-- )
- if( _kinds.at(i) instanceof Kind.Alloc )
+ for( int i=_kinds._len-1; i>=0; i-- ) {
+ Kind k=_kinds.at(i);
+ if( k instanceof Kind.Alloc )
return v._idx >= _kinds.at(i-1)._lexSize && v._idx < _kinds.at(i)._lexSize;
+ if( k instanceof Kind.Define ) return kind(v)==k;
+ if( k instanceof Kind.Func fun )
+ return fun._constructor && i>0 && _kinds.at(i-1) instanceof Kind.Define def && kind(v)==def;
+ }
return false;
}
+ private boolean loopVar(Var v) {
+ return !v._final || (canInit(v) && !v.type().isHighOrConst());
+ }
+
public boolean inConstructor() { return _kinds.last() instanceof Kind.Define; }
public boolean inAllocation () { return _kinds.last() instanceof Kind.Alloc ; }
public boolean inFunction () { return _kinds.last() instanceof Kind.Func ; }
@@ -294,7 +307,7 @@ public class ScopeNode extends Node {
// Memory is one binding, with the same lazy loop Phi as scalar names.
for( int i=1; i<nIns(); i++ )
- dup.addDef(loop && !_vars.at(i)._final ? this : in(i));
+ dup.addDef(loop && loopVar(var(i)) ? this : in(i));
return dup;
}
@@ -356,7 +369,7 @@ public class ScopeNode extends Node {
// Fill in the backedge of any inserted Phis
void _endLoop( ScopeNode scope, Node back, Node exit ) {
for( int i=1; i<nIns(); i++ ) {
- if( _vars.at(i)._final ) continue; // Final vars did not get modified in the loop
+ if( !loopVar(var(i)) ) continue;
if( _vars.at(i).type().isHighOrConst() ) continue; // Cannot lift higher than a constant, so no Phi
if( back.in(i) != scope ) {
PhiNode phi = (PhiNode)in(i);
diff --git a/chapter-escape/src/main/java/com/seaofnodes/simple/type/Field.java b/chapter-escape/src/main/java/com/seaofnodes/simple/type/Field.java
index 96e6d766..9bd85ba6 100644
--- a/chapter-escape/src/main/java/com/seaofnodes/simple/type/Field.java
+++ b/chapter-escape/src/main/java/com/seaofnodes/simple/type/Field.java
@@ -17,10 +17,10 @@ public class Field extends Type {
public Type _t;
// Unique memory alias, not sensibly part of a "type" but very convenient here.
public int _alias;
- // Field must be written to exactly once, no more, no less
+ // Field is immutable after construction; constructor locals may change
public boolean _final;
- // Field is final in the declaration; value is the same for all instances
- // and will be moved to the class object
+ // Shared function declaration, with the same value for every instance.
+ // A final data field is still per-instance, even with a constant default.
public boolean _one;
private static final Ary<Field> FREE = new Ary<>(Field.class);