summaryrefslogtreecommitdiff
path: root/chapter29/src/test
diff options
context:
space:
mode:
Diffstat (limited to 'chapter29/src/test')
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/ArithNodeTest.java31
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/BoundaryTest.java5
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/Chapter10Test.java14
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/Chapter12Test.java2
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/Chapter18Test.java2
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/Chapter19Test.java2
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/ConstructionGroupTest.java135
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/ConstructionRequirementsTest.java222
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/ConstructorCompletionTest.java42
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/FinalConstructionTest.java9
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/TypeTest.java4
11 files changed, 443 insertions, 25 deletions
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/ArithNodeTest.java b/chapter29/src/test/java/com/seaofnodes/simple/ArithNodeTest.java
index e912f224..b28a6af3 100644
--- a/chapter29/src/test/java/com/seaofnodes/simple/ArithNodeTest.java
+++ b/chapter29/src/test/java/com/seaofnodes/simple/ArithNodeTest.java
@@ -9,6 +9,29 @@ import org.junit.Test;
import static org.junit.Assert.*;
public class ArithNodeTest {
+ @Test public void testConstantFoldsRequireInitializedInputs() {
+ new CodeGen("return 0;");
+ Node x = ConstantNode.seed(TypeScalar.BOT);
+ Node zero = ConstantNode.seed(TypeInteger.ZERO);
+ for( Node op : new Node[]{new MulNode(x,zero,(byte)1),new AndNode(null,x,zero),
+ new SubNode(x,x,(byte)1),new BoolNode.EQ(x,x,(byte)1),
+ new BoolNode.NE(x,x,(byte)1),new BoolNode.ULT(x,x),new NotNode(x)} ) {
+ assertSame(op.label(),TypeScalar.BOT,op.compute());
+ Node replacement = op.idealize();
+ assertTrue(op.label(),replacement==null || !replacement.compute().isConstant());
+ }
+ // Once the family is known, compute owns the annihilator fold.
+ x._type = TypeInteger.BOT;
+ assertSame(TypeInteger.ZERO,new MulNode(x,zero,(byte)1).compute());
+ assertSame(TypeInteger.TRUE,new BoolNode.EQ(x,x,(byte)1).compute());
+ assertSame(TypeInteger.FALSE,new BoolNode.NE(x,x,(byte)1).compute());
+ assertSame(TypeInteger.FALSE,new BoolNode.ULT(x,x).compute());
+ x._type = TypeFloat.F64;
+ assertSame(TypeInteger.BOOL,new BoolNode.EQ(x,x,(byte)2).compute());
+ x._type = TypeFloat.constant(Double.NaN);
+ assertSame(TypeInteger.FALSE,new BoolNode.EQ(x,x,(byte)2).compute());
+ }
+
@Test public void testUnresolvedInput() {
new CodeGen("return 0;").driver(CodeGen.Phase.Opto);
Node lhs = ConstantNode.seed(TypeInteger.TRUE);
@@ -16,9 +39,9 @@ public class ArithNodeTest {
ArithNode add = new AddNode(lhs,rhs);
assertSame(TypeInteger.constant(2),add.compute());
lhs._type = TypeScalar.BOT;
- assertSame(Type.BOTTOM,add.compute());
+ assertSame(TypeScalar.BOT,add.compute());
add.setMode((byte)1);
- assertSame(Type.BOTTOM,add.compute());
+ assertSame(TypeScalar.BOT,add.compute());
}
@Test public void testFloatPrecision() {
@@ -26,6 +49,10 @@ public class ArithNodeTest {
assertEquals("1.6777217E7",Eval2.eval(code,16777216));
code = new CodeGen("f32 x = arg; f32 y = x+1.0; return y;").driver(CodeGen.Phase.TypeCheck);
assertEquals("1.6777216E7",Eval2.eval(code,16777216));
+ code = new CodeGen("flt x = 0.0; flt y = 0.0; if(arg) { x = 1.5; y = 3.5; } else { x = 2.5; y = 4.5; } return x+y;")
+ .driver(CodeGen.Phase.TypeCheck);
+ assertEquals("7.0",Eval2.eval(code,0));
+ assertEquals("5.0",Eval2.eval(code,1));
}
@Test public void testMonotoneCompute() {
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/BoundaryTest.java b/chapter29/src/test/java/com/seaofnodes/simple/BoundaryTest.java
index de502371..10b7517e 100644
--- a/chapter29/src/test/java/com/seaofnodes/simple/BoundaryTest.java
+++ b/chapter29/src/test/java/com/seaofnodes/simple/BoundaryTest.java
@@ -24,10 +24,7 @@ public class BoundaryTest {
CodeGen code=new CodeGen(src).driver(CodeGen.Phase.LocalSched);
assertEquals("2018",Eval2.eval(code,19));
assertEquals("0",Eval2.eval(code,0));
- code._stop.walk(n -> {
- if( n instanceof StoreNode st ) assertEquals(0,st.cfg0().loopDepth());
- return null;
- });
+ // Private memory follows the loop; stores need not move to its exit.
code=new CodeGen("struct Byte { new Byte={int n -> self.x=n; self.x+=3;}; u8 x; }; return new Byte(255).x;").driver(CodeGen.Phase.TypeCheck);
assertEquals("2",Eval2.eval(code,0));
// data is still unset when the constructor reads and updates x.
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter29/src/test/java/com/seaofnodes/simple/Chapter10Test.java
index 2c9a69a7..1a954110 100644
--- a/chapter29/src/test/java/com/seaofnodes/simple/Chapter10Test.java
+++ b/chapter29/src/test/java/com/seaofnodes/simple/Chapter10Test.java
@@ -158,7 +158,7 @@ else
return v;
""");
code.driver(CodeGen.Phase.FinalizeIR);
- assertEquals("Stop[ return Test.Vector2D; return MEM[ 2:___ 3:.x=0; 4:.y=0;]; return Test.Vector2D; ]", code.print());
+ assertEquals("Stop[ return Test.Vector2D; return MemTop; return Test.Vector2D; ]", code.print());
}
@Test
@@ -196,7 +196,7 @@ while (arg) {
return bar.a;
""");
code.driver(CodeGen.Phase.FinalizeIR);
- assertEquals("Stop[ return Phi(Loop,0,(Phi_a+2)); return MEM[ 2:___ 3:.a=0;]; return Test.Bar; ]", code.print());
+ assertEquals("Stop[ return Phi(Loop,0,(Phi_a+2)); return MemTop; return Test.Bar; ]", code.print());
}
@Test
@@ -318,7 +318,7 @@ while( i.x < i.len ) {
return sum;
""");
code.driver(CodeGen.Phase.FinalizeIR);
- assertEquals("Stop[ return Phi(Loop,0,(Phi(Loop,0,(Phi_x+1))+Phi_sum)); return MEM[ 2:___ 3:.x=0; 4:.len=0;]; return Test.Iter; ]", code.print());
+ assertEquals("Stop[ return Phi(Loop,0,(Phi(Loop,0,(Phi_x+1))+Phi_sum)); return MemTop; return Test.Iter; ]", code.print());
}
@@ -337,7 +337,7 @@ while(arg) {
return ret;
""");
code.driver(CodeGen.Phase.FinalizeIR);
- assertEquals("Stop[ return Phi(Loop,Test.s0,Phi(Region,Test.s0,Phi_ret)); return MEM[ 2:___ 3:.v0=0;]; return Test.s0; ]", code.print());
+ assertEquals("Stop[ return Phi(Loop,Test.s0,Phi(Region,Test.s0,Phi_ret)); return MemTop; return Test.s0; ]", code.print());
}
@Test
@@ -390,7 +390,7 @@ s0 b = new s0();
return a.x * 100 + b.x * 10 + cnt;
""");
code.driver(CodeGen.Phase.FinalizeIR);
- assertEquals("Stop[ return 112; return MEM[ 2:___ 3:___ 4:.x=1;]; return Test.s0; ]", code.print());
+ assertEquals("Stop[ return 112; return MemTop; return Test.s0; ]", code.print());
}
@@ -433,7 +433,7 @@ else return new s0;
if(new s0.f0) return 0;
""");
code.driver(CodeGen.Phase.TypeCheck);
- assertEquals("Stop[ return Test.s0; return MEM[ 2:___ 3:.f0=0;]; return Test.s0; ]", code.print());
+ assertEquals("Stop[ return Test.s0; return MemTop; return Test.s0; ]", code.print());
}
@Test
@@ -461,7 +461,7 @@ s0 v1 = v0;
return v1;
""");
code.driver(CodeGen.Phase.FinalizeIR);
- assertEquals("Stop[ return (const)Test.s0; return MEM[ 2:___ 3:.f0=0;]; return Test.s0; ]", code.print());
+ assertEquals("Stop[ return (const)Test.s0; return MemTop; return Test.s0; ]", code.print());
}
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter29/src/test/java/com/seaofnodes/simple/Chapter12Test.java
index fae7611d..156e091d 100644
--- a/chapter29/src/test/java/com/seaofnodes/simple/Chapter12Test.java
+++ b/chapter29/src/test/java/com/seaofnodes/simple/Chapter12Test.java
@@ -101,7 +101,7 @@ N n = new N(new M);
return n.next;
""");
code.driver(CodeGen.Phase.FinalizeIR);
- assertEquals("Stop[ return (const)Test.M; return MEM[ 2:___ 3:.m=0;]; return Test.M; return MEM[ 2:___ 3:___ 4:___ 5:___ 6:___ 7:.next=Parm_m(Test.N.N,*Test.M {i64 !m; }); 8:.i=0;]; return MEM[ 2:___ 3:___ 4:___ 5:___ 6:___ 7:___ 8:.i=0;]; return Test.N; ]", code.print());
+ assertEquals("Stop[ return (const)Test.M; return MemTop; return Test.M; return MemTop; return MemTop; return Test.N; ]", code.print());
}
@Test
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/Chapter18Test.java b/chapter29/src/test/java/com/seaofnodes/simple/Chapter18Test.java
index e5813eae..ecb2b934 100644
--- a/chapter29/src/test/java/com/seaofnodes/simple/Chapter18Test.java
+++ b/chapter29/src/test/java/com/seaofnodes/simple/Chapter18Test.java
@@ -347,7 +347,7 @@ for(;;) {
}
""");
code.driver(Phase.LocalSched);
- assertEquals("Stop[ return Top; return Parm_i(x,i64); ]", code.print());
+ assertEquals("return Top;", code.print());
assertNull( Eval2.eval( code, 0 ) ); // Infinite loop, <clinit> never exits
}
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/Chapter19Test.java b/chapter29/src/test/java/com/seaofnodes/simple/Chapter19Test.java
index d5fe207a..e6249d82 100644
--- a/chapter29/src/test/java/com/seaofnodes/simple/Chapter19Test.java
+++ b/chapter29/src/test/java/com/seaofnodes/simple/Chapter19Test.java
@@ -118,7 +118,7 @@ val hashCode = { !String self ->
hashCode(new String("Hello, World!"));
""";
CodeGen code = new CodeGen(src).driver(Phase.LocalSched);
- assertEquals("Stop[ return Phi(Region,123456789,(!=0)Phi(Loop,0,(.[]+((Phi_hash<<5)-Phi_hash)))); return MEM[ 2:___ 3:___ 4:___ 5:.cs=Parm_s(Test.String.String,*[]u8[final]); 6:._hashCode=0;]; return MEM[ 2:___ 3:___ 4:___ 5:___ 6:._hashCode=0;]; return Test.String; return Phi(Region,0,1,0,1); return Phi(Region,(!=0)._hashCode,Phi(Region,123456789,(!=0)Phi(Loop,0,(.[]+((Phi_hash<<5)-Phi_hash))))); ]", code.print());
+ assertEquals("Stop[ return Phi(Region,123456789,(!=0)Phi(Loop,0,(.[]+((Phi_hash<<5)-Phi_hash)))); return MemTop; return MemTop; return Test.String; return Phi(Region,0,1,0,1); return Phi(Region,(!=0)._hashCode,Phi(Region,123456789,(!=0)Phi(Loop,0,(.[]+((Phi_hash<<5)-Phi_hash))))); ]", code.print());
assertEquals("4029215624828139541", Eval2.eval(code, 2));
}
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/ConstructionGroupTest.java b/chapter29/src/test/java/com/seaofnodes/simple/ConstructionGroupTest.java
new file mode 100644
index 00000000..cea6ca91
--- /dev/null
+++ b/chapter29/src/test/java/com/seaofnodes/simple/ConstructionGroupTest.java
@@ -0,0 +1,135 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.parser.Parser;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class ConstructionGroupTest {
+ private static final String CHILD = """
+ struct Child {
+ Test.Parent? parent;
+ int value = 5;
+ new Child = { Test.Parent? ^par -> parent = par; };
+ };
+ val read = { Child c -> return c.value; };
+ """;
+
+ private String parent(String body) {
+ return CHILD+"struct Parent { Child child; int~ payload; new Parent = { int n -> "+body+" }; };";
+ }
+
+ private void rejects(String src, String message) {
+ try {
+ new CodeGen(src).parse();
+ fail("Accepted: "+src);
+ } catch( Parser.ParseException e ) {
+ assertTrue(e.getMessage(),e.getMessage().contains(message));
+ }
+ }
+
+ @Test public void testCyclesAndIndependentAllocations() {
+ String src = parent("val detached = new Child(null); payload = n+read(detached); child = new Child(self);")+
+ "val p = new Parent(arg); val local = new Child(p); val par = local.parent; return (par ? par.payload : 0)+p.child.value;";
+ for( long seed : new long[]{1,123,987654321} ) {
+ CodeGen code = new CodeGen(src,seed,true).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("17",Eval2.eval(code,7));
+ assertEquals("10",Eval2.eval(code,0));
+ }
+ new CodeGen(src.replace("Child","Child_noInline"))
+ .driver(CodeGen.Phase.Encoding,TestC.CPU_PORT,TestC.CALL_CONVENTION,true,false,0);
+ }
+
+ @Test public void testNoIncompleteLookupOrEscape() {
+ // This lookup supplies a live result. Inlining must not turn self
+ // complete inside the body before checking the use of child.
+ rejects(parent("child = new Child(self); payload = child.parent==null;")+
+ "return new Parent(arg).payload;","incomplete object");
+ rejects(parent("payload = 1; child = new Child(self); int x = child.parent==null;")+"return 0;","incomplete object");
+ rejects(parent("payload = 1; child = new Child(self); read(child);")+"return 0;","ordinary function");
+ rejects(parent("payload = 1; child = new Child(self);").replace("parent = par;","parent = par; value = par.payload;")+
+ "return 0;","incomplete object");
+ rejects("""
+ struct Box { Test.C? value; }; val box = new Box;
+ struct C { int~ x; new C = { -> box.value = self; x = 1; }; };
+ return 0;
+ ""","ordinary object");
+ rejects("struct C { C next = self; int~ x; new C = { -> int y = next.x; x = 7; }; }; return 0;",
+ "incomplete object");
+ }
+
+ @Test public void testAliasesAndLoopBackedges() {
+ String src = parent("val alias = self; alias.payload = n; child = new Child(alias);")+"return new Parent(arg).child.value;";
+ assertEquals("5",Eval2.eval(new CodeGen(src).driver(CodeGen.Phase.TypeCheck),3));
+ rejects(parent("payload = 1; child = new Child(self); Child? !c = null; while(n--) { if(c) read(c); c = child; }")+
+ "return 0;","ordinary function");
+ rejects(parent("payload = 1; child = new Child(self); Parent? !p = null; while(n--) { val c = new Child(p); read(c); p = self; }")+
+ "return 0;","ordinary function");
+ rejects(parent("payload = 1; child = new Child(self); val c = n ? new Child(null) : child; read(c);")+
+ "return 0;","ordinary function");
+ }
+
+ @Test public void testConstructorOnlyQualifier() {
+ rejects("struct C {}; val f = { C ^c -> return 0; }; return 0;","constructor");
+ rejects("struct C { new C = { int ^x -> }; }; return 0;","struct parameter");
+ rejects(parent("payload = 1; child = new Child(self);").replace("^par","par")+"return 0;","without '^'");
+ CodeGen code = new CodeGen(parent("payload = 1; child = new Child(self);")+"return 0;")
+ .driver(CodeGen.Phase.TypeCheck);
+ try {
+ code.serialize();
+ fail("Exported a constructor qualifier without its contract");
+ } catch( Parser.ParseException e ) {
+ assertTrue(e.getMessage(),e.getMessage().contains("Separate compilation"));
+ }
+ }
+
+ @Test public void testCompleteLoopAllocations() {
+ // Optimistic types prove the cycle p -> new Link(p) -> p complete.
+ String src = """
+ struct Link {
+ Link? next;
+ int value = 5;
+ new Link = { Link? ^n -> next = n; };
+ };
+ struct Root {
+ Link head;
+ int value;
+ new Root = { int n ->
+ Link? !p = null;
+ while(n--) p = new Link(p);
+ if(!p) p = new Link(null);
+ value = p.value;
+ head = p;
+ };
+ };
+ val root = new Root(arg);
+ return root.value + root.head.value;
+ """;
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("10",Eval2.eval(code,0));
+ assertEquals("10",Eval2.eval(code,3));
+ // Dead calls discard their arguments before a construction result exists.
+ assertEquals("0",Eval2.eval(new CodeGen(CHILD+"struct Parent {}; return 0; new Child(null);")
+ .driver(CodeGen.Phase.TypeCheck),0));
+ }
+
+ @Test public void testDeclaredQualifierAndOwnReceiver() {
+ // A complete caller does not grant permission to read a ^ formal.
+ rejects("""
+ struct Parent { int payload = 7; };
+ struct Child {
+ int value;
+ new Child = { Parent ^par -> value = par.payload; };
+ };
+ return new Child(new Parent).value;
+ ""","incomplete object");
+ // The self exception belongs to the receiver, not every pointer of its class.
+ rejects("""
+ struct C {
+ int !value;
+ new C = { !C? ^other -> if(other) other.value = 1; value = 2; };
+ };
+ return new C(new C(null)).value;
+ ""","incomplete object");
+ }
+}
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/ConstructionRequirementsTest.java b/chapter29/src/test/java/com/seaofnodes/simple/ConstructionRequirementsTest.java
new file mode 100644
index 00000000..c570e643
--- /dev/null
+++ b/chapter29/src/test/java/com/seaofnodes/simple/ConstructionRequirementsTest.java
@@ -0,0 +1,222 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.parser.Parser;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+@org.junit.Ignore("Deferred: inferred field requirements and incomplete-object traversal")
+public class ConstructionRequirementsTest {
+ private static final String CHILD = """
+ struct Child {
+ Test.Parent parent;
+ int~ width;
+ new Child = { Test.Parent ^par ->
+ parent = par;
+ width = par.width;
+ };
+ };
+ """;
+
+ private String parent(String body) {
+ return CHILD+"struct Parent { Child child; int~ width; new Parent = { int n -> "+body.replace("arg","n")+" }; };";
+ }
+
+ private void rejects(String src, String message) {
+ try {
+ new CodeGen(src).parse();
+ fail("Accepted: "+src);
+ } catch( Parser.ParseException e ) {
+ assertTrue(e.getMessage(),e.getMessage().contains(message));
+ }
+ }
+
+ @Test public void testCycleAndRequiredField() {
+ String src = parent("width = arg+10; child = new Child(self);")+"return new Parent(arg).child.parent.child.width;";
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("17",Eval2.eval(code,7));
+ assertEquals("10",Eval2.eval(code,0));
+ code = new CodeGen(parent("width = arg+10; child = new Child(self);")+
+ "val p = new Parent(arg); return new Child(p).parent.child.width;").driver(CodeGen.Phase.TypeCheck);
+ assertEquals("17",Eval2.eval(code,7));
+ code = new CodeGen(CHILD+"struct Parent { int width = 10; Child child = new Child(self); }; return new Parent().child.width;")
+ .driver(CodeGen.Phase.TypeCheck);
+ assertEquals("10",Eval2.eval(code,0));
+ rejects(parent("child = new Child(self); width = 10;")+"return 0;","requires initialized field 'width'");
+ rejects(parent("if(arg) width = 10; child = new Child(self); width = 20;")+"return 0;","requires initialized field 'width'");
+ code = new CodeGen(parent("child = new Child(self); width = 10;").replace("width = par.width;","width = 5;")+
+ "return new Parent(arg).child.parent.width;").driver(CodeGen.Phase.TypeCheck);
+ assertEquals("10",Eval2.eval(code,0));
+ // Even if the comparison folds using the non-null declared type,
+ // accessing child still requires that field to have been initialized.
+ rejects(parent("width = 10; child = new Child(self);").replace("width = par.width;","width = par.child==null;")+
+ "return 0;","requires initialized field 'child'");
+ }
+
+ @Test public void testNoEscapeOrFieldLaundering() {
+ rejects("struct C { C next = self; int~ payload; new C = { -> payload = next.payload; }; }; return 0;",
+ "might not be initialized");
+ CodeGen code = new CodeGen("struct C { C next = self; int payload; new C = { -> payload = 17; payload = next.payload; }; }; return new C().next.payload;")
+ .driver(CodeGen.Phase.TypeCheck);
+ assertEquals("17",Eval2.eval(code,0));
+ rejects("""
+ struct C {
+ C next; int~ payload;
+ new C = { -> next = self; payload = next.payload; };
+ }; return 0;
+ ""","might not be initialized");
+ rejects(parent("width = 10; child = new Child(self); use(child);")+
+ "val use = { Child c -> return c.parent.width; }; return 0;","ordinary function");
+ rejects(CHILD+"""
+ struct Parent {
+ Child child; int~ width;
+ new Parent = { -> width = 10; child = new Child(self); int x = child.parent.width; };
+ }; return 0;
+ ""","Traversing an unfinished reference field");
+ rejects("""
+ struct C {
+ int x;
+ val get = { -> return x; };
+ new C = { -> x = get(); };
+ }; return 0;
+ ""","ordinary function");
+ rejects("""
+ struct Box { Test.C? value; }; val box = new Box;
+ struct C { new C = { -> box.value = self; }; }; return 0;
+ ""","ordinary object");
+ }
+
+ @Test public void testConstructorOnlyQualifier() {
+ rejects("struct C {}; val f = { C ^c -> return 0; }; return 0;","constructor");
+ rejects("struct C { new C = { int ^x -> }; }; return 0;","struct parameter");
+ String src = parent("width = 10; child = new Child(self);")+"return 0;";
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
+ try {
+ code.serialize();
+ fail("Exported an unrecorded construction contract");
+ } catch( Parser.ParseException e ) {
+ assertTrue(e.getMessage(),e.getMessage().contains("Separate compilation"));
+ }
+ }
+
+ @Test public void testTransitiveRequirementsAndAliases() {
+ String child = """
+ struct Leaf {
+ int~ width;
+ new Leaf = { Test.Parent ^par -> val alias = par; width = alias.width; };
+ };
+ struct Child {
+ Test.Parent parent; Leaf leaf;
+ new Child = { Test.Parent ^par -> parent = par; leaf = new Leaf(par); };
+ };
+ """;
+ String parent = "struct Parent { Child child; int~ width; new Parent = { int n -> %s }; };";
+ String good = child+parent.formatted("width = n+10; val alias = self; child = new Child(alias);");
+ CodeGen code = new CodeGen(good+"return new Parent(arg).child.leaf.width;").driver(CodeGen.Phase.TypeCheck);
+ assertEquals("17",Eval2.eval(code,7));
+ rejects(child+parent.formatted("child = new Child(self); width = 10;")+"return 0;",
+ "requires initialized field 'width'");
+ }
+
+ @Test public void testLoopsAndUninlinedConstructor() {
+ String src = parent("width = 1; for(int i = 0; i < arg; i++) width += 2; child = new Child(self);")+
+ "return new Parent(arg).child.width;";
+ for( long seed : new long[]{1,123,987654321} ) {
+ CodeGen code = new CodeGen(src.replace("Child","Child_noInline"),seed,true).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("7",Eval2.eval(code,3));
+ assertEquals("1",Eval2.eval(code,0));
+ }
+ new CodeGen(src.replace("Child","Child_noInline"))
+ .driver(CodeGen.Phase.Encoding,TestC.CPU_PORT,TestC.CALL_CONVENTION,true,false,0);
+ rejects(parent("for(int i = 0; i < arg; i++) width = 2; child = new Child(self); width = 10;")+"return 0;",
+ "requires initialized field 'width'");
+ // The assignment on the backedge introduces the unfinished alias
+ // after the call was parsed. It must not pass as an ordinary Child.
+ rejects(parent("width = 10; child = new Child(self); Child? !c = null; while(arg--) { use(c); c = child; }")+
+ "val use = { Child? c -> return 0; }; return 0;","ordinary function");
+ }
+
+ @Test public void testNullableFieldAndDuplicateArguments() {
+ String src = """
+ struct Child {
+ Test.Parent parent; int~ width;
+ new Child = { Test.Parent ^a, Test.Parent ^b ->
+ parent = a; width = a.width+b.width+(a.optional==null);
+ };
+ };
+ struct Parent {
+ Child child; int width; Child? optional;
+ new Parent = { -> width = 10; child = new Child(self,self); };
+ };
+ return new Parent().child.width;
+ """;
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("21",Eval2.eval(code,0));
+ }
+
+ @Test public void testNullableUnfinishedParameter() {
+ String child = """
+ struct Child {
+ Test.Parent? parent; int width;
+ new Child = { Test.Parent? ^par ->
+ parent = par; width = par ? par.width : 0;
+ };
+ };
+ val read = { Child c -> return c.width; };
+ """;
+ String parent = """
+ struct Parent {
+ Child child; int~ width;
+ new Parent = { int n ->
+ val detached = new Child(null);
+ width = n+read(detached);
+ child = new Child(self);
+ };
+ };
+ """;
+ CodeGen code = new CodeGen(child+parent+"""
+ val p = new Parent(arg);
+ val leaf = new Child(p);
+ return read(leaf)+read(p.child);
+ """).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("14",Eval2.eval(code,7));
+ assertEquals("0",Eval2.eval(code,0));
+ rejects(child+parent.replace("width = n+read(detached);","").replace("child = new Child(self);",
+ "child = new Child(self); width = n;"),"requires initialized field 'width'");
+ rejects(child+parent.replace("child = new Child(self);","child = new Child(self); read(child);"),
+ "ordinary function");
+ try {
+ new CodeGen(child.replace("par ? par.width : 0","par.width")+parent+
+ "return new Child(null).width;").driver(CodeGen.Phase.TypeCheck);
+ fail("Dereferenced null constructor argument");
+ } catch( Parser.ParseException e ) {
+ assertTrue(e.getMessage(),e.getMessage().toLowerCase().contains("null"));
+ }
+ }
+
+ @Test public void testDistinctCallTimeMemory() {
+ String src = CHILD+"""
+ struct Parent {
+ Child child; Child second; int~ width;
+ new Parent = { int n ->
+ width = n+1; child = new Child(self);
+ width = n+2; second = new Child(self);
+ width = n+3;
+ };
+ };
+ val p = new Parent(arg); return p.child.width*100+p.second.width*10+p.width;
+ """;
+ for( long seed : new long[]{1,123,987654321} ) {
+ CodeGen code = new CodeGen(src.replace("Child","Child_noInline"),seed,true).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("123",Eval2.eval(code,0));
+ assertEquals("456",Eval2.eval(code,3));
+ }
+ new CodeGen(src.replace("Child","Child_noInline"))
+ .driver(CodeGen.Phase.Encoding,TestC.CPU_PORT,TestC.CALL_CONVENTION,true,false,0);
+ String loop = parent("width = 1; child = new Child(self); for(int i = 0; i < arg; i++) { width += 1; child = new Child(self); } width += 10;")+
+ "val p = new Parent(arg); return p.child.width*100+p.width;";
+ CodeGen code = new CodeGen(loop.replace("Child","Child_noInline")).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("414",Eval2.eval(code,3));
+ assertEquals("111",Eval2.eval(code,0));
+ }
+}
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/ConstructorCompletionTest.java b/chapter29/src/test/java/com/seaofnodes/simple/ConstructorCompletionTest.java
index 6a229a4f..cbb9ee37 100644
--- a/chapter29/src/test/java/com/seaofnodes/simple/ConstructorCompletionTest.java
+++ b/chapter29/src/test/java/com/seaofnodes/simple/ConstructorCompletionTest.java
@@ -6,6 +6,48 @@ import org.junit.Test;
import static org.junit.Assert.*;
public class ConstructorCompletionTest {
+ @Test public void testFieldMemoryDuringInitialization() {
+ CodeGen code = new CodeGen("""
+ struct A {
+ int x = 1;
+ x = 2;
+ int y = self.x;
+ new A = { -> self.x = 3; y += x; };
+ };
+ val a = new A; return a.x*10+a.y;
+ """).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("35",Eval2.eval(code,0));
+ code = new CodeGen("""
+ struct A {
+ int[] data = new int[3];
+ new A = { -> for(int i = 0; i < 3; i++) data[i] = i+1; };
+ };
+ return new A().data[2];
+ """).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("3",Eval2.eval(code,0));
+ for( String body : new String[]{
+ "int~ x; int y = x; new A = { -> x = 7; };",
+ "int~ x; int y = x*0; new A = { -> x = 7; };",
+ // Constructor entry includes the initializer's early return.
+ // Its later Store of 2 must not excuse the earlier read of x.
+ "int~ x; if(arg) return 0; x = 1; new A = { -> int y = x; x = 2; };"
+ } )
+ try {
+ new CodeGen("struct A { "+body+" }; return 0;").parse();
+ fail("Read a field before its constructor wrote it");
+ } catch( Parser.ParseException e ) {
+ assertTrue(e.getMessage(),e.getMessage().contains("might not be initialized"));
+ }
+ }
+
+ @Test public void testRequiredNumberBesideNullableDefault() {
+ CodeGen code = new CodeGen("""
+ struct A { int~ x; A? p; new A = { -> x = 10; }; };
+ return new A().x;
+ """).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("10",Eval2.eval(code,0));
+ }
+
private void rejects(String fields, String field, boolean explicit) {
String src = "struct B {}; struct A { "+fields+" }; return 0;";
try {
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/FinalConstructionTest.java b/chapter29/src/test/java/com/seaofnodes/simple/FinalConstructionTest.java
index 38b94693..66fdacc3 100644
--- a/chapter29/src/test/java/com/seaofnodes/simple/FinalConstructionTest.java
+++ b/chapter29/src/test/java/com/seaofnodes/simple/FinalConstructionTest.java
@@ -2,7 +2,6 @@ package com.seaofnodes.simple;
import com.seaofnodes.simple.parser.Parser;
import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.node.StoreNode;
import org.junit.Test;
import static org.junit.Assert.*;
@@ -20,16 +19,12 @@ public class FinalConstructionTest {
return new C(arg).get();
""";
- @Test public void testFinalConstructorLocals() throws java.io.IOException {
+ @Test public void testFinalConstructorFields() throws java.io.IOException {
var code=new CodeGen(LOOP).driver(CodeGen.Phase.LocalSched);
assertEquals("1",Eval2.eval(code,0));
assertEquals("15",Eval2.eval(code,3));
assertEquals("63",Eval2.eval(code,5));
- code._stop.walk(n -> {
- if( n instanceof StoreNode st && st._name.equals("x") )
- assertEquals("Final field writes belong after the loop",0,st.cfg0().loopDepth());
- return null;
- });
+ // Writes are ordinary Stores and may remain inside the loop.
String[] sources={
"struct C { val x=1; new C={->x=1; x=2;}; }; return new C().x;",
"struct C { int ~x; new C={int n -> self.x=1; for(int i=0;i<n;i++) self.x*=2;}; }; return new C(arg).x;",
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/TypeTest.java b/chapter29/src/test/java/com/seaofnodes/simple/TypeTest.java
index b4055eef..68239396 100644
--- a/chapter29/src/test/java/com/seaofnodes/simple/TypeTest.java
+++ b/chapter29/src/test/java/com/seaofnodes/simple/TypeTest.java
@@ -147,7 +147,7 @@ public class TypeTest {
Assert.assertNotEquals(ptr1, ptr2);
Assert.assertEquals(ptr1, ptr1.dual().dual());
- Assert.assertEquals(TypeMemPtr.makeNullable(TypeStruct.BOT).withAccess(false), ptr1.meet(ptr2nil));
+ Assert.assertEquals(TypeMemPtr.makeNullable(TypeStruct.BOT).withAccess(false).withPartial(false), ptr1.meet(ptr2nil));
TypeMemPtr TOP = TypeMemPtr.TOP;
TypeMemPtr BOT = TypeMemPtr.makeNullable(TypeStruct.BOT);
@@ -156,7 +156,7 @@ public class TypeTest {
Type PTR_meet_NULL = NULL.meet(PTR);
Assert.assertEquals(BOT, PTR_meet_NULL);
Type ptr1_meet_ptr2 = ptr1.meet(ptr2);
- Assert.assertEquals(PTR.withAccess(false), ptr1_meet_ptr2);
+ Assert.assertEquals(PTR.withAccess(false).withPartial(false), ptr1_meet_ptr2);
Type NULL_join_ptr1 = NULL.join(ptr1);
Assert.assertEquals(TypePtr.XNPTR, NULL_join_ptr1);
Type NULL_join_PTR = PTR.join(NULL);