summaryrefslogtreecommitdiff
path: root/chapter25/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'chapter25/src/main')
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/Parser.java95
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/Serialize.java11
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/CallNode.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ConvertNode.java4
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java5
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/Type.java4
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java68
-rw-r--r--chapter25/src/main/smp/sys/aryi64.smp2
10 files changed, 125 insertions, 70 deletions
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/Parser.java b/chapter25/src/main/java/com/seaofnodes/simple/Parser.java
index 19894357..42c83f93 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/Parser.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/Parser.java
@@ -217,13 +217,17 @@ 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) {
// Record & restore the existing scope vars. The function parse might
// update fields, so when the parse is done reset back to the pre-parse
// state.
Node[] preParse = _scope.save();
// Parse function body normally
- ReturnNode ret = _parseFunctionBody(null, sig,loc,ids);
+ ReturnNode ret = _parseFunctionBody(null, sig,loc,finals,ids);
// Reset all fields to pre-parse days
_scope.restore(preParse);
@@ -246,6 +250,10 @@ public class Parser {
// Unstack the {break,continue,return} scopes.
private ReturnNode _parseFunctionBody( String funName, TypeFunPtr sig, Lexer loc, String[] ids) {
+ return _parseFunctionBody(funName, sig, loc, null, ids);
+ }
+
+ private ReturnNode _parseFunctionBody( String funName, TypeFunPtr sig, Lexer loc, java.util.BitSet finals, String[] ids) {
boolean isInit = FunNode.isInit(funName);
boolean isCtor = isInit || isExplicitConstructorName(funName);
String typeName = isCtor ? ((TypeMemPtr)sig._sig[0])._obj._name : null;
@@ -278,7 +286,7 @@ public class Parser {
for( int i=0; i<ids.length; i++ ) {
Type t = sig.arg(i);
// Args take a default input if called from Start/external-world
- _scope.define(ids[i], t, i==0 && ids[0]=="self", new ParmNode(ids[i],i+2,t,fun,ConstantNode.seed(t).peephole()).peephole(), loc);
+ _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);
}
// Parse the function body.
@@ -434,7 +442,7 @@ public class Parser {
// value; otherwise a user constructor must initialize it.
Node val = _returnScope.in(vidx);
if( upgrade && v._uninit &&
- !(val._type instanceof TypeNil tn && tn.notNull() && !val._type.isHigh()) ) continue;
+ !initialized(val._type) ) continue;
// 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));
@@ -455,6 +463,10 @@ public class Parser {
// Validate each user constructor, or an implicit empty one, before the
// declaration scope goes away. Later fields use their declaration defaults.
+ private static boolean initialized(Type t) {
+ return t!=Type.BOTTOM && !t.isHigh() && (!(t instanceof TypeNil tn) || tn.notNull());
+ }
+
private void checkInitFields(TypeStruct ts, int base) {
if( _returnScope==null || _returnScope.ctrl()._type.isHigh() ) return;
boolean implicit = _ctorExits.isEmpty();
@@ -467,7 +479,7 @@ public class Parser {
Var v = _scope.var(idx);
if( !v._uninit || v._fref ) continue;
Type t = exit.getOrDefault(v,_returnScope.in(idx)._type);
- if( !(t instanceof TypeNil tn && tn.notNull() && !t.isHigh()) )
+ if( !initialized(t) )
throw error("'"+ts._name+"' is not fully initialized, field '"+fld._fname+
(implicit ? "' needs to be set in a constructor" :
"' is only partially set in the constructor"),v._loc);
@@ -1025,6 +1037,10 @@ public class Parser {
// Widen ints to floats; narrow wide int types.
// Early error if types do not match variable.
private Node convertExpr( Node expr, Type t ) {
+ if( !expr._type.accessISA(t) )
+ throw error("Type "+expr._type.str()+" is not of declared type "+t.str());
+ if( t instanceof TypeMemPtr tmp && tmp.isFinal() )
+ expr = peep(new ReadOnlyNode(expr));
// A FunPtr carries the semantic edge to its function's Return. Do not
// hide that edge behind a Convert which can fold to a plain constant.
if( expr instanceof FunPtrNode && t instanceof TypeFunPtr )
@@ -1090,17 +1106,24 @@ public class Parser {
return require(n,";");
}
- /** Parse final: [!]var['=' asgn]
+ // 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 uninit = false; // Required non-null constructor field
Node expr;
@@ -1121,18 +1144,6 @@ public class Parser {
} else {
expr = 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 &&
- // 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 )
@@ -1141,12 +1152,6 @@ public class Parser {
if( !xfinal ) t = mutableInferredType(t);
}
- // Final is deep on ptrs
- if( xfinal && t instanceof TypeMemPtr tmp ) {
- t = tmp.makeRO();
- expr = peep(new ReadOnlyNode(expr));
- }
-
// expr is a constant function
if( t instanceof TypeFunPtr decl && expr._type instanceof TypeFunPtr tfp && tfp.isConstant() ) {
t = decl.makeFrom(tfp.fidx());
@@ -1168,8 +1173,8 @@ public class Parser {
// Required pointers retain their declared shape, but include
// null until every constructor path assigns them.
case TypeNil tn -> tn.nullable() ? con(Type.NIL) : con(t.meet(Type.NIL));
- case TypeInteger ti -> _code.ZERO;
- case TypeFloat tf -> con(TypeFloat.FZERO);
+ case TypeInteger ti -> xfinal ? con(Type.BOTTOM) : _code.ZERO;
+ case TypeFloat tf -> con(xfinal ? Type.BOTTOM : 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); }
@@ -1177,9 +1182,9 @@ public class Parser {
// Every non-null pointer without a default is constructor-required.
// Without `!` it is also shallow-final; mutability and definite
// initialization are independent properties.
- if( t instanceof TypeNil tn && !tn.nullable() ) {
+ if( (xfinal && !(t instanceof TypeNil)) || (t instanceof TypeNil tn && !tn.nullable()) ) {
uninit = true;
- if( !hasBang ) fld_final = true;
+ if( xfinal ) fld_final = true;
}
}
@@ -1430,8 +1435,10 @@ public class Parser {
// Otherwise you get a type name
int old1 = pos();
+ boolean writable = match("!");
+ boolean readonly = !writable && match("~");
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), t1 = t0;
@@ -1544,6 +1551,13 @@ public class Parser {
if( t1 instanceof TypeStruct ts1 )
t1 = TypeMemPtr.make(ts1);
+ // 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 ) {
@@ -1554,7 +1568,7 @@ public class Parser {
} else
throw error("Type "+t0+" cannot be null");
} else if( match("[~]") ) {
- t2 = TypeMemPtr.make(typeAry(t2,true));
+ t2 = TypeMemPtr.make(typeAry(t2,true)).makeRO();
} else if( match("[]") ) {
t2 = TypeMemPtr.make(typeAry(t2,false));
} else
@@ -1566,7 +1580,7 @@ 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(';') || peek('=') || match("->")) || id==null )
return posT(old1); // Reset lexer to reparse
@@ -1616,9 +1630,8 @@ public class Parser {
ta = TypeStruct.makeAry(tname,TypeInteger.U32,_code.alias(rcname),t,_code.alias(rcname), false );
TYPES.put(tname,ta);
}
- if( !efinal ) return ta;
// Already have the aliases, just efinal is wrong
- return TypeStruct.makeAry(tname,TypeInteger.U32,ta._fields[0]._alias,t,ta._fields[1]._alias,true );
+ return TypeStruct.makeAry(tname,TypeInteger.U32,ta._fields[0]._alias,t,ta._fields[1]._alias,efinal );
}
private Type implicitSelfType() {
@@ -2187,7 +2200,7 @@ public class Parser {
// array body.
TypeMemPtr str = TypeMemPtr.make((byte)2,TypeStruct.makeAry("[]u8", slen, lenAlias, body, elemAlias, true),true);
assert str.isConstant();
- return con(str);
+ return con(str.makeRO());
}
/**
@@ -2305,6 +2318,8 @@ public class Parser {
Node mem = mem();
// Store to field
mem.keep();
+ if( expr._type instanceof TypeMemPtr view && view._ro && ctrl()._type!=Type.XCONTROL )
+ throw error("Cannot modify final field '"+name+"'");
Node st = new StoreNode(loc(), name, alias, decl, ctrl(), mem, expr.unkeep(), off.unkeep(), lift, false).peephole().keep();
storeMem(st,alias,mem);
st.unkeep();
@@ -2330,6 +2345,8 @@ public class Parser {
throw error( "'" + ts._name + "' is not fully initialized, field '" + fld._fname + "' needs to be set in a constructor" );
Node op = opAssign(ch,load, decl ).keep();
mem.keep();
+ if( expr._type instanceof TypeMemPtr view && view._ro && ctrl()._type!=Type.XCONTROL )
+ throw error("Cannot modify final field '"+name+"'");
Node st = new StoreNode(loc(), name, alias, decl, ctrl(), mem, expr.unkeep(), off.unkeep(), op, false).peephole().keep();
storeMem(st,alias,mem);
st.unkeep();
@@ -2375,6 +2392,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();
Type self = implicitSelfType();
ts.push(self);
ids.push(self == TypePtr.PTR ? "#self" : "self");
@@ -2385,6 +2403,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);
@@ -2393,7 +2412,7 @@ public class Parser {
require("->");
// Make a concrete function type, with a fidx
TypeFunPtr tfp = TypeFunPtr.make1((byte)2,true,ts.asAry(),Type.BOTTOM,_code.fidx(_ref._cname));
- ReturnNode ret = parseFunctionBody(tfp,loc,ids.asAry());
+ ReturnNode ret = parseFunctionBody(tfp,loc,finals,ids.asAry());
return new FunPtrNode(tfp,_code._start,ret).peephole();
}
@@ -2401,6 +2420,7 @@ public class Parser {
require("{");
Ary<Type> ts = new Ary<>(Type.class);
Ary<String> ids = new Ary<>(String.class);
+ java.util.BitSet finals = new java.util.BitSet();
TypeStruct self = _scope.constructorSelf();
assert self != null;
ts.push(TypeMemPtr.make(constructorRecv(self)));
@@ -2416,6 +2436,7 @@ public class Parser {
while( true ) {
Type t = type();
if( t==null ) break;
+ finals.set(ids.size(),bindingFinal(t));
String id = requireId();
ts .push(t );
ids.push(id);
@@ -2424,7 +2445,7 @@ public class Parser {
require("->");
TypeFunPtr tfp = TypeFunPtr.make1((byte)2,true,ts.asAry(),TypeMem.makePrivate(self),_code.fidx(_ref._cname));
Node[] preParse = _scope.save();
- ReturnNode ret = _parseFunctionBody((self._name+"."+name).intern(),tfp,loc,ids.asAry());
+ ReturnNode ret = _parseFunctionBody((self._name+"."+name).intern(),tfp,loc,finals,ids.asAry());
_scope.restore(preParse);
require("}");
return new FunPtrNode(tfp,_code._start,ret).peephole();
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Serialize.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Serialize.java
index ee5c48df..a8cd315f 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Serialize.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Serialize.java
@@ -63,8 +63,9 @@ abstract public class Serialize {
Type.TAGOFFS();
BAOS baos = new BAOS();
+ // C0D2 adds independent pointer access permissions to serialized types.
// A - Print a header
- baos.write('C').write('0').write('D').write('E');
+ baos.write('C').write('0').write('D').write('2');
// Count unique Types
var types = new HashMap<Type,Integer>();
@@ -217,8 +218,8 @@ abstract public class Serialize {
Type.TAGOFFS();
// A - Read a header
- if( bais.read()!='C' || bais.read()!='0' || bais.read()!='D' || bais.read()!='E' )
- throw new IllegalArgumentException("Missing magic word");
+ if( bais.read()!='C' || bais.read()!='0' || bais.read()!='D' || bais.read()!='2' )
+ throw new IllegalArgumentException("Unsupported serialized IR format; rebuild the object file");
// B - Packed read of #strings, then strings
int ndependents = bais.packed4(); // Number of dependent object files
@@ -246,8 +247,8 @@ abstract public class Serialize {
String[] strs = elf._strs;
if( strs == null ) {
// A - Read a header
- if( bais.read() != 'C' || bais.read() != '0' || bais.read() != 'D' || bais.read() != 'E' )
- throw new IllegalArgumentException( "Missing magic word" );
+ if( bais.read() != 'C' || bais.read() != '0' || bais.read() != 'D' || bais.read() != '2' )
+ throw new IllegalArgumentException( "Unsupported serialized IR format; rebuild the object file" );
// B - Packed read of #strings, then strings
int ndependents = bais.packed4(); // Number of dependent object files
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
index 2e921301..162f9c24 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
@@ -227,7 +227,7 @@ public class CallEndNode extends CFGNode implements MultiNode {
// references here. Compare against its recursively upgraded form,
// so deep mutability requirements participate before inlining.
Type formal = tfp.arg(i).upgradeType(Parser.TYPES);
- if( !arg._type.isa(formal) )
+ if( !arg._type.isa(formal) || !arg._type.accessISA(formal) )
{ addDep(arg); return false; }
}
return true;
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/CallNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CallNode.java
index 84f1e705..4372e2d5 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/CallNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CallNode.java
@@ -187,7 +187,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)) ) {
// Constructors return private memory and carry hidden self and
// selfMem arguments ahead of the source-level arguments.
int argno = i - (tfp.ret() instanceof TypeMem ? 2 : 0);
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ConvertNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ConvertNode.java
index 4ee5521a..b038366a 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/ConvertNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ConvertNode.java
@@ -35,7 +35,7 @@ public class ConvertNode extends Node {
Type src = val()._type;
if( CodeGen.CODE._phase == CodeGen.Phase.Opto ) {
if( src.isHigh() ) return Type.TOP;
- if( src.isa(_dst) ) return src;
+ if( (src.isa(_dst) && src.accessISA(_dst)) ) return src;
}
return _dst;
}
@@ -43,7 +43,7 @@ public class ConvertNode extends Node {
@Override public Node idealize() {
Type src = val()._type;
if( src==Type.BOTTOM || src==Type.TOP ) return null;
- if( src.isa(_dst) ) return val();
+ if( (src.isa(_dst) && src.accessISA(_dst)) ) return val();
// Managed arrays are represented by an object pointer, while C wants
// the address of the first element. The representation cast itself is
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java
index a2345d92..a557f97c 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java
@@ -85,6 +85,7 @@ public class LoadNode extends MemOpNode {
return tmp.isHigh() || tmp._obj.isHigh() ? TypeScalar.TOP : TypeScalar.BOT;
Type t = pfld._t;
+ boolean readonly = t instanceof TypeMemPtr view && view._ro;
// Load member of constant array
if( t instanceof TypeConAry ary )
t = ary.elem(); // TODO: if offset is known, can peek the constant
@@ -93,7 +94,7 @@ public class LoadNode extends MemOpNode {
// A deeply read-only base produces a deeply read-only value. The
// generic declared field type must not cast this information away.
- if( ptr0.isFinal() )
+ if( ptr0.isFinal() || readonly )
t = t.makeRO();
return scalar(t);
}
@@ -306,7 +307,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/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java
index 7e2963f2..415ade34 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java
@@ -269,7 +269,7 @@ public class StoreNode extends MemOpNode {
return null;
TypeMemPtr tmp = (TypeMemPtr)ptr()._type;
Field f = tmp._obj.field(_name);
- if( f!=null && f._final && !_init )
+ if( f!=null && (tmp._ro || f._final) && !_init )
return Parser.error("Cannot modify final field '"+_name+"'",_loc);
return null;
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/Type.java b/chapter25/src/main/java/com/seaofnodes/simple/type/Type.java
index d1af6ca8..980daf0f 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/type/Type.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/Type.java
@@ -468,6 +468,10 @@ public class Type /*implements Cloneable*/ {
boolean _isConstant() { return _type==TNIL; }
// Are all reachable struct Fields are final?
+ // 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; }
+
public final boolean isFinal() { return recurClose(recurOpen()._isFinal()); }
boolean _isFinal() { assert is_nokids(); return true; }
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java
index cfd213b7..175b1c63 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java
@@ -27,12 +27,15 @@ 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 pointer value; the pointer itself is constant.
public boolean _pub; // Escaped/public pointer. False means constructor-private.
private static final Ary<TypeMemPtr> FREE = new Ary<>(TypeMemPtr.class);
private TypeMemPtr(byte nil, TypeStruct obj, boolean one, boolean pub) { super(TMEMPTR,nil); init(nil,obj,one,pub); }
- private TypeMemPtr init(byte nil, TypeStruct obj, boolean one, boolean pub) { _nil=nil; _obj=obj; _one=one; _pub=pub; return this; }
+ private TypeMemPtr init(byte nil, TypeStruct obj, boolean one, boolean pub) { _nil=nil; _obj=obj; _ro=obj==TypeStruct.BOT; _one=one; _pub=pub; return this; }
// Return a filled-in TypeMemPtr; either from free list or alloc new.
static TypeMemPtr malloc(byte nil, TypeStruct obj, boolean one, boolean pub) {
return FREE.isEmpty() ? new TypeMemPtr(nil,obj,one,pub) : FREE.pop().init(nil,obj,one,pub);
@@ -43,7 +46,10 @@ public class TypeMemPtr extends TypeNil {
return make(nil,obj,one,true);
}
public static TypeMemPtr make(byte nil, TypeStruct obj, boolean one, boolean pub) {
- TypeMemPtr tmp = malloc(nil, obj, one, pub);
+ return make(nil,obj,one, pub,obj==TypeStruct.BOT);
+ }
+ private static TypeMemPtr make(byte nil, TypeStruct obj, boolean one, boolean pub, boolean ro) {
+ TypeMemPtr tmp = malloc(nil, obj, one, pub).access(ro);
TypeMemPtr t2 = tmp.intern();
if( t2==tmp ) return tmp;
return VISIT.isEmpty() ? t2.free(tmp) : t2.delayFree(tmp);
@@ -66,15 +72,36 @@ public class TypeMemPtr extends TypeNil {
public static TypeMemPtr makeNullable(TypeStruct obj) { return make((byte)3, obj); }
public static TypeMemPtr makePrivate (TypeStruct obj) { return make((byte)2, obj, false, false); }
- public TypeMemPtr makeFrom(TypeStruct obj) { return obj==_obj ? this : make(_nil, obj, _one, _pub); }
+ public TypeMemPtr makeFrom(TypeStruct obj) { return obj==_obj ? this : make(_nil, obj, _one, _pub,_ro); }
public TypeMemPtr makeNullable() { return makeFrom((byte)3); }
/** Inferred mutable local: nullable, public and non-singleton. */
public TypeMemPtr makeVar() { return (TypeMemPtr)makeStorage(); }
/** Public-memory shape: nullable and non-singleton, preserving the referent. */
- @Override TypeMemPtr _makeStorage() { return make(_nil,_obj._makeStorage(),false,true); }
- @Override TypeMemPtr makeFrom(byte nil) { return nil==_nil ? this : make(nil, _obj, _one, _pub); }
+ @Override TypeMemPtr _makeStorage() { return make(_nil,_obj._makeStorage(),false,true,_ro); }
+ @Override TypeMemPtr makeFrom(byte nil) { return nil==_nil ? this : make(nil, _obj, _one, _pub,_ro); }
@Override public Type nonZero() { return makeFrom((byte)(_nil <= 1 ? 1 : 2)); }
+ 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, _pub);
+ 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}
public static final TypeMemPtr BOT = make((byte)3, TypeStruct.BOT);
@@ -82,7 +109,7 @@ 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) {
@@ -91,28 +118,28 @@ public class TypeMemPtr extends TypeNil {
// Can't keep _one if mixing two unequal singletons; the result is not
// either singleton. Once either side is public, the meet is public.
boolean one = _one && that._one && (_obj==that._obj || !(_obj.isConAry() || that._obj.isConAry()));
- return make(xmeet0(that), (TypeStruct)_obj.meet(that._obj), one, _pub | that._pub);
+ return make(xmeet0(that), (TypeStruct)_obj.meet(that._obj), one, _pub | that._pub,_ro | that._ro);
}
@Override
- TypeMemPtr xdual() { return malloc( dual0(), _obj.dual(), !_one, !_pub); }
+ TypeMemPtr xdual() { return malloc( dual0(), _obj.dual(), !_one, !_pub).access(!_ro); }
@Override TypeMemPtr rdual() {
if( _dual!=null ) return dual();
assert !_terned;
- TypeMemPtr d = malloc(dual0(), null, !_one, !_pub);
+ TypeMemPtr d = malloc(dual0(), null, !_one, !_pub).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,true,_ro); }
@Override boolean _isConstant() { return _one; }
- @Override boolean _isFinal() { return _obj._isFinal(); }
- @Override TypeMemPtr _makeRO() { return makeFrom(_obj._makeRO()); }
- @Override TypeMemPtr _close( String name, HashMap<String, Type> TYPES ) { return malloc(_nil,_obj._close(name, TYPES ),_one,_pub); }
+ @Override boolean _isFinal() { return _ro; }
+ @Override TypeMemPtr _makeRO() { return withAccess(true); }
+ @Override TypeMemPtr _close( String name, HashMap<String, Type> TYPES ) { return malloc(_nil,_obj._close(name, TYPES ),_one,_pub).access(_ro); }
@Override Type _upgradeType(HashMap<String,Type> TYPES) {
return makeFrom((TypeStruct)_obj._upgradeType(TYPES));
@@ -120,33 +147,34 @@ 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) ^ (_pub ? 4096 : 0); }
+ @Override int hash() { return (_ro ? 8192 : 0) ^ _obj.hashCode() ^ super.hash() ^ (_one ? 2048 : 0) ^ (_pub ? 4096 : 0); }
@Override boolean eq(Type t) {
TypeMemPtr ptr = (TypeMemPtr)t; // Invariant
- return super.eq(ptr) && _one == ptr._one && _pub == ptr._pub && _obj == ptr._obj;
+ return _ro==ptr._ro && super.eq(ptr) && _one == ptr._one && _pub == ptr._pub && _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 && _pub == ptr._pub && _obj.cycle_eq(ptr._obj);
+ return _ro==ptr._ro && super.eq(ptr) && _one == ptr._one && _pub == ptr._pub && _obj.cycle_eq(ptr._obj);
}
@Override public int nkids() { return 1; }
@Override public Type at( int idx ) { return _obj; }
@Override public void set( int idx, Type t ) { _obj = (TypeStruct)t; }
- // Reserve tags for null/not, one/general and public/private
- @Override int TAGOFF() { return 8; }
+ // Reserve tags for null/not, one/general, public/private and read-only access
+ @Override int TAGOFF() { return 16; }
@Override public void packed( BAOS baos, HashMap<String,Integer> strs ) {
assert _nil>=2;
baos.write(TAGOFFS[_type]
+ (_nil==2 ? 0 : 1)
+ (!_one ? 0 : 2)
- + (!_pub ? 0 : 4) );
+ + (!_pub ? 0 : 4)
+ + (!_ro ? 0 : 8) );
}
static TypeMemPtr packed( int tag, BAOS bais ) {
- return malloc((byte)((tag&1)+2),null,(tag&2)==2,(tag&4)==4);
+ return malloc((byte)((tag&1)+2),null,(tag&2)==2,(tag&4)==4).access((tag&8)==8);
}
@Override public String str() {
diff --git a/chapter25/src/main/smp/sys/aryi64.smp b/chapter25/src/main/smp/sys/aryi64.smp
index 22ee082e..98b72605 100644
--- a/chapter25/src/main/smp/sys/aryi64.smp
+++ b/chapter25/src/main/smp/sys/aryi64.smp
@@ -53,7 +53,7 @@ struct aryi64 {
// parseAryI64 & aryi64.str are bijections modulo whitespace.
// Returns null if the parse failed, without giving a clue as to why.
val parseAryI64 = { u8[~] str ->
- scan !s = new scan(str);
+ !scan !s = new scan(str);
if( !s.match('[') ) return null;
// Array of parsed results
var nums = new aryi64(new i64[1]);