diff options
26 files changed, 426 insertions, 215 deletions
diff --git a/chapter17/README.md b/chapter17/README.md index b7a1afd6..bf998ab8 100644 --- a/chapter17/README.md +++ b/chapter17/README.md @@ -15,7 +15,7 @@ Allow pre-increment for identifiers only, for now: `--pc`. ## var & val `var` can be used in the `type` position to indicate a "variable" (mutable -value), whose type will be inferred from the initalizing expression. +value), whose type will be inferred from the required initalizing expression. `val` is the same as `var`, except it is a "value" (not mutable). @@ -30,78 +30,68 @@ infers as `S s? = new S;` -## Initialized fields are immutable by default +## Mutability -Typed field declarations with an initializer are immutable by default, and can -be made mutable with a leading `!`. `var` and `val` keep their current sense. -Fields are immutable at the end of either constructor, but can be changed -during construction. +Typed primitive fields are always mutable. Typed reference fields without an +initializer must be mutable to get a value. References with an initializer are +immutable by default and can be made mutable with a leading `!`. `var` and +`val` keep their current sense and can be used to make any field mutable or +immutable. Fields are always mutable during construction, but will become +immutable at the end of either constructor. +`int x; x=3; x++; // OK, primitive so mutable` +`int x =3; x++; // Ok, primitive so mutable, despite initializer` -Examples: +Assume in the next examples this struct exists: +`struct S { int x; };` +Then: +`S s; s = new S ; s.x++; // OK, no initializer so s.x is mutable` +`S s; s = new S{x=3}; s.x++; // OK, no initializer so s.x is mutable` +`S s = new S ; s.x++; // Error, initializer so s.x is immutable` +`S s = new S{x=3}; s.x++; // Error, initializer so s.x is immutable` -```java -struct Point { int x,y; }; // Fields x and y are not initialized, mutable -Point p = new Point; -p.x++; // Mutate is ok -return p.x; -``` +Leading `!` makes mutable: +`S !s = new S; s.x++; // Ok, has '!' so mutable` -```java -struct Point { int x=3,y=4; }; // Fields x and y are initialized, immutable -Point p = new Point; -p.x++; // Error! -return p.x; -``` +'var' is variable: +`var s = new S; s.x++; // Ok, has var so mutable` -```java -struct Point { int x=3,y=4; }; // Fields x and y are initialized, immutable -Point p = new Point { x=x*x; y=y*y; }; // Ok to mutate in the constructor -p.x++; // Error! -return p.x; -``` +'val' is a "value": not mutable through this reference, but may be mutable +through other references. +`val s = new S; s.x++; // Error, val so s.x is immutable` -```java -struct Point { int !x=3, !y=4; }; // Fields x and y are initialized, mutable -Point p = new Point; -p.x++; // Mutate is OK -return p.x; -``` -```java -struct Point { var x,y; }; // Error, var and val require an initializer -Point p = new Point; -p.x++; -return p.x; -``` -```java -struct Point { var x=3,y=4; }; // Fields x and y are mutable -Point p = new Point; -p.x++; // Mutate is OK -return p.x; -``` +### References with an initializer are deeply immutable -```java -struct Point { val x=3,y=4; }; // Fields x and y are immutable -Point p = new Point; -p.x++; // Error! -return p.x; -``` +References with an initializer are deeply immutable, but a mutable version of +the same reference can exist. This works for `var` and normal type +declarations: `var s = new S;` and `S s = new S` both make `s` an +immutable reference to a `struct S`. -## Deep Immutability (proposal) - ```cpp -struct Bar { int x; }; -Bar b = new Bar; /* b = new Bar; *//* b.x++; */ // immutable & deep immutable -Bar !b = new Bar; b = new Bar; b.x++; // mutable & deep mutable -val b = new Bar; /* b = new Bar; *//* b.x++; */ // immutable & deep immutable -var b = new Bar; b = new Bar; b.x++; // mutable & inherits deep mutability from RHS +struct Bar { int x; } +Bar !bar = new Bar; +bar.x = 3; // Ok, bar is mutable + +struct Foo { Bar !bar; int y; } +Foo !foo = new Foo; +foo.bar = barl // Ok bar is mutable +foo.bar.x++; // Ok foo and foo.bar and foo.bar.x are all mutable + +val xfoo = foo; // Throw away mutability +xfoo.bar.x++; // Error, cannot mutate through xfoo + +print(xfoo.bar.x); // Ok to read through xfoo, prints 4 +foo.bar.x++; // Bumps to 5 +print(xfoo.bar.x); // Ok to read through xfoo, prints 5 ``` + + ## Trinary Allow `pred ? e_true : e_false`. Also allow `pred ? e_true`, where the false result @@ -138,7 +128,6 @@ return i; // ERROR: Undefined name 'i' finalize final oper-assign +=, -= TODO -for-interator switch diff --git a/chapter17/src/main/java/com/seaofnodes/simple/Parser.java b/chapter17/src/main/java/com/seaofnodes/simple/Parser.java index e2f8ad32..09ea21ac 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/Parser.java @@ -395,7 +395,7 @@ public class Parser { } // Look for an unbalanced `)`, skipping balanced - private void skipAsgn() { + private Type skipAsgn() { int paren=0; while( true ) // Next X char handles skipping complex comments @@ -403,10 +403,8 @@ public class Parser { case Character.MAX_VALUE: throw Utils.TODO(); case ')': - if( --paren<0 ) { - pos(pos()-1); // Leave the `)` behind - return; - } + if( --paren<0 ) + return posT(pos()-1); // Leave the `)` behind break; case '(': paren ++; break; default: break; @@ -521,10 +519,12 @@ public class Parser { boolean hasType = t!=null && t!=Type.TOP && t!=Type.BOTTOM; // User written type boolean hasExpr = !(peek(';') || peek(',')); // Expression follows if( hasExpr && !match("=") ) // Something else - { pos(old); return parseExpression(); } - if( !hasExpr && t==Type.BOTTOM ) throw errorSyntax("expression"); + { pos(old); return parseExpression(); } + if( !hasExpr && !hasType ) throw errorSyntax("expression"); Node expr = hasExpr ? parseExpression() : con(t.makeInit()); - boolean xfinal = t==Type.TOP || (hasType && hasExpr && !hasBang && _scope.inCon()); + + // Final if no Bang AND TMP; primitives are not-final by default + boolean xfinal = !hasBang && (t instanceof TypeMemPtr) && hasExpr; // Defining a new variable vs updating an old one if( t==null ) { @@ -538,11 +538,19 @@ public class Parser { if( _scope.in(def._idx)._type!=Type.TOP && def._final && !_scope.inCon() ) throw error("Cannot reassign final '"+name+"'"); t = def.type(); // Declared field type - } else if( !hasType && hasExpr ) { - // Discovered type from expression + + } else if( !hasType && hasExpr ) { // Either 'val' or 'var' + // Either 'var' or 'val'; type comes from expression + xfinal = t==Type.TOP; // xfinal === 'val'; otherwise 'var' t = expr._type.glb(); } + // Final is deep on ptrs + if( xfinal && t instanceof TypeMemPtr tmp ) { + t = tmp.makeRO(); + expr = peep(new ReadOnlyNode(expr)); + } + // Auto-widen int to float if( expr._type instanceof TypeInteger && t instanceof TypeFloat ) expr = peep(new ToFloatNode(expr)); @@ -550,15 +558,13 @@ public class Parser { expr = zsMask(expr,t); // Auto-deepen forward ref types Type e = expr._type; - if( e instanceof TypeMemPtr tmp && tmp._obj._fields==null ) + if( e instanceof TypeMemPtr tmp && tmp.isFRef() ) e = TYPES.get(tmp._obj._name); - // "var" and "val" use TOP and BOTTOM - but just take the expression type. - if( t==Type.TOP || t==Type.BOTTOM ) - t = expr._type.glb(); // Type is sane if( !e.isa(t) ) throw error("Type " + e.str() + " is not of declared type " + t.str()); + // Define a new name, vs update an old one if( isDecl ) { if( !_scope.define(name,t,xfinal,expr) ) throw error("Redefining name '" + name + "'"); @@ -612,7 +618,7 @@ public class Parser { if (_xScopes.size() > 1) throw errorSyntax("struct declarations can only appear in top level scope"); String typeName = requireId(); Type t = TYPES.get(typeName); - if( t!=null && !(t instanceof TypeMemPtr tmp && tmp._obj._fields==null) ) + if( t!=null && !(t instanceof TypeMemPtr tmp && tmp.isFRef() ) ) throw errorSyntax("struct '" + typeName + "' cannot be redefined"); // A Block scope parse, and inspect the scope afterward for fields. @@ -646,6 +652,8 @@ public class Parser { // 'id' which the caller must parse. This lets us distinguish forward ref // types (which ARE valid here) from local vars in an (optional) forward // ref type position. + + // t = int|i8|i16|i32|i64|u8|u16|u32|u64|byte|bool | flt|f32|f64 | val | var | struct[?] private Type type() { int old1 = pos(); String tname = _lexer.matchId(); @@ -654,7 +662,7 @@ public class Parser { Type t0 = TYPES.get(tname); // No new types as keywords if( t0 == null && KEYWORDS.contains(tname) ) - { pos(old1); return null; } + return posT(old1); Type t1 = t0 == null ? TypeMemPtr.make(TypeStruct.makeFRef(tname)) : t0; // Null: assume a forward ref type // Nest arrays and '?' as needed while( true ) { @@ -680,10 +688,8 @@ public class Parser { int old2 = pos(); String id = _lexer.matchId(); pos(old2); // Reset lexer to reparse - if( id==null ) { - pos(old1); // Reset lexer to reparse - return null; // Not a type - } + if( id==null ) + return posT(old1); // Reset lexer to reparse // Yes a forward ref, so declare it TYPES.put(tname,t1); return t1; @@ -853,7 +859,7 @@ public class Parser { if( n != null ) { if( n._final ) throw error("Cannot reassign final '"+n._name+"'"); - Node expr = peep(new AddNode(_scope.in(n),con(delta))); + Node expr = zsMask(peep(new AddNode(_scope.in(n),con(delta))),n.type()); _scope.update(n,expr); return expr; } @@ -887,7 +893,7 @@ public class Parser { if( matchx("++") || matchx("--") ) { if( n._final ) throw error("Cannot reassign final '"+n._name+"'"); - _scope.update(n,peep(new AddNode(rvalue,con( _lexer.peek(-1)=='+' ? 1 : -1)))); + _scope.update(n,zsMask(peep(new AddNode(rvalue,con( _lexer.peek(-1)=='+' ? 1 : -1))),n.type())); } return rvalue.unkeep(); } @@ -1025,7 +1031,7 @@ public class Parser { // Sanity check field name for existing TypeMemPtr tmp = (TypeMemPtr)TYPES.get(ptr._obj._name); - if( tmp == null ) throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'"); + if( tmp == null ) throw error("Accessing unknown field '" + name + "' from '" + ptr + "'"); TypeStruct base = tmp._obj; int fidx = base.find(name); if( fidx == -1 ) throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'"); @@ -1053,28 +1059,28 @@ public class Parser { return val.unkeep(); // "obj.a = expr" returns the expression while updating memory } - Node load = new LoadNode(name, f._alias, f._type.glb(), memAlias(f._alias), expr, off); + Node load = new LoadNode(name, f._alias, f._type.glb(), memAlias(f._alias), expr.keep(), off); // Arrays include control, as a proxy for a safety range check // Structs don't need this; they only need a NPE check which is // done via the type system. if( base.isAry() ) load.setDef(0,ctrl()); load = peep(load); - // ary[idx]++ + // ary[idx]++ or ptr.fld++ if( matchx("++") || matchx("--") ) { if( f._final && !f._fname.equals("[]") ) throw error("Cannot reassign final '"+f._fname+"'"); Node inc = peep(new AddNode(load,con( _lexer.peek(-1)=='+' ? 1 : -1))); Node val = zsMask(inc,f._type); - Node st = new StoreNode(name, f._alias, f._type, memAlias(f._alias), expr, off, val, false); + Node st = new StoreNode(name, f._alias, f._type, memAlias(f._alias), expr.unkeep(), off, val, false); // Arrays include control, as a proxy for a safety range check. // Structs don't need this; they only need a NPE check which is // done via the type system. if( base.isAry() ) st.setDef(0,ctrl()); memAlias(f._alias, peep(st)); // And use the original loaded value as the result - } - if( off.unkeep().isDead() ) off.kill(); + } else expr.unkill(); + off.unkill(); return parsePostfix(load); } @@ -1134,6 +1140,7 @@ public class Parser { _lexer._position = pos; return old; } + private Type posT(int pos) { _lexer._position = pos; return null; } // Require and return an identifier private String requireId() { diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/LoadNode.java index 555b9ae4..1528f2e9 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -63,13 +63,15 @@ public class LoadNode extends MemOpNode { // Load-after-Store on same address, but bypassing provably unrelated // stores. This is a more complex superset of the above two peeps. // "Provably unrelated" is really weak. + if( ptr instanceof ReadOnlyNode ro ) + ptr = ro.in(1); Node mem = mem(); outer: while( true ) { switch( mem ) { case StoreNode st: if( ptr == st.ptr() && off() == st.off() ) - return st.val(); // Proved equal + return castRO(st.val()); // Proved equal // Can we prove unequal? Offsets do not overlap? if( !off()._type.join(st.off()._type).isHigh() && // Offsets overlap !neverAlias(ptr,st.ptr()) ) // And might alias @@ -82,7 +84,7 @@ public class LoadNode extends MemOpNode { case ProjNode mproj: if( mproj.in(0) instanceof NewNode nnn1 ) { if( ptr instanceof ProjNode pproj && pproj.in(0) == mproj.in(0) ) - return nnn1.in(nnn1.findAlias(_alias)); // Load from New init + return castRO(nnn1.in(nnn1.findAlias(_alias))); // Load from New init if( !(ptr instanceof ProjNode pproj && pproj.in(0) instanceof NewNode nnn2) ) break outer; // Cannot tell, ptr not related to New mem = nnn1.in(_alias);// Bypass unrelated New @@ -120,10 +122,12 @@ public class LoadNode extends MemOpNode { return null; } + private Node ld( int idx ) { Node mem = mem(), ptr = ptr(); return new LoadNode(_name,_alias,_declaredType,mem.in(idx),ptr instanceof PhiNode && ptr.in(0)==mem.in(0) ? ptr.in(idx) : ptr, off()).peephole(); } + private static boolean neverAlias( Node ptr1, Node ptr2 ) { return ptr1.in(0) != ptr2.in(0) && // Unrelated allocations @@ -158,4 +162,11 @@ public class LoadNode extends MemOpNode { if( px instanceof StoreNode st1 && ptr()==st1.ptr() && off()==st1.off() ) { px.addDep(this); return true; } return false; } + + // Read-Only is a deep property, and cannot be cast-away + private Node castRO(Node rez) { + if( ptr()._type.isFinal() && !rez._type.isFinal() ) + return new ReadOnlyNode(rez).peephole(); + return rez; + } } diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/PhiNode.java index 58ce6b47..cb266ca3 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -47,7 +47,7 @@ public class PhiNode extends Node { // During parsing Phis have to be computed type pessimistically. if( r.inProgress() ) return _declaredType; // Set type to local top of the starting type - Type t = _declaredType.glb().dual(); + Type t = _declaredType.lub(); for (int i = 1; i < nIns(); i++) // If the region's control input is live, add this as a dependency // to the control because we can be peeped should it become dead. diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java new file mode 100644 index 00000000..fddcaa67 --- /dev/null +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java @@ -0,0 +1,29 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeMemPtr; + +import java.util.BitSet; + +/** + * Cast a pointer to read-only + */ +public class ReadOnlyNode extends Node { + public ReadOnlyNode( Node n ) { super(null,n); } + @Override public String label() { return "ReadOnly"; } + + @Override + StringBuilder _print1(StringBuilder sb, BitSet visited) { + return in(1)._print0(sb.append("(const)"),visited); + } + @Override public Type compute() { + Type t = in(1)._type; + return t instanceof TypeMemPtr tmp ? tmp.makeRO() : t; + } + + @Override public Node idealize() { + if( in(1)._type instanceof TypeMemPtr tmp && tmp.isFinal() ) + return in(1); + return null; + } +} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/ScopeMinNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/ScopeMinNode.java index c51349fa..1cff690c 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/ScopeMinNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/ScopeMinNode.java @@ -29,12 +29,12 @@ public class ScopeMinNode extends Node { Type def = Parser.TYPES.get(((TypeMemPtr)_type)._obj._name); return (_type=_type.meet(def)); } - public Type glb() { + public Type lazyGLB() { Type t = type(); return t instanceof TypeMemPtr ? t : t.glb(); } @Override public String toString() { - return _type.toString()+(_final ? " !": " ")+_name; + return _type.toString()+(_final ? " ": " !")+_name; } } diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/ScopeNode.java index 5a735515..bfb6eed6 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -167,7 +167,7 @@ public class ScopeNode extends ScopeMinNode { // Set real Phi in the loop head // The phi takes its one input (no backedge yet) from a recursive // lookup, which might have insert a Phi in every loop nest. - : loop.setDef(v._idx,new PhiNode(v._name, v.glb(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).peephole()); + : loop.setDef(v._idx,new PhiNode(v._name, v.lazyGLB(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).peephole()); setDef(v._idx,old); } if( st!=null ) setDef(v._idx,st); // Set new value diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/StoreNode.java index 8e3e6693..62b49a1a 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -114,6 +114,9 @@ public class StoreNode extends MemOpNode { String err() { String err = super.err(); if( err != null ) return err; + TypeMemPtr tmp = (TypeMemPtr)ptr()._type; + if( tmp._obj.field(_name)._final ) + return "Cannot modify final field '"+_name+"'"; Type t = val()._type; return _init || t.isa(_declaredType) ? null : "Cannot store "+t+" into field "+_declaredType+" "+_name; } diff --git a/chapter17/src/main/java/com/seaofnodes/simple/type/Field.java b/chapter17/src/main/java/com/seaofnodes/simple/type/Field.java index e2c0aa59..349c1fb0 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/type/Field.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/type/Field.java @@ -31,8 +31,10 @@ public class Field extends Type { return new Field(fname,type,alias,xfinal).intern(); } public Field makeFrom( Type type ) { - return new Field(_fname,type,_alias,_final).intern(); + return type == _type ? this : new Field(_fname,type,_alias,_final).intern(); } + @Override public Field makeRO() { return _final ? this : make(_fname,_type.makeRO(),_alias,true); } + @Override public boolean isFinal() { return _final && _type.isFinal(); } public static final Field TEST = make("test",TypeInteger.ZERO,-2,false); public static void gather(ArrayList<Type> ts) { ts.add(TEST); } @@ -41,16 +43,20 @@ public class Field extends Type { Field fld = (Field)that; // Invariant assert _fname.equals(fld._fname); assert _alias==fld._alias; - assert _final==fld._final; - return make(_fname,_type.meet(fld._type),_alias,_final); + return make(_fname,_type.meet(fld._type),_alias,_final | fld._final); } @Override - public Field dual() { return make(_fname,_type.dual(),_alias,_final); } + public Field dual() { return make(_fname,_type.dual(),_alias,!_final); } - @Override - public Field glb() { - return make(_fname,_type.glb(),_alias,_final); + @Override public Field glb() { + Type glb = _type.glb(); + return glb==_type ? this : make(_fname,glb,_alias,_final); + } + + @Override public Field lub() { + Type lub = _type.lub(); + return lub==_type ? this : make(_fname,lub,_alias,_final); } // Override in subclasses @@ -64,7 +70,7 @@ public class Field extends Type { @Override public StringBuilder print( StringBuilder sb ) { - return _type.print(sb.append(_final?"!":"").append(_fname).append(":").append(_alias).append(" : ")); + return _type.print(sb.append(_final?"":"!").append(_fname).append(":").append(_alias).append(" : ")); } @Override public String str() { return _fname; } diff --git a/chapter17/src/main/java/com/seaofnodes/simple/type/Type.java b/chapter17/src/main/java/com/seaofnodes/simple/type/Type.java index e454f48f..84b0119a 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/type/Type.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/type/Type.java @@ -81,9 +81,6 @@ public class Type { */ public StringBuilder typeName( StringBuilder sb) { return print(sb); } - public Type makeInit() { return null; } - public Type makeZero() { return null; } - public Type nonZero() { return glb(); } // ---------------------------------------------------------- @@ -175,14 +172,29 @@ public class Type { // True if this "isa" t; e.g. 17 isa TypeInteger.BOT public boolean isa( Type t ) { return meet(t)==t; } - /** - * Compute greatest lower bound in the lattice - */ - public Type glb() { return _type==TCTRL ? XCONTROL : BOTTOM; } + /** Compute greatest lower bound in the lattice */ + public Type glb() { return _type==TCTRL ? CONTROL : BOTTOM; } + /** Compute least upper bound in the lattice */ + public Type lub() { return _type==TCTRL ? XCONTROL : TOP; } + + // Make an initial/default version of this type. Typically, 0 for integers + // and null for nullable pointers. + public Type makeInit() { return null; } + // Make a zero version of this type, 0 for integers and null for pointers. + public Type makeZero() { return null; } + // Make a non-zero version of this type, if possible. Integers attempt to + // exclude zero from their range and pointers become not-null. + public Type nonZero() { return glb(); } // Is forward-reference public boolean isFRef() { return false; } + // All reachable struct Fields are final + public boolean isFinal() { return true; } + + // Make all reachable struct Fields final + public Type makeRO() { return this; } + // ---------------------------------------------------------- // Size in bits to hold an instance of this type. diff --git a/chapter17/src/main/java/com/seaofnodes/simple/type/TypeFloat.java b/chapter17/src/main/java/com/seaofnodes/simple/type/TypeFloat.java index 37f6ce71..f85abbed 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/type/TypeFloat.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/type/TypeFloat.java @@ -104,9 +104,8 @@ public class TypeFloat extends Type { return make((byte)-_sz,0); } - @Override - public Type glb() { return BOT; } - + @Override public Type glb() { return BOT; } + @Override public Type lub() { return TOP; } @Override public TypeFloat makeInit() { return ZERO; } @Override public TypeFloat makeZero() { return ZERO; } diff --git a/chapter17/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/chapter17/src/main/java/com/seaofnodes/simple/type/TypeInteger.java index 06848dc7..b2979d7b 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/type/TypeInteger.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/type/TypeInteger.java @@ -115,6 +115,7 @@ public class TypeInteger extends Type { @Override public TypeInteger dual() { return make(_max,_min); } @Override public TypeInteger glb() { return BOT; } + @Override public TypeInteger lub() { return TOP; } @Override public TypeInteger makeInit() { return ZERO; } @Override public TypeInteger makeZero() { return ZERO; } @Override public TypeInteger nonZero() { diff --git a/chapter17/src/main/java/com/seaofnodes/simple/type/TypeMem.java b/chapter17/src/main/java/com/seaofnodes/simple/type/TypeMem.java index 32491a11..33c6a8ce 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/type/TypeMem.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/type/TypeMem.java @@ -21,7 +21,7 @@ public class TypeMem extends Type { private TypeMem(int alias, Type t) { super(TMEM); _alias = alias; _t = t; } public static TypeMem make(int alias, Type t) { return new TypeMem(alias,t).intern(); } - public static final TypeMem TOP = make( 0, Type.TOP); + public static final TypeMem TOP = make(-1, Type.TOP ); public static final TypeMem BOT = make(-1, Type.BOTTOM); public static void gather(ArrayList<Type> ts) { ts.add(make(1,TypeInteger.ZERO)); ts.add(BOT); } @@ -40,13 +40,11 @@ public class TypeMem extends Type { @Override public Type dual() { - if( _alias== 0 ) return BOT; - if( _alias==-1 ) return TOP; return make(_alias,_t.dual()); } - @Override - public Type glb() { return make(_alias,_t.glb()); } + @Override public Type glb() { return make(_alias,_t.glb()); } + @Override public Type lub() { return make(_alias,_t.lub()); } @Override int hash() { return 9876543 + _alias + _t.hashCode(); } diff --git a/chapter17/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java b/chapter17/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java index cc4b9fcd..08e7db1c 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java @@ -33,7 +33,9 @@ public class TypeMemPtr extends Type { } public static TypeMemPtr make(TypeStruct obj, boolean nil) { return new TypeMemPtr(obj, nil).intern(); } public static TypeMemPtr make(TypeStruct obj) { return make(obj, false); } - public TypeMemPtr makeFrom(TypeStruct obj) { return make(obj, _nil); } + public TypeMemPtr makeFrom(TypeStruct obj) { return obj==_obj ? this : make(obj, _nil); } + @Override public TypeMemPtr makeRO() { return makeFrom(_obj.makeRO()); } + @Override public boolean isFinal() { return _obj.isFinal(); } // An abstract pointer, pointing to either a Struct or an Array. // Can also be null or not. @@ -55,8 +57,8 @@ public class TypeMemPtr extends Type { @Override public TypeMemPtr dual() { return TypeMemPtr.make(_obj.dual(), !_nil); } - @Override - public TypeMemPtr glb() { return make(_obj.glb(),true); } + @Override public TypeMemPtr glb() { return make(_obj.glb(),true ); } + @Override public TypeMemPtr lub() { return make(_obj.lub(),false); } // Is forward-reference @Override public boolean isFRef() { return _obj.isFRef(); } @Override public Type makeInit() { return _nil ? NULLPTR : Type.TOP; } diff --git a/chapter17/src/main/java/com/seaofnodes/simple/type/TypeStruct.java b/chapter17/src/main/java/com/seaofnodes/simple/type/TypeStruct.java index 39011ce2..b3dbcad0 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/type/TypeStruct.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/type/TypeStruct.java @@ -37,20 +37,28 @@ public class TypeStruct extends Type { public static final TypeStruct TEST = make("test",new Field[]{Field.TEST}); // Forward-ref version public static TypeStruct makeFRef(String name) { return make(name, (Field[])null); } + // Make a read-only version + @Override public TypeStruct makeRO() { + if( isFinal() ) return this; + Field[] flds = new Field[_fields.length]; + for( int i=0; i<flds.length; i++ ) + flds[i] = _fields[i].makeRO(); + return make(_name,flds); + } // Array public static TypeStruct makeAry(TypeInteger len, int lenAlias, Type body, int bodyAlias) { assert !(body instanceof TypeMemPtr tmp && !tmp._nil); return make("[" + body.str() + "]", - Field.make("#" ,len , lenAlias,true), - Field.make("[]",body,bodyAlias,true)); + Field.make("#" ,len , lenAlias,true ), + Field.make("[]",body,bodyAlias,false)); } // A pair of self-cyclic types private static final TypeStruct S1F = makeFRef("S1"); private static final TypeStruct S2F = makeFRef("S2"); - public static final TypeStruct S1 = make("S1", new Field[]{ Field.make("a", TypeInteger.BOT, -1, false), Field.make("s2",TypeMemPtr.make(S2F,false),-2, false) }); - private static final TypeStruct S2 = make("S2", new Field[]{ Field.make("b", TypeFloat .BOT, -3, false), Field.make("s1",TypeMemPtr.make(S1F,false),-4, false) }); + public static final TypeStruct S1 = make("S1", Field.make("a", TypeInteger.BOT, -1, false), Field.make("s2",TypeMemPtr.make(S2F,false),-2, false) ); + private static final TypeStruct S2 = make("S2", Field.make("b", TypeFloat .BOT, -3, false), Field.make("s1",TypeMemPtr.make(S1F,false),-4, false) ); private static final TypeStruct ARY = makeAry(TypeInteger.BOT,-1,TypeFloat.BOT,-2); @@ -63,6 +71,10 @@ public class TypeStruct extends Type { return i; return -1; } + public Field field(String fname) { + int idx = find(fname); + return idx== -1 ? null : _fields[idx]; + } // Find field index by alias public int findAlias( int alias ) { @@ -115,8 +127,7 @@ public class TypeStruct extends Type { } // Keeps the same struct, but lower-bounds all fields. - @Override - public TypeStruct glb() { + @Override public TypeStruct glb() { if( _glb() ) return this; // Need to glb each field Field[] flds = new Field[_fields.length]; @@ -131,9 +142,33 @@ public class TypeStruct extends Type { return false; return true; } + @Override public TypeStruct lub() { + if( _lub() ) return this; + // Need to glb each field + Field[] flds = new Field[_fields.length]; + for( int i=0; i<_fields.length; i++ ) + flds[i] = _fields[i].lub(); + return make(_name,flds); + } + private boolean _lub() { + if( _fields!=null ) + for( Field f : _fields ) + if( f.lub() != f ) + return false; + return true; + } + // Is forward-reference @Override public boolean isFRef() { return _fields==null; } + // All fields are final + @Override public boolean isFinal() { + if( _fields==null ) return true; + for( Field fld : _fields ) + if( !fld.isFinal() ) + return false; + return true; + } @Override boolean eq(Type t) { @@ -164,10 +199,8 @@ public class TypeStruct extends Type { sb.append(_name); if( _fields == null ) return sb; // Forward reference struct, just print the name sb.append(" {"); - for( Field f : _fields ) { - sb.append(" ").append(f._fname).append(" :"); - f._type.print(sb).append("; "); - } + for( Field f : _fields ) + f._type.print(sb).append(f._final ? " " : " !").append(f._fname).append("; "); return sb.append("}"); } diff --git a/chapter17/src/test/java/com/seaofnodes/simple/BrainFuckTest.java b/chapter17/src/test/java/com/seaofnodes/simple/BrainFuckTest.java index 4833483b..88d79215 100644 --- a/chapter17/src/test/java/com/seaofnodes/simple/BrainFuckTest.java +++ b/chapter17/src/test/java/com/seaofnodes/simple/BrainFuckTest.java @@ -15,7 +15,7 @@ public class BrainFuckTest { @Test public void testBrainfuck() { var program = "++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.".getBytes(StandardCharsets.UTF_8); - var encoded = new StringBuilder("u8[] program = new u8[").append(program.length).append("];"); + var encoded = new StringBuilder("u8[] !program = new u8[").append(program.length).append("];"); for (int i = 0; i < program.length; i++) { int value = program[i] & 0xFF; encoded.append("program[").append(i).append("] = ").append(value).append(";"); @@ -25,7 +25,7 @@ public class BrainFuckTest { encoded + """ var d = 0; u8[] !output = new u8[0]; -val data = new u8[100]; +u8[] !data = new u8[100]; for( int pc = 0; pc < program#; pc++ ) { var command = program[pc]; diff --git a/chapter17/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter17/src/test/java/com/seaofnodes/simple/Chapter10Test.java index 44d71b1e..d41c83d8 100644 --- a/chapter17/src/test/java/com/seaofnodes/simple/Chapter10Test.java +++ b/chapter17/src/test/java/com/seaofnodes/simple/Chapter10Test.java @@ -44,7 +44,7 @@ struct Foo { int x; }; Foo? foo = null; -Bar bar = new Bar; +Bar !bar = new Bar; bar.a = 1; bar.a = 2; return bar.a; @@ -57,7 +57,7 @@ return bar.a; public void testExample() { Parser parser = new Parser(""" struct Vector2D { int x; int y; }; -Vector2D v = new Vector2D; +Vector2D !v = new Vector2D; v.x = 1; if (arg) v.y = 2; @@ -96,7 +96,7 @@ arg=0+new s0.0; public void testLoop() { Parser parser = new Parser(""" struct Bar { int a; }; -Bar bar = new Bar; +Bar !bar = new Bar; while (arg) { bar.a = bar.a + 2; arg = arg + 1; @@ -111,7 +111,7 @@ return bar.a; public void testIf() { Parser parser = new Parser(""" struct Bar { int a; }; -Bar bar = new Bar; +Bar !bar = new Bar; if (arg) bar = null; bar.a = 1; return bar.a; @@ -124,7 +124,7 @@ return bar.a; public void testIf2() { Parser parser = new Parser(""" struct Bar { int a; }; -Bar? bar = null; +Bar? !bar = null; if (arg) bar = new Bar; bar.a = 1; return bar.a; @@ -150,7 +150,7 @@ return bar.a; public void testIfOrNull() { Parser parser = new Parser(""" struct Bar { int a; }; -Bar? bar = new Bar; +Bar? !bar = new Bar; if (arg) bar = null; if( bar ) bar.a = 1; return bar; @@ -164,7 +164,7 @@ return bar; Parser parser = new Parser( """ struct Bar { int a; }; -Bar? bar = new Bar; +Bar? !bar = new Bar; if (arg) bar = null; int rez = 3; if( !bar ) rez=4; @@ -179,7 +179,7 @@ return rez; public void testWhileWithNullInside() { Parser parser = new Parser(""" struct s0 {int v0;}; -s0? v0 = new s0; +s0? !v0 = new s0; int ret = 0; while(arg) { ret = v0.v0; @@ -216,7 +216,7 @@ struct Iter { int x; int len; }; -Iter i = new Iter; +Iter !i = new Iter; i.len = arg; int sum=0; while( i.x < i.len ) { @@ -234,9 +234,9 @@ return sum; public void test1() { Parser parser = new Parser(""" struct s0 {int v0;}; -s0 ret = new s0; +s0 !ret = new s0; while(arg) { - s0 v0 = new s0; + s0 !v0 = new s0; v0.v0 = arg; arg = arg-1; if (arg==5) ret=v0; @@ -252,8 +252,8 @@ return ret; public void test2() { Parser parser = new Parser(""" struct s0 {int v0;}; -s0 ret = new s0; -s0 v0 = new s0; +s0 !ret = new s0; +s0 !v0 = new s0; while(arg) { v0.v0 = arg; arg = arg-1; @@ -271,9 +271,9 @@ return ret; public void test3() { Parser parser = new Parser(""" struct s0 {int v0;}; -s0 ret = new s0; +s0 !ret = new s0; while(arg < 10) { - s0 v0 = new s0; + s0 !v0 = new s0; if (arg == 5) ret=v0; arg = arg + 1; } @@ -350,7 +350,7 @@ s0 v1 = v0; return v1; """); StopNode stop = parser.parse().iterate(); - assertEquals("return s0;", stop.toString()); + assertEquals("return (const)s0;", stop.toString()); } diff --git a/chapter17/src/test/java/com/seaofnodes/simple/Chapter11Test.java b/chapter17/src/test/java/com/seaofnodes/simple/Chapter11Test.java index ddcc9e63..79c0622c 100644 --- a/chapter17/src/test/java/com/seaofnodes/simple/Chapter11Test.java +++ b/chapter17/src/test/java/com/seaofnodes/simple/Chapter11Test.java @@ -338,7 +338,7 @@ return primeCount; Parser parser = new Parser( """ struct S { int f; }; -S v=new S; +S !v=new S; v.f = 2; int i=new S.f; i=v.f; @@ -356,9 +356,9 @@ return i; Parser parser = new Parser( """ struct S { int f; }; -S v = new S; +S !v = new S; v.f = arg; -S t = new S; +S !t = new S; int i = 0; if (arg) { if (arg+1) v = t; @@ -377,7 +377,7 @@ return i; Parser parser = new Parser( """ struct S { int f; }; -S v0 = new S; +S !v0 = new S; S? v1; if (arg) v1 = new S; if (v1) { @@ -397,7 +397,7 @@ return v0; Parser parser = new Parser( """ struct S { int f; }; -S v = new S; +S !v = new S; v.f = arg; S t = new S; int i = v.f; @@ -414,7 +414,7 @@ return i; Parser parser = new Parser( """ struct S { int f; }; -S v = new S; +S !v = new S; while(1) { while(arg+1) { arg=arg-1; } if (arg) break; @@ -431,7 +431,7 @@ return v; Parser parser = new Parser( """ struct s { int v; }; -s ptr=new s; +s !ptr=new s; while( -arg ) ptr = new s; while(1) @@ -446,7 +446,7 @@ while(1) Parser parser = new Parser( """ struct S { int f; }; -S v = new S; +S !v = new S; S t = new S; int i = v.f; while (arg) { @@ -464,7 +464,7 @@ return i; Parser parser = new Parser( """ struct S { int f; }; -S v = new S; +S !v = new S; S t = new S; while(arg) { arg=arg-1; @@ -484,7 +484,7 @@ return arg; Parser parser = new Parser( """ struct S { int f; }; -S v = new S; +S !v = new S; S t = new S; if (arg) { v.f=2; @@ -502,7 +502,7 @@ return v; Parser parser = new Parser( """ struct S { int f; }; -S v = new S; +S !v = new S; int i = arg; while (arg > 0) { int j = i/3; diff --git a/chapter17/src/test/java/com/seaofnodes/simple/Chapter13Test.java b/chapter17/src/test/java/com/seaofnodes/simple/Chapter13Test.java index aa659147..46eac241 100644 --- a/chapter17/src/test/java/com/seaofnodes/simple/Chapter13Test.java +++ b/chapter17/src/test/java/com/seaofnodes/simple/Chapter13Test.java @@ -23,9 +23,9 @@ return 3.14; Parser parser = new Parser( """ struct LLI { LLI? next; int i; }; -LLI? head = null; +LLI? !head = null; while( arg ) { - LLI x = new LLI; + LLI !x = new LLI; x.next = head; x.i = arg; head = x; @@ -42,9 +42,9 @@ return head.next.i; Parser parser = new Parser( """ struct LLI { LLI? next; int i; }; -LLI? head = null; +LLI? !head = null; while( arg ) { - LLI x = new LLI; + LLI !x = new LLI; x.next = head; x.i = arg; head = x; @@ -66,9 +66,9 @@ return next.i; """ struct int0 { int i; flt0? f; }; struct flt0 { flt f; int0? i; }; -int0 i0 = new int0; +int0 !i0 = new int0; i0.i = 17; -flt0 f0 = new flt0; +flt0 !f0 = new flt0; f0.f = 3.14; i0.f = f0; f0.i = i0; @@ -100,7 +100,7 @@ N n = new N { next = new M; }; return n.next; """); StopNode stop = parser.parse().iterate(); - assertEquals("return M;", stop.toString()); + assertEquals("return (const)M;", stop.toString()); } @Test @@ -121,7 +121,7 @@ return n.next; Parser parser = new Parser( """ struct N { N? next; int i; }; -N n = new N; +N !n = new N; n.i = 3.14; return n.i; """); @@ -169,7 +169,7 @@ return new S1.s=new S2; """ struct I {int i;}; struct P { I? pi; }; -P p1 = new P; +P !p1 = new P; P p2 = new P; p2.pi = new I; p2.pi.i = 2; diff --git a/chapter17/src/test/java/com/seaofnodes/simple/Chapter14Test.java b/chapter17/src/test/java/com/seaofnodes/simple/Chapter14Test.java index 01cecebb..23c82358 100644 --- a/chapter17/src/test/java/com/seaofnodes/simple/Chapter14Test.java +++ b/chapter17/src/test/java/com/seaofnodes/simple/Chapter14Test.java @@ -94,7 +94,7 @@ return b; Parser parser = new Parser( """ struct Foo { u1 b; }; -Foo f = new Foo; +Foo !f = new Foo; f.b = 123; return f.b; """); diff --git a/chapter17/src/test/java/com/seaofnodes/simple/Chapter15Test.java b/chapter17/src/test/java/com/seaofnodes/simple/Chapter15Test.java index 04958819..4e2682dc 100644 --- a/chapter17/src/test/java/com/seaofnodes/simple/Chapter15Test.java +++ b/chapter17/src/test/java/com/seaofnodes/simple/Chapter15Test.java @@ -25,7 +25,7 @@ return 3.14; Parser parser = new Parser( """ struct C { C? l; }; -C c = new C; +C !c = new C; c.l = c; return c; """); @@ -39,7 +39,7 @@ return c; Parser parser = new Parser( """ u8[] old = new u8[0]; -u8[] output = new u8[1]; +u8[] !output = new u8[1]; int i = 0; while (i < old#) { output[i] = old[i]; @@ -82,7 +82,7 @@ return is[1]; public void testBasic3() { Parser parser = new Parser( """ -int[] a = new int[2]; +int[] !a = new int[2]; a[0] = 1; a[1] = 2; return a[0]; @@ -97,7 +97,7 @@ return a[0]; Parser parser = new Parser( """ struct A { int i; }; -A?[] a = new A?[2]; +A?[] !a = new A?[2]; return a; """); StopNode stop = parser.parse().iterate(); @@ -111,10 +111,10 @@ return a; """ struct S { int x; flt y; }; // A new S -S s = new S; s.x=99; s.y = 3.14; +S !s = new S; s.x=99; s.y = 3.14; // Double-d array of Ss. Fill in one row. -S?[]?[] iss = new S?[]?[2]; +S?[]?[] !iss = new S?[]?[2]; iss[0] = new S?[7]; iss[0][2] = s; @@ -135,14 +135,13 @@ return rez; assertEquals(1.2 , Evaluator.evaluate(stop, 1)); } - @Ignore @Test public void testBasic6() { Parser parser = new Parser( """ struct S { int x; flt y; }; // A new S -S s = new S; s.x=99; s.y = 3.14; +S !s = new S; s.x=99; s.y = 3.14; // Double-d array of Ss. Fill in one row. S?[]?[] iss = new S?[]?[2]; @@ -156,9 +155,8 @@ if( iss[arg] ) rez = iss[arg][2].y; return rez; """); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region,Phi(Region,.y,1.2),1.2);", stop.toString()); - assertEquals(3.14, Evaluator.evaluate(stop, 0)); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Might be null accessing 'y'",e.getMessage()); } } @Test @@ -167,8 +165,8 @@ return rez; """ // Can we define a forward-reference array? struct Tree { Tree?[]? _kids; }; -Tree root = new Tree; -root._kids = new Tree?[2]; +Tree !root = new Tree; +root._kids = new Tree?[2]; // NO BANG SO ARRAY IS OF IMMUTABLE TREES???? root._kids[0] = new Tree; return root; """); @@ -192,7 +190,7 @@ return 0; public void testRollingSum() { Parser parser = new Parser( """ -int[] ary = new int[arg]; +int[] !ary = new int[arg]; // Fill [0,1,2,3,4,...] int i=0; while( i < ary# ) { @@ -216,7 +214,7 @@ return ary[1] * 1000 + ary[3]; // 1 * 1000 + 6 public void sieveOEratosthenes() { Parser parser = new Parser( """ -int[] ary = new int[arg], primes = new int[arg]; +int[] !ary = new int[arg], !primes = new int[arg]; int nprimes = 0, p=2; // Find primes while p^2 < arg while( p*p < arg ) { @@ -240,7 +238,7 @@ while( p < arg ) { p = p + 1; } // Copy/shrink the result array -int[] rez = new int[nprimes]; +int[] !rez = new int[nprimes]; int j = 0; while( j < nprimes ) { rez[j] = primes[j]; @@ -251,7 +249,7 @@ return rez; StopNode stop = parser.parse().iterate(); assertEquals("return [int];", stop.toString()); Evaluator.Obj obj = (Evaluator.Obj)Evaluator.evaluate(stop, 20); - assertEquals("[int] { # :int; [] :int; }",obj.struct().toString()); + assertEquals("[int] {int #; int ![]; }",obj.struct().toString()); long nprimes = (Long)obj.fields()[0]; long[] primes = new long[]{2,3,5,7,11,13,17,19}; for( int i=0; i<nprimes; i++ ) @@ -263,8 +261,8 @@ return rez; Parser parser = new Parser( """ struct S {int i; flt f;}; -S s1 = new S; -S s2 = new S; +S !s1 = new S; +S !s2 = new S; s2.i = 3; s2.f = 2.0; if (arg) s1 = new S; diff --git a/chapter17/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/chapter17/src/test/java/com/seaofnodes/simple/Chapter16Test.java index d0e30a7c..533241a9 100644 --- a/chapter17/src/test/java/com/seaofnodes/simple/Chapter16Test.java +++ b/chapter17/src/test/java/com/seaofnodes/simple/Chapter16Test.java @@ -114,7 +114,7 @@ Point p = new Point { x=3; y=4; }; return p; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Point;", stop.toString()); + assertEquals("return (const)Point;", stop.toString()); assertEquals("Obj<Point>{x=3,y=4}", Evaluator.evaluate(stop, 0).toString()); } @@ -127,7 +127,7 @@ p.x++; return p; """); try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Cannot reassign final 'x'",e.getMessage()); } + catch( Exception e ) { assertEquals("Cannot modify final field 'x'",e.getMessage()); } } @Test @@ -139,7 +139,7 @@ p.x++; return p; """); try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Cannot reassign final 'x'",e.getMessage()); } + catch( Exception e ) { assertEquals("Cannot modify final field 'x'",e.getMessage()); } } @Test @@ -170,7 +170,7 @@ return p; public void testStructFinal5() { Parser parser = new Parser(""" struct Point { var x=3; var y=4; }; -Point p = new Point; +Point !p = new Point; p.x++; return p; """); @@ -186,7 +186,7 @@ return p; Parser parser = new Parser( """ struct LLI { LLI? next; int i; }; -LLI? head = null; +LLI? !head = null; while( arg ) { head = new LLI { next=head; i=arg; }; arg = arg-1; @@ -208,7 +208,7 @@ return next.i; Parser parser = new Parser( """ struct LLI { LLI? next; int i; }; -LLI? head = null; +LLI? !head = null; while( arg ) { head = new LLI { next=head; diff --git a/chapter17/src/test/java/com/seaofnodes/simple/Chapter17Test.java b/chapter17/src/test/java/com/seaofnodes/simple/Chapter17Test.java index 3773f87f..7de7d514 100644 --- a/chapter17/src/test/java/com/seaofnodes/simple/Chapter17Test.java +++ b/chapter17/src/test/java/com/seaofnodes/simple/Chapter17Test.java @@ -12,13 +12,24 @@ public class Chapter17Test { @Test public void testJig() { Parser parser = new Parser(""" -return 3.14; +i32 v0=0; +if(1<<arg) return 0&0; + int v1=0; + while(0<<0!=1) { + v1=v0; + while(0<--v0) + if(1&v0) continue; + else arg=0; + if(v1) continue; + arg=0; + } """); StopNode stop = parser.parse().iterate(); assertEquals("return 3.14;", stop.toString()); assertEquals(3.14, Evaluator.evaluate(stop, 0)); } + // --------------------------------------------------------------- @Test public void testInc0() { Parser parser = new Parser(""" @@ -53,7 +64,7 @@ return -arg---arg--; @Test public void testInc3() { Parser parser = new Parser(""" -int[] xs = new int[arg]; +int[] !xs = new int[arg]; xs[0]++; xs[1]++; return xs[0]; @@ -66,7 +77,7 @@ return xs[0]; @Test public void testInc4() { Parser parser = new Parser(""" -u8[] xs = new u8[1]; +u8[] !xs = new u8[1]; xs[0]--; return xs[0]; """); @@ -79,7 +90,7 @@ return xs[0]; public void testInc5() { Parser parser = new Parser(""" struct S { u16 x; }; -S s = new S; +S !s = new S; s.x--; return s.x; """); @@ -90,26 +101,134 @@ return s.x; @Test public void testInc6() { - Parser parser = new Parser(""" -return --arg; -"""); + Parser parser = new Parser("return --arg;"); StopNode stop = parser.parse().iterate(); assertEquals("return (arg+-1);", stop.toString()); assertEquals(-1L, Evaluator.evaluate(stop, 0)); } - @Test - public void testVar0() { - Parser parser = new Parser(""" -var d; -return d; -"""); + public void testInc7() { + Parser parser = new Parser("u8 x=0; return --x;"); + StopNode stop = parser.parse().iterate(); + assertEquals("return 255;", stop.toString()); + assertEquals(255L, Evaluator.evaluate(stop, 0)); + } + + // --------------------------------------------------------------- + @Test public void testVar0() { + Parser parser = new Parser("var d; return d;"); try { parser.parse().iterate(); fail(); } catch( Exception e ) { assertEquals("Syntax error, expected expression: ;",e.getMessage()); } } + @Test public void testVar1() { + Parser parser = new Parser("val d; return d;"); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Syntax error, expected expression: ;",e.getMessage()); } + } + + @Test public void testVar2() { + Parser parser = new Parser("int x; x=3; x++; return x; // Ok, no initializer so x is mutable "); + StopNode stop = parser.parse().iterate(); + assertEquals("return 4;", stop.toString()); + assertEquals(4L, Evaluator.evaluate(stop, 0)); + } + + @Test public void testVar3() { + Parser parser = new Parser("int x=3; x++; return x; // Ok, primitive so x is mutable despite initializer "); + StopNode stop = parser.parse().iterate(); + assertEquals("return 4;", stop.toString()); + assertEquals(4L, Evaluator.evaluate(stop, 0)); + } + + @Test public void testVar4() { + Parser parser = new Parser("struct S{int x;}; S s; s=new S; s.x++; return s.x; // Ok, no initializer so x is mutable "); + StopNode stop = parser.parse().iterate(); + assertEquals("return 1;", stop.toString()); + assertEquals(1L, Evaluator.evaluate(stop, 0)); + } + + @Test public void testVar5() { + Parser parser = new Parser("struct S{int x;}; S s; s=new S{x=3;}; s.x++; return s.x; // Ok, no initializer so x is mutable "); + StopNode stop = parser.parse().iterate(); + assertEquals("return 4;", stop.toString()); + assertEquals(4L, Evaluator.evaluate(stop, 0)); + } + + @Test public void testVar6() { + Parser parser = new Parser("struct S{int x;}; S s=new S; s.x++; return s.x; // Error initializer so x is immutable "); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Cannot modify final field 'x'",e.getMessage()); } + } + + @Test public void testVar7() { + Parser parser = new Parser("struct S{int x;}; S s=new S{x=3;}; s.x++; return s.x; // Error initializer so x is immutable "); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Cannot modify final field 'x'",e.getMessage()); } + } + + @Test public void testVar8() { + Parser parser = new Parser("struct S{int x;}; S !s=new S; s.x++; return s.x; // Ok, has '!' so s.x is mutable "); + StopNode stop = parser.parse().iterate(); + assertEquals("return 1;", stop.toString()); + assertEquals(1L, Evaluator.evaluate(stop, 0)); + } + + @Test public void testVar9() { + Parser parser = new Parser("struct S{int x;}; var s=new S; s.x++; return s.x; // Ok, has var so s.x is mutable "); + StopNode stop = parser.parse().iterate(); + assertEquals("return 1;", stop.toString()); + assertEquals(1L, Evaluator.evaluate(stop, 0)); + } + + @Test public void testVar10() { + Parser parser = new Parser("struct S{int x;}; val s=new S; s.x++; return s.x; // Error, has val so x is immutable "); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Cannot modify final field 'x'",e.getMessage()); } + } + + @Test public void testVar11() { + Parser parser = new Parser(""" +struct Bar { int x; }; +Bar !bar = new Bar; +bar.x = 3; // Ok, bar is mutable + +struct Foo { Bar? !bar; int y; }; +Foo !foo = new Foo { bar = bar; }; +foo.bar = bar; // Ok foo is mutable +foo.bar.x++; // Ok foo and foo.bar and foo.bar.x are all mutable + +val xfoo = foo; // Throw away mutability +xfoo.bar.x++; // Error, cannot mutate through xfoo + """); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Cannot modify final field 'x'",e.getMessage()); } + } + + @Test public void testVar12() { + Parser parser = new Parser(""" +struct Bar { int x; }; +Bar !bar = new Bar; +bar.x = 3; // Ok, bar is mutable + +struct Foo { Bar? !bar; int y; }; +Foo !foo = new Foo; +foo.bar = bar; // Ok bar is mutable +foo.bar.x++; // Ok foo and foo.bar and foo.bar.x are all mutable + +val xfoo = foo; // Throw away mutability +int x4 = xfoo.bar.x; // Ok to read through xfoo, gets 4 +foo.bar.x++; // Bumps to 5 +int x5 = xfoo.bar.x; // Ok to read through xfoo, gets 5 +return x4*10+x5; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return 45;", stop.toString()); + assertEquals(45L, Evaluator.evaluate(stop, 0)); + } + // --------------------------------------------------------------- @Test public void testTrinary0() { Parser parser = new Parser(""" @@ -173,6 +292,7 @@ return b ? b.next ? b.next.x : b.x; // parses "b ? (b.next ? b.next.x : b.x) : 0 } + // --------------------------------------------------------------- @Test public void testFor0() { Parser parser = new Parser(""" @@ -240,16 +360,18 @@ return i; } + // --------------------------------------------------------------- @Test public void testLinkedList2() { Parser parser = new Parser(""" struct LLI { LLI? next; int i; }; -LLI? head = null; +LLI? !head = null; while( arg-- ) head = new LLI { next=head; i=arg; }; int sum=0; -for( ; head; head = head.next ) - sum = sum + head.i; +var ptr = head; // A read-only ptr, to be assigned from read-only next fields +for( ; ptr; ptr = ptr.next ) + sum = sum + ptr.i; return sum; """); StopNode stop = parser.parse().iterate(); @@ -261,7 +383,7 @@ return sum; public void sieveOfEratosthenes() { Parser parser = new Parser( """ -val ary = new bool[arg], primes = new int[arg]; +var ary = new bool[arg], primes = new int[arg]; var nprimes=0, p=0; // Find primes while p^2 < arg for( p=2; p*p < arg; p++ ) { @@ -278,7 +400,7 @@ for( ; p < arg; p++ ) if( !ary[p] ) primes[nprimes++] = p; // Copy/shrink the result array -var rez = new int[nprimes]; +var !rez = new int[nprimes]; for( int j=0; j<nprimes; j++ ) rez[j] = primes[j]; return rez; @@ -286,7 +408,7 @@ return rez; StopNode stop = parser.parse().iterate(); assertEquals("return [int];", stop.toString()); Evaluator.Obj obj = (Evaluator.Obj)Evaluator.evaluate(stop, 20); - assertEquals("[int] { # :int; [] :int; }",obj.struct().toString()); + assertEquals("[int] {int #; int ![]; }",obj.struct().toString()); long nprimes = (Long)obj.fields()[0]; long[] primes = new long[]{2,3,5,7,11,13,17,19}; for( int i=0; i<nprimes; i++ ) diff --git a/chapter17/src/test/java/com/seaofnodes/simple/SchedulerTest.java b/chapter17/src/test/java/com/seaofnodes/simple/SchedulerTest.java index 168e0ed7..c67217af 100644 --- a/chapter17/src/test/java/com/seaofnodes/simple/SchedulerTest.java +++ b/chapter17/src/test/java/com/seaofnodes/simple/SchedulerTest.java @@ -24,7 +24,7 @@ public class SchedulerTest { struct S { int f; } -S v=new S; +S !v=new S; v.f = 2; int i=v.f; if (arg) v.f=1; @@ -42,7 +42,7 @@ return i; struct S { int f; }; -S v0=new S; +S !v0=new S; if(arg) v0.f=1; return v0; """); @@ -58,7 +58,7 @@ return v0; struct S { int f; }; -S v=new S; +S !v=new S; v.f = 2; int i=new S.f; i=v.f; diff --git a/chapter17/src/test/java/com/seaofnodes/simple/TypeTest.java b/chapter17/src/test/java/com/seaofnodes/simple/TypeTest.java index 03b0e31d..1cb97cf6 100644 --- a/chapter17/src/test/java/com/seaofnodes/simple/TypeTest.java +++ b/chapter17/src/test/java/com/seaofnodes/simple/TypeTest.java @@ -20,7 +20,7 @@ public class TypeTest { Field.make("b", TypeInteger.BOT,-4, false) }); Assert.assertEquals(s1, s1.glb()); Assert.assertNotEquals(s1, s1.dual()); - Assert.assertEquals(s1, s1.dual().glb()); + Assert.assertEquals(s1.makeRO(), s1.dual().glb()); TypeMem m1 = TypeMem.make(1,TypeInteger.ZERO); TypeMem m2 = TypeMem.make(2,TypeInteger.U16); @@ -57,7 +57,7 @@ public class TypeTest { Assert.assertEquals(ptr1nil, ptr1.glb()); Assert.assertEquals(ptr1, ptr1.dual().dual()); - Assert.assertEquals(ptr1.glb(), ptr1.dual().glb()); + Assert.assertEquals(ptr1.glb().makeRO(), ptr1.dual().glb()); Assert.assertEquals(TypeMemPtr.make(TypeStruct.BOT,true), ptr1.meet(ptr2nil)); Assert.assertEquals(ptr1.glb(), ptr1.meet(TypeMemPtr.NULLPTR)); diff --git a/chapter17/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/chapter17/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java index 37d2a9b1..72f84fa2 100644 --- a/chapter17/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java +++ b/chapter17/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java @@ -308,6 +308,7 @@ public class Evaluator { case CProjNode cproj -> ((Object[])val(cproj.ctrl()))[cproj._idx]; case ProjNode proj -> ((Object[])val( proj.in(0) ))[ proj._idx]; case ScopeMinNode mem -> null; + case ReadOnlyNode ro -> val(ro.in(1)); default -> throw new AssertionError("Unexpected node " + node); }; } |
