summaryrefslogtreecommitdiff
path: root/chapter23/src/main/java
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-10-03 14:03:48 -0700
committerCliff Click <cliffc@acm.org>2026-10-03 14:03:48 -0700
commit22df891893d30e9906d0b14fc3ae5d8e67d500b2 (patch)
tree2bda142f92febc8b26f1cabd4bb0c46d1864f34b /chapter23/src/main/java
parentc3958e34c0de21df52e57e47e4f47ffe6464f138 (diff)
downloadSimple-22df891893d30e9906d0b14fc3ae5d8e67d500b2.tar.gz
Simple-22df891893d30e9906d0b14fc3ae5d8e67d500b2.zip
Chapter renumber, and split ch 17
Renumber chapters 10a, 10b, 11, 12, 13, 14 -> 10, 11, 13, 12, [11,14]. Split ch 17 into a 17a and 17b; shuffle around im/mutable syntax to be consistent, and present it more slowly. THe new immutable syntax is pushed through all later chapters
Diffstat (limited to 'chapter23/src/main/java')
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/Parser.java130
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/Var.java1
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/CallNode.java2
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/CastNode.java2
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/LoadNode.java4
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java4
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/PhiNode.java4
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/ScopeNode.java9
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/StoreNode.java2
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/type/Field.java2
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/type/Type.java4
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java61
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/type/TypeStruct.java5
13 files changed, 151 insertions, 79 deletions
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/Parser.java b/chapter23/src/main/java/com/seaofnodes/simple/Parser.java
index 2dc83905..fa6568ab 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/Parser.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/Parser.java
@@ -189,6 +189,10 @@ public class Parser {
* 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) {
// If this is a method, record & restore the existing struct fields,
// which are also the struct initializers. The method might update
// mutable fields, so when the function parse is done reset back to
@@ -232,7 +236,7 @@ public class Parser {
// All args, "as-if" called externally
for( int i=0; i<ids.length; i++ ) {
Type t = sig.arg(i);
- _scope.define(ids[i], t, false, new ParmNode(ids[i],i+2,t,fun,con(t)).peephole(), loc);
+ _scope.define(ids[i], t, finals==null ? t instanceof TypeNil : finals.get(i), new ParmNode(ids[i],i+2,t,fun,con(t)).peephole(), loc);
}
// Parse the body
@@ -662,30 +666,31 @@ 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.inAllocation() && def._idx >= _scope._kinds.last(-1)._lexSize) )
+ !_scope.canInit(def) )
throw error("Cannot reassign final '"+name+"'");
// Parse assignment expression
Node expr = parseAsgn();
// Lift expression, based on type
- Node lift = liftExpr(expr.keep(), def.type(), def._final, true);
+ Node lift = liftExpr(expr.keep(), def.type(), true);
// Update
_scope.update(name,lift);
// Return un-lifted expr
return expr.unkeep();
}
- // Make finals deep; widen ints to floats; narrow wide int types.
+ // Apply declared access permissions; widen floats and narrow integers.
// Early error if types do not match variable.
- private Node liftExpr( Node expr, Type t, boolean xfinal, boolean isLoad ) {
+ private Node liftExpr( Node expr, Type t, boolean isLoad ) {
if( expr._type instanceof TypeMemPtr tmp && TYPES.get(tmp._obj._name).isFRef() )
throw error("Must define forward ref "+tmp._obj._name);
- // Final is deep on ptrs
- if( xfinal && t instanceof TypeMemPtr tmp ) {
- t = tmp.makeRO();
+ // Read-only access is deep; a fixed binding alone does not remove access.
+ if( t instanceof TypeMemPtr tmp && tmp.isFinal() ) {
expr = peep(new ReadOnlyNode(expr));
}
+ if( !expr._type.accessISA(t) )
+ throw error("Type " + expr._type.str() + " is not of declared type " + t.str());
// Auto-widen array to i64 (cast ptr to raw int bits)
if( t == TypeInteger.BOT && expr._type instanceof TypeMemPtr tmp && tmp._obj.isAry() )
expr = peep(new AddNode(peep(new CastNode(t,ctrl(),expr)),off(tmp._obj,"[]")));
@@ -699,7 +704,7 @@ public class Parser {
else if( et instanceof TypeInteger && t instanceof TypeInteger ) et=t;
// Type is sane
- if( et!=Type.BOTTOM && !et.shallowISA(t) )
+ if( et!=Type.BOTTOM && (!et.shallowISA(t) || !et.accessISA(t)) )
expr = peep(new CastNode(t,null,expr));
return expr;
}
@@ -730,43 +735,54 @@ public class Parser {
return require(n,";");
}
- /** Parse final: [!]var['=' asgn]
+ // Widen a reassignable binding without discarding the initializer's
+ // permissions. Lattice glb includes readonly memory and is too broad here.
+ private Type inferredType(Type t) {
+ if( t instanceof TypeMemPtr tmp ) {
+ if( tmp._obj.isAry() ) {
+ Type elem = tmp._obj._fields[1]._t;
+ if( elem instanceof TypeConAry )
+ elem = ((TypeMemPtr)TYPES.get(tmp._obj._name))._obj._fields[1]._t;
+ return typeAry(elem,tmp._obj._fields[1]._final).withAccess(tmp._ro).makeNullable();
+ }
+ Type base = TYPES.get(tmp._obj._name);
+ TypeMemPtr ptr = (TypeMemPtr)base;
+ return ptr.withAccess(tmp._ro).makeNullable();
+ }
+ return t.glb(false);
+ }
+
+ // 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 hasBang = match("!");
+ boolean xfinal = bindingFinal(t);
Lexer loc = loc();
String name = requireId();
// Optional initializing expression follows
- boolean xfinal = false;
- boolean fld_final = false; // Field is final, but not deeply final
+ boolean fld_final = false; // Fixed field independent of pointee access
+ boolean initialized = match("=");
Node expr;
- if( match("=") ) {
+ if( initialized ) {
expr = isExternDecl()
? externDecl(name,t)
: parseAsgn();
- // TOP means val and val is always final
- xfinal = (t==Type.TOP) ||
- expr instanceof ExternNode ||
- // BOTTOM is var and var is always not-final
- (t!=Type.BOTTOM &&
- // no Bang AND
- !hasBang &&
- // Locals are not-final by default
- !_scope.inFunction() &&
- // not-null (expecting null to be set to not-null)
- expr._type != Type.NIL &&
- // Pointers are final by default; int/flt are not-final by default.
- (t instanceof TypeNil));
-
// 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 = t.glb(false); // Widen if not final
+ if( !xfinal ) t = inferredType(t); // Widen if not final
}
// expr is a constant function
if( t instanceof TypeFunPtr && expr._type instanceof TypeFunPtr tfp && tfp.isConstant() ) {
@@ -785,23 +801,20 @@ public class Parser {
// Nullable pointers get a NIL; not-null get a TOP which
// signals that they *must* be initialized in the constructor.
case TypeNil tn -> tn.nullable() ? NIL : con(Type.TOP);
- case TypeInteger ti -> ZERO;
- case TypeFloat tf -> con(TypeFloat.FZERO);
+ case TypeInteger ti -> xfinal ? con(Type.TOP) : ZERO;
+ case TypeFloat tf -> con(xfinal ? Type.TOP : 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); }
};
// Nullable fields are set in the constructor, but remain shallow final.
// e.g. final pointer to a r/w array
- if( t instanceof TypeNil tn && !tn.nullable() && !hasBang )
+ if( t instanceof TypeNil tn && !tn.nullable() && xfinal )
fld_final = true;
}
// Lift expression, based on type
- Node lift = liftExpr(expr, t, xfinal, true);
- // Rebuild the deep RO 't', not returned from liftExpr
- if( xfinal && t instanceof TypeMemPtr tmp )
- t = tmp.makeRO();
+ Node lift = liftExpr(expr, t, true);
// Lift type to the declaration. This will report as an error later if
// we cannot lift the type.
@@ -810,6 +823,7 @@ public class Parser {
// Define a new name
if( !_scope.define(name,t,xfinal || fld_final,lift, loc) )
throw error("Redefining name '" + name + "'", loc);
+ _scope.lookup(name)._init = initialized;
return lift;
}
@@ -841,7 +855,7 @@ public class Parser {
while( nvar < _scope._vars._len ) {
Var v = _scope.var(nvar++);
if( !v.isFRef() ) {
- ts = ts.add( Field.make( v._name, v.type(), _code.getALIAS(), v._final, v._final && _scope.in( v._idx )._type != Type.TOP ) );
+ ts = ts.add( Field.make( v._name, v.type(), _code.getALIAS(), v._final, v._final && v._init && _scope.in( v._idx )._type != Type.TOP ) );
TYPES.put( typeName, TypeMemPtr.make( ts ) );
}
}
@@ -885,12 +899,14 @@ public class Parser {
// t = int|i8|i16|i32|i64|u8|u16|u32|u64|byte|bool | flt|f32|f64 | val | var | struct[?]
private Type type() {
int old1 = pos();
+ boolean writable = match("!");
+ boolean readonly = !writable && match("~");
// Only type with a leading `{` is a function pointer...
if( peek('{') ) return typeFunPtr();
// Otherwise you get a type name
String tname = _lexer.matchId();
- if( tname==null ) return null;
+ if( tname==null ) return posT(old1);
// Convert the type name to a type.
Type t0 = TYPES.get(tname);
@@ -913,6 +929,13 @@ public class Parser {
// Still no type found? Assume forward reference
Type t1 = t0 == null ? TypeMemPtr.make(TypeStruct.open(tname)) :t0; // Null: assume a forward ref type
+ // 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 ) {
@@ -922,7 +945,7 @@ public class Parser {
if( tmp.nullable() ) throw error("Type "+t2+" already allows null");
t2 = tmp.makeNullable();
} else if( match("[~]") ) {
- t2 = typeAry(t2,true);
+ t2 = typeAry(t2,true).makeRO();
} else if( match("[]") ) {
t2 = typeAry(t2,false);
} else
@@ -934,13 +957,13 @@ public class Parser {
// 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();
- match("!");
+ if( !match("!") ) match("~");
String id = _lexer.matchId();
if( !(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
- TYPES.put(tname,t1);
+ TYPES.put(tname,((TypeMemPtr)t1).withAccess(false));
// Return the (array, final) type
return t2;
}
@@ -954,10 +977,9 @@ public class Parser {
if( ta==null )
// Remember final version
TYPES.put(tname, ta = TypeMemPtr.make( TypeStruct.makeAry(tname,TypeInteger.U32,_code.getALIAS(),t,_code.getALIAS(),true) ));
- Field elem = ta._obj.field("[]");
- if( elem._final == efinal )
- return ta;
- return TypeMemPtr.make( ta._obj.replace(elem.makeFrom(efinal)));
+ // Reuse aliases, but preserve this occurrence's element permissions.
+ TypeStruct ary = ta._obj;
+ return TypeMemPtr.make(TypeStruct.makeAry(tname,TypeInteger.U32,ary._fields[0]._alias,t,ary._fields[1]._alias,efinal));
}
// A function type is `{ type... -> type }` or `{ type }`.
@@ -1197,7 +1219,8 @@ public class Parser {
Type t = type();
if( t!=null ) {
if( peek('.') )
- return parsePostfix(con(t));
+ // A namespace lookup is not a reference declaration.
+ return parsePostfix(con(TYPES.get(((TypeMemPtr)t)._obj._name)));
//pos(pos); // TODO: not a `type.fld`
//return null;
throw Utils.TODO();
@@ -1307,7 +1330,7 @@ public class Parser {
};
// Convert to float ops, or narrow int types; error if not declared type.
// Also, if postfix LHS is still keep()
- return liftExpr(peep(op.widen()),t,false,true);
+ return liftExpr(peep(op.widen()),t,true);
}
@@ -1423,7 +1446,7 @@ public class Parser {
// array body.
TypeMemPtr str = TypeMemPtr.make((byte)2,TypeStruct.makeAry("[]u8", TypeInteger.constant(s.length()), lenAlias, con, elemAlias, true),true);
assert str.isConstant();
- return con(str);
+ return con(str.makeRO());
}
// Memory is one hidden SSA variable, including across branches and loops.
@@ -1431,6 +1454,8 @@ public class Parser {
private void mem(Node n) { _scope.mem(n); }
private void store(String name, int alias, Type glb, Node ptr, Node off, Node val, boolean init, Node ctrl) {
+ if( !init && ptr._type instanceof TypeMemPtr view && view._ro && ctrl()._type!=Type.XCONTROL )
+ throw error("Cannot modify final field '"+name+"'");
Node prior = mem().keep();
Node st = new StoreNode(loc(),name,alias,glb,prior,ptr,off,val,init);
st.setDef(0,ctrl);
@@ -1483,7 +1508,8 @@ public class Parser {
// Find the field from the Type. Lookup in the base object field names.
TypeMemPtr ptr2 = (TypeMemPtr)TYPES.get(ptr._obj._name);
- ptr = (TypeMemPtr)ptr.join(ptr2);// Upgrade to latest TYPES
+ if( !ptr._obj.isAry() )
+ ptr = ptr.makeFrom((TypeStruct)ptr._obj.join(ptr2._obj)); // Resolve the shape, retaining access.
TypeStruct base = ptr._obj;
int fidx = base.find(name);
if( fidx == -1 )
@@ -1506,7 +1532,7 @@ public class Parser {
// Disambiguate "obj.fld==x" boolean test from "obj.fld=x" field assignment
if( matchOpx('=','=') ) {
Node val = parseAsgn().keep();
- Node lift = liftExpr( val, tf, f._final, false );
+ Node lift = liftExpr( val, tf, false );
store(name,f._alias,tf,expr.unkeep(),off.unkeep(),lift,false,base.isAry() ? ctrl() : null);
return val.unkeep(); // "obj.a = expr" returns the expression while updating memory
@@ -1583,6 +1609,7 @@ public class Parser {
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();
// Defined in constructor? Add `self` argument. "static" calls still
// add a `self` they just ignore it.
@@ -1599,6 +1626,7 @@ public class Parser {
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);
@@ -1607,7 +1635,7 @@ public class Parser {
require("->");
// Make a concrete function type, with a fidx
TypeFunPtr tfp = _code.makeFun(TypeFunPtr.make(false,false,ts.asAry(),Type.BOTTOM));
- ReturnNode ret = parseFunctionBody(tfp,loc,ids.asAry());
+ ReturnNode ret = parseFunctionBody(tfp,loc,finals,ids.asAry());
return new FunPtrNode(ret._fun.sig(),ret).peephole();
}
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/Var.java b/chapter23/src/main/java/com/seaofnodes/simple/Var.java
index 14295395..e13b85c5 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/Var.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/Var.java
@@ -13,6 +13,7 @@ public class Var {
// promotions.
public int _idx; // index in containing scope
private Type _type; // Declared type
+ public boolean _init; // Explicit declaration initializer
public boolean _final; // Final field
public boolean _fref; // Forward ref
public Parser.Lexer _loc; // Source location
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/CallNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/CallNode.java
index 215e3c78..f66d1f71 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/CallNode.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/CallNode.java
@@ -162,7 +162,7 @@ public class CallNode extends CFGNode {
// Check for args
for( int i=0; i<tfp.nargs(); i++ )
- if( !arg(i+2)._type.isa(tfp.arg(i)) ) {
+ if( !arg(i+2)._type.isa(tfp.arg(i)) || !arg(i+2)._type.accessISA(tfp.arg(i)) ) {
cend().addDep(arg(i+2)); // Upgrading arg type may allow inlining
return Parser.error( "Argument #" + i + " isa " + arg(i+2)._type + ", but must be a " + tfp.arg(i), _loc );
}
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/CastNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/CastNode.java
index 08d72e35..4e93f3e6 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/CastNode.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/CastNode.java
@@ -49,7 +49,7 @@ public class CastNode extends Node {
@Override
public Node idealize() {
- return in(1)._type.isa(_t) ? in(1) : null;
+ return in(1)._type.isa(_t) && in(1)._type.accessISA(_t) ? in(1) : null;
}
@Override
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/LoadNode.java
index 31844741..0a9dae59 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/LoadNode.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/LoadNode.java
@@ -64,7 +64,7 @@ public class LoadNode extends MemOpNode {
}
// Lift from declared type and memory input
t = t.join(_declaredType).join(MemMergeNode.contents(mem(),_alias,this));
- if( _declaredType.isFinal() )
+ if( ptr()._type.isFinal() || _declaredType.isFinal() )
t = t.makeRO(); // Deep final applied
return t;
}
@@ -227,7 +227,7 @@ public class LoadNode extends MemOpNode {
// Read-Only is a deep property, and cannot be cast-away
private Node castRO(Node rez) {
- if( ptr()._type.isFinal() && !rez._type.isFinal() )
+ if( (ptr()._type.isFinal() || _declaredType.isFinal()) && !rez._type.isFinal() )
return new ReadOnlyNode(rez).peephole();
return rez;
}
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
index ad173bc3..8d924b96 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
@@ -36,7 +36,9 @@ public class MemMergeNode extends Node {
// join it with the incoming contents for all previously allocated objects.
// Phis use their cached types, so this query does not recurse around loops.
static Type contents(Node mem, int alias, Node dep) {
- dep.addDep(mem);
+ // A loop memory Phi can also be a user of the querying Store.
+ // Its type changing still needs to revisit this query.
+ dep.addDepForwards(mem);
if( mem instanceof CastNode cast ) return contents(cast.in(1),alias,dep);
if( mem instanceof MemMergeNode merge )
return contents(merge.alias(alias),alias,dep);
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/PhiNode.java
index 88d1a7ef..af5d900b 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/PhiNode.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/PhiNode.java
@@ -80,8 +80,8 @@ public class PhiNode extends Node {
// Can upgrade minType even while in-progress
if( _minType instanceof TypeMemPtr tmp && _minType.isFRef() ) {
TypeMemPtr tmp2 = (TypeMemPtr)CodeGen.CODE.P.TYPES.get(tmp._obj._name);
- if( tmp2!=null && tmp2 != _minType ) {
- _minType = tmp2;
+ if( tmp2!=null && tmp2._obj != tmp._obj ) {
+ _minType = tmp.makeFrom(tmp2._obj);
return this;
}
}
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
index cd5ab6ff..307fc0f9 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
@@ -137,6 +137,15 @@ 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.
+ public boolean canInit(Var v) {
+ for( int i=_kinds._len-1; i>0; i-- )
+ if( _kinds.at(i) instanceof Kind.Alloc )
+ return v._idx >= _kinds.at(i-1)._lexSize && v._idx < _kinds.at(i)._lexSize;
+ return false;
+ }
+
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 ; }
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/StoreNode.java
index def61478..5d3e10e5 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/StoreNode.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/StoreNode.java
@@ -127,7 +127,7 @@ public class StoreNode extends MemOpNode {
if( ptr()._type == Type.TOP )
return null; // Dead store
TypeMemPtr tmp = (TypeMemPtr)ptr()._type;
- if( tmp._obj.field(_name)._final && !_init )
+ if( (tmp._ro || tmp._obj.field(_name)._final) && !_init )
return Parser.error("Cannot modify final field '"+_name+"'",_loc);
return null;
}
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/type/Field.java b/chapter23/src/main/java/com/seaofnodes/simple/type/Field.java
index b2b2deca..1f22cb05 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/type/Field.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/type/Field.java
@@ -99,7 +99,7 @@ public class Field extends Type {
@Override boolean _isConstant() { return _t._isConstant(); }
@Override boolean _isFinal() { return _final && _t._isFinal(); }
- @Override Field _makeRO() { return _final ? this : make(_fname, _t._makeRO(),_alias,true,_one); }
+ @Override Field _makeRO() { return make(_fname, _t._makeRO(),_alias,true,_one); }
boolean isGLB2() { return _final && _t._isGLB(true); }
Field glb2() {
Type glb = _t._glb(true);
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/type/Type.java b/chapter23/src/main/java/com/seaofnodes/simple/type/Type.java
index fe0c33f3..3f115c3f 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/type/Type.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/type/Type.java
@@ -326,6 +326,10 @@ public class Type /*implements Cloneable*/ {
// Make a zero version of this type, 0 for integers and null for pointers.
public Type makeZero() { return Type.NIL; }
+ // Writable array slots are invariant in their element access permissions.
+ // Otherwise an alias could insert a readonly pointer into a writable slot.
+ public boolean accessISA(Type dst) { return true; }
+
// Is forward-reference
public boolean isFRef() { return false; }
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java b/chapter23/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java
index ffbc71da..ad744c8f 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java
@@ -26,11 +26,14 @@ public class TypeMemPtr extends TypeNil {
// (TOP ,true ) - a nil
public TypeStruct _obj;
+ // Deep read-only access through this pointer; independent of slot finality.
+ public boolean _ro;
+
public boolean _one; // Singleton instance
private static final Ary<TypeMemPtr> FREE = new Ary<>(TypeMemPtr.class);
private TypeMemPtr(byte nil, TypeStruct obj, boolean one) { super(TMEMPTR,nil); init(nil,obj,one); }
- private TypeMemPtr init(byte nil, TypeStruct obj, boolean one) { _nil=nil; _obj=obj; _one=one; return this; }
+ private TypeMemPtr init(byte nil, TypeStruct obj, boolean one) { _nil=nil; _obj=obj; _ro=obj==TypeStruct.BOT; _one=one; return this; }
// Return a filled-in TypeMemPtr; either from free list or alloc new.
private static TypeMemPtr malloc(byte nil, TypeStruct obj, boolean one) {
return FREE.isEmpty() ? new TypeMemPtr(nil,obj,one) : FREE.pop().init(nil,obj,one);
@@ -38,7 +41,10 @@ public class TypeMemPtr extends TypeNil {
// All fields directly listed
public static TypeMemPtr make(byte nil, TypeStruct obj, boolean one) {
- TypeMemPtr tmp = malloc(nil, obj, one);
+ return make(nil,obj,one,obj==TypeStruct.BOT);
+ }
+ private static TypeMemPtr make(byte nil, TypeStruct obj, boolean one, boolean ro) {
+ TypeMemPtr tmp = malloc(nil, obj, one).access(ro);
TypeMemPtr t2 = tmp.intern();
if( t2==tmp ) return tmp;
return VISIT.isEmpty() ? t2.free(tmp) : t2.delayFree(tmp);
@@ -58,9 +64,30 @@ public class TypeMemPtr extends TypeNil {
public static TypeMemPtr make (TypeStruct obj) { return make((byte)2, obj); }
public static TypeMemPtr makeNullable(TypeStruct obj) { return make((byte)3, obj); }
- public TypeMemPtr makeFrom(TypeStruct obj) { return obj==_obj ? this : make(_nil, obj, _one); }
+ public TypeMemPtr makeFrom(TypeStruct obj) { return obj==_obj ? this : make(_nil, obj, _one,_ro); }
public TypeMemPtr makeNullable() { return makeFrom((byte)3); }
- @Override TypeMemPtr makeFrom(byte nil) { return nil==_nil ? this : make(nil, _obj, _one); }
+ @Override TypeMemPtr makeFrom(byte nil) { return nil==_nil ? this : make(nil, _obj, _one,_ro); }
+
+ private TypeMemPtr access(boolean ro) { _ro=ro; return this; }
+
+ public TypeMemPtr withAccess(boolean ro) {
+ if( _ro==ro ) return this;
+ TypeMemPtr ptr = malloc(_nil, _obj, _one);
+ ptr._ro = ro;
+ TypeMemPtr res = ptr.intern();
+ if( res==ptr ) return ptr;
+ return VISIT.isEmpty() ? res.free(ptr) : res.delayFree(ptr);
+ }
+
+ // Writable slots can load and store references, so their element access
+ // must agree in both directions. A read-only outer view can weaken access.
+ @Override public boolean accessISA(Type dst) {
+ if( !(dst instanceof TypeMemPtr ptr) ) return true;
+ if( _ro && !ptr._ro ) return false;
+ if( ptr._ro || _obj._fields==null || ptr._obj._fields==null || !_obj.isAry() || !ptr._obj.isAry() ) return true;
+ Type a = _obj._fields[1]._t, b = ptr._obj._fields[1]._t;
+ return a.accessISA(b) && b.accessISA(a);
+ }
// An abstract pointer, pointing to either a Struct or an Array.
// Can also be null or not, so 4 choices {TOP,BOT} x {nil,not}
@@ -69,35 +96,35 @@ public class TypeMemPtr extends TypeNil {
public static final TypeMemPtr NOTBOT = make((byte)2,TypeStruct.BOT);
public static final TypeMemPtr TEST= make((byte)2, TypeStruct.TEST);
- public static void gather(ArrayList<Type> ts) { ts.add(NOTBOT); ts.add(BOT); ts.add(TEST); }
+ public static void gather(ArrayList<Type> ts) { ts.add(NOTBOT); ts.add(BOT); ts.add(TEST); ts.add(TEST.makeRO()); }
@Override
public TypeNil xmeet(Type t) {
TypeMemPtr that = (TypeMemPtr) t;
assert !isFree() && !that.isFree();
- return make(xmeet0(that), (TypeStruct)_obj.meet(that._obj), _one & that._one);
+ return make(xmeet0(that), (TypeStruct)_obj.meet(that._obj), _one & that._one,_ro | that._ro);
}
@Override
- TypeMemPtr xdual() { return malloc( dual0(), _obj.dual(), _one); }
+ TypeMemPtr xdual() { return malloc( dual0(), _obj.dual(), _one).access(!_ro); }
@Override TypeMemPtr rdual() {
if( _dual!=null ) return dual();
assert !_terned;
- TypeMemPtr d = malloc(dual0(), null, _one);
+ TypeMemPtr d = malloc(dual0(), null, _one).access(!_ro);
(_dual = d)._dual = this; // Cross link duals
d._obj = _obj._terned ? _obj.dual() : _obj.rdual();
return d;
}
// RHS is NIL; do not deep-dual when crossing the center line
- @Override Type meet0() { return _nil==3 ? this : make((byte)3,_obj); }
+ @Override Type meet0() { return _nil==3 ? this : make((byte)3,_obj,false,_ro); }
@Override boolean _isConstant() { return _one && _obj._isConstant(); }
- @Override boolean _isFinal() { return _obj._isFinal(); }
- @Override TypeMemPtr _makeRO() { return makeFrom(_obj._makeRO()); }
- @Override boolean _isGLB(boolean mem) { return _obj.isGLB2(); }
- @Override TypeMemPtr _glb(boolean mem) { return make((byte)3,_obj.glb2()); }
+ @Override boolean _isFinal() { return _ro; }
+ @Override TypeMemPtr _makeRO() { return withAccess(true); }
+ @Override boolean _isGLB(boolean mem) { return _ro && _nil==3 && !_one && _obj.isGLB2(); }
+ @Override TypeMemPtr _glb(boolean mem) { return make((byte)3,_obj.glb2(),false,true); }
@Override TypeMemPtr _close() { return makeFrom(_obj._close()); }
@@ -105,7 +132,7 @@ public class TypeMemPtr extends TypeNil {
@Override public boolean shallowISA( Type t ) {
if( !(t instanceof TypeMemPtr that) ) return false;
if( this==that ) return true;
- if( xmeet0(that)!=that._nil ) return false;
+ if( xmeet0(that)!=that._nil || (_ro && !that._ro) ) return false;
if( _obj==that._obj ) return true;
if( _obj._name.equals(that._obj._name) )
return true; // Shallow, do not follow matching names, just assume ok
@@ -117,16 +144,16 @@ public class TypeMemPtr extends TypeNil {
@Override public int log_size() { return 3; } // (1<<3)==8-byte pointers
- @Override int hash() { return _obj.hashCode() ^ super.hash() ^ (_one ? 2048 : 0); }
+ @Override int hash() { return (_ro ? 8192 : 0) ^ _obj.hashCode() ^ super.hash() ^ (_one ? 2048 : 0); }
@Override boolean eq(Type t) {
TypeMemPtr ptr = (TypeMemPtr)t; // Invariant
- return super.eq(ptr) && _one == ptr._one && _obj == ptr._obj;
+ return _ro==ptr._ro && super.eq(ptr) && _one == ptr._one && _obj == ptr._obj;
}
@Override boolean cycle_eq(Type t) {
if( t._type != TMEMPTR ) return false;
TypeMemPtr ptr = (TypeMemPtr)t; // Invariant
- return super.eq(ptr) && _one == ptr._one && _obj.cycle_eq(ptr._obj);
+ return _ro==ptr._ro && super.eq(ptr) && _one == ptr._one && _obj.cycle_eq(ptr._obj);
}
@Override int nkids() { return 1; }
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/type/TypeStruct.java b/chapter23/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
index dc796ffd..f065126a 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
@@ -87,9 +87,10 @@ public class TypeStruct extends Type {
return (TypeStruct)recurOpen()._close().recurClose();
}
@Override TypeStruct _close() {
- TypeStruct ts = (TypeStruct)VISIT.get(_name);
+ Object key = isAry() ? _uid : _name;
+ TypeStruct ts = (TypeStruct)VISIT.get(key);
if( ts!=null ) return ts;
- ts = recurPre(_name,false);
+ ts = recurPre(key,false);
Field[] flds = ts._fields;
// Now start the recursion