summaryrefslogtreecommitdiff
path: root/chapter17
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2024-11-13 13:41:18 -0800
committerCliff Click <cliffc@acm.org>2024-11-23 09:03:20 -0800
commit68cd11f31dd4ba7f089d1a15de5a36ec67043da6 (patch)
tree19adb6cee02232eba346c6aed2d56081556302f2 /chapter17
parent82fb275502f84e17228fc587385eeb76f195a09e (diff)
downloadSimple-68cd11f31dd4ba7f089d1a15de5a36ec67043da6.tar.gz
Simple-68cd11f31dd4ba7f089d1a15de5a36ec67043da6.zip
Final? pass of finals
Add a least-upper-bound to set Phis to their respective Top while preserving final fields. Requires a ReadOnly node to "cast-away-writable". Series of isFinal/makeRO in all Types. Changes tests to match r/w intent.
Diffstat (limited to 'chapter17')
-rw-r--r--chapter17/README.md103
-rw-r--r--chapter17/src/main/java/com/seaofnodes/simple/Parser.java63
-rw-r--r--chapter17/src/main/java/com/seaofnodes/simple/node/LoadNode.java15
-rw-r--r--chapter17/src/main/java/com/seaofnodes/simple/node/PhiNode.java2
-rw-r--r--chapter17/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java29
-rw-r--r--chapter17/src/main/java/com/seaofnodes/simple/node/ScopeMinNode.java4
-rw-r--r--chapter17/src/main/java/com/seaofnodes/simple/node/ScopeNode.java2
-rw-r--r--chapter17/src/main/java/com/seaofnodes/simple/node/StoreNode.java3
-rw-r--r--chapter17/src/main/java/com/seaofnodes/simple/type/Field.java22
-rw-r--r--chapter17/src/main/java/com/seaofnodes/simple/type/Type.java26
-rw-r--r--chapter17/src/main/java/com/seaofnodes/simple/type/TypeFloat.java5
-rw-r--r--chapter17/src/main/java/com/seaofnodes/simple/type/TypeInteger.java1
-rw-r--r--chapter17/src/main/java/com/seaofnodes/simple/type/TypeMem.java8
-rw-r--r--chapter17/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java8
-rw-r--r--chapter17/src/main/java/com/seaofnodes/simple/type/TypeStruct.java53
-rw-r--r--chapter17/src/test/java/com/seaofnodes/simple/BrainFuckTest.java4
-rw-r--r--chapter17/src/test/java/com/seaofnodes/simple/Chapter10Test.java32
-rw-r--r--chapter17/src/test/java/com/seaofnodes/simple/Chapter11Test.java22
-rw-r--r--chapter17/src/test/java/com/seaofnodes/simple/Chapter13Test.java18
-rw-r--r--chapter17/src/test/java/com/seaofnodes/simple/Chapter14Test.java2
-rw-r--r--chapter17/src/test/java/com/seaofnodes/simple/Chapter15Test.java36
-rw-r--r--chapter17/src/test/java/com/seaofnodes/simple/Chapter16Test.java12
-rw-r--r--chapter17/src/test/java/com/seaofnodes/simple/Chapter17Test.java160
-rw-r--r--chapter17/src/test/java/com/seaofnodes/simple/SchedulerTest.java6
-rw-r--r--chapter17/src/test/java/com/seaofnodes/simple/TypeTest.java4
-rw-r--r--chapter17/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java1
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);
};
}