From 22df891893d30e9906d0b14fc3ae5d8e67d500b2 Mon Sep 17 00:00:00 2001 From: Cliff Click Date: Sat, 3 Oct 2026 14:03:48 -0700 Subject: 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 --- .../main/java/com/seaofnodes/simple/Parser.java | 130 +++++++++++++-------- .../src/main/java/com/seaofnodes/simple/Var.java | 1 + .../java/com/seaofnodes/simple/node/CallNode.java | 2 +- .../java/com/seaofnodes/simple/node/CastNode.java | 2 +- .../java/com/seaofnodes/simple/node/LoadNode.java | 4 +- .../com/seaofnodes/simple/node/MemMergeNode.java | 4 +- .../java/com/seaofnodes/simple/node/PhiNode.java | 6 +- .../java/com/seaofnodes/simple/node/ScopeNode.java | 9 ++ .../java/com/seaofnodes/simple/node/StoreNode.java | 2 +- .../java/com/seaofnodes/simple/type/Field.java | 2 +- .../main/java/com/seaofnodes/simple/type/Type.java | 4 + .../com/seaofnodes/simple/type/TypeMemPtr.java | 61 +++++++--- .../com/seaofnodes/simple/type/TypeStruct.java | 5 +- 13 files changed, 152 insertions(+), 80 deletions(-) (limited to 'chapter24/src/main/java/com/seaofnodes') diff --git a/chapter24/src/main/java/com/seaofnodes/simple/Parser.java b/chapter24/src/main/java/com/seaofnodes/simple/Parser.java index 432ec137..f362d062 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/Parser.java @@ -197,6 +197,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 @@ -240,7 +244,7 @@ public class Parser { // All args, "as-if" called externally for( int i=0; i= _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,"[]"))); @@ -706,7 +711,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.isConstant() ? t : t.widen(),null,expr)); return expr; } @@ -737,43 +742,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() ) { @@ -792,23 +808,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. @@ -817,6 +830,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; } @@ -848,7 +862,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 ) ); } } @@ -892,12 +906,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); @@ -920,6 +936,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 ) { @@ -929,7 +952,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 @@ -941,13 +964,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; } @@ -961,10 +984,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 }`. @@ -1255,7 +1277,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(); @@ -1365,7 +1388,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); } @@ -1481,7 +1504,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. @@ -1489,6 +1512,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); @@ -1541,7 +1566,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 ) @@ -1566,7 +1592,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 @@ -1643,6 +1669,7 @@ public class Parser { private Node func() { Ary ts = new Ary<>(Type.class); Ary 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. @@ -1659,6 +1686,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); @@ -1667,7 +1695,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/chapter24/src/main/java/com/seaofnodes/simple/Var.java b/chapter24/src/main/java/com/seaofnodes/simple/Var.java index 14295395..e13b85c5 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/Var.java +++ b/chapter24/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/chapter24/src/main/java/com/seaofnodes/simple/node/CallNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/CallNode.java index 569c0877..e63b3f62 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/CallNode.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/CallNode.java @@ -161,7 +161,7 @@ public class CallNode extends CFGNode { // Check for args for( int i=0; i0; 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/chapter24/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/StoreNode.java index 99a6c6a0..4e4ab202 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter24/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; // This means we have an error input, report elsewhere 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/chapter24/src/main/java/com/seaofnodes/simple/type/Field.java b/chapter24/src/main/java/com/seaofnodes/simple/type/Field.java index b2b2deca..1f22cb05 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/type/Field.java +++ b/chapter24/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/chapter24/src/main/java/com/seaofnodes/simple/type/Type.java b/chapter24/src/main/java/com/seaofnodes/simple/type/Type.java index b47de0a4..b9a6a1f3 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/type/Type.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/type/Type.java @@ -330,6 +330,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/chapter24/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java b/chapter24/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java index a07796e2..9fc90544 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java +++ b/chapter24/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 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,11 +64,32 @@ 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); } public TypeMemPtr makeHigh() { return make((byte)0,_obj.makeHigh(),false); } + 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} public static final TypeMemPtr BOT = make((byte)3, TypeStruct.BOT); @@ -70,35 +97,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 ts) { ts.add(NOTBOT); ts.add(BOT); ts.add(TEST); } + public static void gather(ArrayList 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()); } @@ -106,7 +133,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 @@ -118,16 +145,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/chapter24/src/main/java/com/seaofnodes/simple/type/TypeStruct.java b/chapter24/src/main/java/com/seaofnodes/simple/type/TypeStruct.java index 3215fe91..bb6ce4cb 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/type/TypeStruct.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/type/TypeStruct.java @@ -93,9 +93,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 -- cgit v1.2.3