summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--chapter10/src/test/java/com/seaofnodes/simple/Chapter10Test.java6
-rw-r--r--chapter11/src/test/java/com/seaofnodes/simple/Chapter10Test.java6
-rw-r--r--chapter12/src/test/java/com/seaofnodes/simple/Chapter10Test.java6
-rw-r--r--chapter13/src/test/java/com/seaofnodes/simple/Chapter10Test.java6
-rw-r--r--chapter14/src/test/java/com/seaofnodes/simple/Chapter10Test.java6
-rw-r--r--chapter15/src/test/java/com/seaofnodes/simple/Chapter10Test.java6
-rw-r--r--chapter16/src/test/java/com/seaofnodes/simple/Chapter10Test.java6
-rw-r--r--chapter16/src/test/java/com/seaofnodes/simple/Chapter16Test.java9
-rw-r--r--chapter17/src/main/java/com/seaofnodes/simple/Parser.java2
-rw-r--r--chapter17/src/test/java/com/seaofnodes/simple/Chapter10Test.java6
-rw-r--r--chapter17/src/test/java/com/seaofnodes/simple/Chapter16Test.java13
-rw-r--r--chapter18/src/main/java/com/seaofnodes/simple/node/StoreNode.java2
-rw-r--r--chapter18/src/test/java/com/seaofnodes/simple/Chapter10Test.java6
-rw-r--r--chapter18/src/test/java/com/seaofnodes/simple/Chapter16Test.java13
-rw-r--r--chapter19/src/main/java/com/seaofnodes/simple/node/LoadNode.java1
-rw-r--r--chapter19/src/main/java/com/seaofnodes/simple/node/StoreNode.java2
-rw-r--r--chapter19/src/test/java/com/seaofnodes/simple/Chapter10Test.java6
-rw-r--r--chapter19/src/test/java/com/seaofnodes/simple/Chapter16Test.java13
-rw-r--r--chapter20/src/main/java/com/seaofnodes/simple/node/LoadNode.java1
-rw-r--r--chapter20/src/main/java/com/seaofnodes/simple/node/StoreNode.java2
-rw-r--r--chapter20/src/test/java/com/seaofnodes/simple/Chapter10Test.java6
-rw-r--r--chapter20/src/test/java/com/seaofnodes/simple/Chapter16Test.java13
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/LoadNode.java1
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/StoreNode.java2
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/Chapter10Test.java6
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/Chapter16Test.java13
-rw-r--r--chapter22/src/main/java/com/seaofnodes/simple/node/LoadNode.java1
-rw-r--r--chapter22/src/main/java/com/seaofnodes/simple/node/StoreNode.java2
-rw-r--r--chapter22/src/test/java/com/seaofnodes/simple/Chapter10Test.java6
-rw-r--r--chapter22/src/test/java/com/seaofnodes/simple/Chapter16Test.java13
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/LoadNode.java1
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/StoreNode.java2
-rw-r--r--chapter23/src/test/java/com/seaofnodes/simple/Chapter10Test.java6
-rw-r--r--chapter23/src/test/java/com/seaofnodes/simple/Chapter16Test.java13
-rw-r--r--chapter24/src/main/java/com/seaofnodes/simple/node/ArithFNode.java2
-rw-r--r--chapter24/src/main/java/com/seaofnodes/simple/node/ArithNode.java4
-rw-r--r--chapter24/src/main/java/com/seaofnodes/simple/node/LoadNode.java1
-rw-r--r--chapter24/src/test/java/com/seaofnodes/simple/Chapter10Test.java6
-rw-r--r--chapter24/src/test/java/com/seaofnodes/simple/Chapter16Test.java13
-rw-r--r--chapter25/docs/examples/BubbleSort.smp2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/IterPeeps.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/Parser.java60
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ArithNode.java9
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java46
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/Node.java12
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java3
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java2
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter10Test.java1
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter15Test.java2
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter17Test.java41
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter20Test.java7
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java10
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter25Test.java11
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/FuzzerWrap.java2
-rw-r--r--docs/chapter-backports.md45
-rw-r--r--skills/chapter25-codex-notes.md6
57 files changed, 318 insertions, 174 deletions
diff --git a/chapter10/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter10/src/test/java/com/seaofnodes/simple/Chapter10Test.java
index 527999c2..da9a4c5f 100644
--- a/chapter10/src/test/java/com/seaofnodes/simple/Chapter10Test.java
+++ b/chapter10/src/test/java/com/seaofnodes/simple/Chapter10Test.java
@@ -53,6 +53,7 @@ public class Chapter10Test {
public void testNullGuardErrors() {
for( String body : new String[] {
"return p.x;",
+ "if (p == null) return p.x; return -1;",
"if (!!point) return p.x; return -1;",
"if (!!p) { int x = p.x; } return p.x;"
} ) {
@@ -60,7 +61,10 @@ public class Chapter10Test {
new Parser(NULLABLE_POINT_SOURCE+body).parse().iterate();
fail(body);
} catch( RuntimeException e ) {
- assertEquals(body,"Might be null accessing 'x'",e.getMessage());
+ // Known null is rejected during parsing in these chapters.
+ String expected = body.startsWith("if (p == null)")
+ ? "Accessing unknown field 'x' from 'null'" : "Might be null accessing 'x'";
+ assertEquals(body,expected,e.getMessage());
}
}
}
diff --git a/chapter11/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter11/src/test/java/com/seaofnodes/simple/Chapter10Test.java
index 3e97e8e7..8dc489f0 100644
--- a/chapter11/src/test/java/com/seaofnodes/simple/Chapter10Test.java
+++ b/chapter11/src/test/java/com/seaofnodes/simple/Chapter10Test.java
@@ -48,6 +48,7 @@ public class Chapter10Test {
public void testNullGuardErrors() {
for( String body : new String[] {
"return p.x;",
+ "if (p == null) return p.x; return -1;",
"if (!!point) return p.x; return -1;",
"if (!!p) { int x = p.x; } return p.x;"
} ) {
@@ -55,7 +56,10 @@ public class Chapter10Test {
new Parser(NULLABLE_POINT_SOURCE+body).parse().iterate();
fail(body);
} catch( RuntimeException e ) {
- assertEquals(body,"Might be null accessing 'x'",e.getMessage());
+ // Known null is rejected during parsing in these chapters.
+ String expected = body.startsWith("if (p == null)")
+ ? "Accessing unknown field 'x' from 'null'" : "Might be null accessing 'x'";
+ assertEquals(body,expected,e.getMessage());
}
}
}
diff --git a/chapter12/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter12/src/test/java/com/seaofnodes/simple/Chapter10Test.java
index fae91067..9ac1f1e6 100644
--- a/chapter12/src/test/java/com/seaofnodes/simple/Chapter10Test.java
+++ b/chapter12/src/test/java/com/seaofnodes/simple/Chapter10Test.java
@@ -53,6 +53,7 @@ public class Chapter10Test {
public void testNullGuardErrors() {
for( String body : new String[] {
"return p.x;",
+ "if (p == null) return p.x; return -1;",
"if (!!point) return p.x; return -1;",
"if (!!p) { int x = p.x; } return p.x;"
} ) {
@@ -60,7 +61,10 @@ public class Chapter10Test {
new Parser(NULLABLE_POINT_SOURCE+body).parse().iterate();
fail(body);
} catch( RuntimeException e ) {
- assertEquals(body,"Might be null accessing 'x'",e.getMessage());
+ // Known null is rejected during parsing in these chapters.
+ String expected = body.startsWith("if (p == null)")
+ ? "Accessing unknown field 'x' from 'null'" : "Might be null accessing 'x'";
+ assertEquals(body,expected,e.getMessage());
}
}
}
diff --git a/chapter13/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter13/src/test/java/com/seaofnodes/simple/Chapter10Test.java
index cfe677ec..dddddf13 100644
--- a/chapter13/src/test/java/com/seaofnodes/simple/Chapter10Test.java
+++ b/chapter13/src/test/java/com/seaofnodes/simple/Chapter10Test.java
@@ -48,6 +48,7 @@ public class Chapter10Test {
public void testNullGuardErrors() {
for( String body : new String[] {
"return p.x;",
+ "if (p == null) return p.x; return -1;",
"if (!!point) return p.x; return -1;",
"if (!!p) { int x = p.x; } return p.x;"
} ) {
@@ -55,7 +56,10 @@ public class Chapter10Test {
new Parser(NULLABLE_POINT_SOURCE+body).parse().iterate();
fail(body);
} catch( RuntimeException e ) {
- assertEquals(body,"Might be null accessing 'x'",e.getMessage());
+ // Known null is rejected during parsing in these chapters.
+ String expected = body.startsWith("if (p == null)")
+ ? "Accessing unknown field 'x' from 'null'" : "Might be null accessing 'x'";
+ assertEquals(body,expected,e.getMessage());
}
}
}
diff --git a/chapter14/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter14/src/test/java/com/seaofnodes/simple/Chapter10Test.java
index cfe677ec..dddddf13 100644
--- a/chapter14/src/test/java/com/seaofnodes/simple/Chapter10Test.java
+++ b/chapter14/src/test/java/com/seaofnodes/simple/Chapter10Test.java
@@ -48,6 +48,7 @@ public class Chapter10Test {
public void testNullGuardErrors() {
for( String body : new String[] {
"return p.x;",
+ "if (p == null) return p.x; return -1;",
"if (!!point) return p.x; return -1;",
"if (!!p) { int x = p.x; } return p.x;"
} ) {
@@ -55,7 +56,10 @@ public class Chapter10Test {
new Parser(NULLABLE_POINT_SOURCE+body).parse().iterate();
fail(body);
} catch( RuntimeException e ) {
- assertEquals(body,"Might be null accessing 'x'",e.getMessage());
+ // Known null is rejected during parsing in these chapters.
+ String expected = body.startsWith("if (p == null)")
+ ? "Accessing unknown field 'x' from 'null'" : "Might be null accessing 'x'";
+ assertEquals(body,expected,e.getMessage());
}
}
}
diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter10Test.java
index 589ae97f..bd56a944 100644
--- a/chapter15/src/test/java/com/seaofnodes/simple/Chapter10Test.java
+++ b/chapter15/src/test/java/com/seaofnodes/simple/Chapter10Test.java
@@ -49,6 +49,7 @@ public class Chapter10Test {
public void testNullGuardErrors() {
for( String body : new String[] {
"return p.x;",
+ "if (p == null) return p.x; return -1;",
"if (!!points) return p.x; return -1;",
"if (!!p) { int x = p.x; } return p.x;"
} ) {
@@ -56,7 +57,10 @@ public class Chapter10Test {
new Parser(NULLABLE_POINT_SOURCE+body).parse().iterate();
fail(body);
} catch( RuntimeException e ) {
- assertEquals(body,"Might be null accessing 'x'",e.getMessage());
+ // Known null is rejected during parsing in these chapters.
+ String expected = body.startsWith("if (p == null)")
+ ? "Accessing unknown field 'x' from 'null'" : "Might be null accessing 'x'";
+ assertEquals(body,expected,e.getMessage());
}
}
}
diff --git a/chapter16/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter16/src/test/java/com/seaofnodes/simple/Chapter10Test.java
index 2b07234f..408a4814 100644
--- a/chapter16/src/test/java/com/seaofnodes/simple/Chapter10Test.java
+++ b/chapter16/src/test/java/com/seaofnodes/simple/Chapter10Test.java
@@ -48,6 +48,7 @@ public class Chapter10Test {
public void testNullGuardErrors() {
for( String body : new String[] {
"return p.x;",
+ "if (p == null) return p.x; return -1;",
"if (!!points) return p.x; return -1;",
"if (!!p) { int x = p.x; } return p.x;"
} ) {
@@ -55,7 +56,10 @@ public class Chapter10Test {
new Parser(NULLABLE_POINT_SOURCE+body).parse().iterate();
fail(body);
} catch( RuntimeException e ) {
- assertEquals(body,"Might be null accessing 'x'",e.getMessage());
+ // Known null is rejected during parsing in these chapters.
+ String expected = body.startsWith("if (p == null)")
+ ? "Accessing unknown field 'x' from 'null'" : "Might be null accessing 'x'";
+ assertEquals(body,expected,e.getMessage());
}
}
}
diff --git a/chapter16/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/chapter16/src/test/java/com/seaofnodes/simple/Chapter16Test.java
index ad287e82..6fb41233 100644
--- a/chapter16/src/test/java/com/seaofnodes/simple/Chapter16Test.java
+++ b/chapter16/src/test/java/com/seaofnodes/simple/Chapter16Test.java
@@ -9,6 +9,15 @@ import static org.junit.Assert.fail;
public class Chapter16Test {
@Test
+ public void testStructFinal3() {
+ for( String fields : new String[] { "int[] x;", "int[] !x;" } ) {
+ String src = "struct Point { "+fields+" }; Point p = new Point; p.x++; return p;";
+ try { new Parser(src).parse().iterate(); fail(fields); }
+ catch( Exception e ) { assertEquals("'Point' is not fully initialized, field 'x' needs to be set in a constructor",e.getMessage()); }
+ }
+ }
+
+ @Test
public void testJig() {
Parser parser = new Parser(
"""
diff --git a/chapter17/src/main/java/com/seaofnodes/simple/Parser.java b/chapter17/src/main/java/com/seaofnodes/simple/Parser.java
index 07312396..86b59dc3 100644
--- a/chapter17/src/main/java/com/seaofnodes/simple/Parser.java
+++ b/chapter17/src/main/java/com/seaofnodes/simple/Parser.java
@@ -992,7 +992,7 @@ public class Parser {
}
// Check that all fields are initialized
for( int i=idx; i<init.size(); i++ )
- if( /*init.at(i)._type == Type.TOP ||*/ init.at(i)._type == Type.BOTTOM )
+ if( init.at(i)._type == Type.TOP || init.at(i)._type == Type.BOTTOM )
throw error("'"+tmp._obj._name+"' is not fully initialized, field '" + fs[i-idx]._fname + "' needs to be set in a constructor");
Node ptr = newStruct(tmp._obj, con(tmp._obj.offset(fs.length)), idx, init );
if( hasConstructor )
diff --git a/chapter17/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter17/src/test/java/com/seaofnodes/simple/Chapter10Test.java
index 089649bf..e3d8b177 100644
--- a/chapter17/src/test/java/com/seaofnodes/simple/Chapter10Test.java
+++ b/chapter17/src/test/java/com/seaofnodes/simple/Chapter10Test.java
@@ -48,6 +48,7 @@ public class Chapter10Test {
public void testNullGuardErrors() {
for( String body : new String[] {
"return p.x;",
+ "if (p == null) return p.x; return -1;",
"if (!!points) return p.x; return -1;",
"if (!!p) { int x = p.x; } return p.x;"
} ) {
@@ -55,7 +56,10 @@ public class Chapter10Test {
new Parser(NULLABLE_POINT_SOURCE+body).parse().iterate();
fail(body);
} catch( RuntimeException e ) {
- assertEquals(body,"Might be null accessing 'x'",e.getMessage());
+ // Known null is rejected during parsing in these chapters.
+ String expected = body.startsWith("if (p == null)")
+ ? "Accessing unknown field 'x' from 'null'" : "Might be null accessing 'x'";
+ assertEquals(body,expected,e.getMessage());
}
}
}
diff --git a/chapter17/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/chapter17/src/test/java/com/seaofnodes/simple/Chapter16Test.java
index 5450824a..36510737 100644
--- a/chapter17/src/test/java/com/seaofnodes/simple/Chapter16Test.java
+++ b/chapter17/src/test/java/com/seaofnodes/simple/Chapter16Test.java
@@ -144,14 +144,11 @@ return p;
@Test
public void testStructFinal3() {
- Parser parser = new Parser("""
-struct Point { var x; var y; };
-Point p = new Point;
-p.x++;
-return p;
-""");
- try { parser.parse().iterate(); fail(); }
- catch( Exception e ) { assertEquals("'Point' is not fully initialized, field 'x' needs to be set in a constructor",e.getMessage()); }
+ for( String fields : new String[] { "var x; var y;", "int[] x;", "int[] !x;" } ) {
+ String src = "struct Point { "+fields+" }; Point p = new Point; p.x++; return p;";
+ try { new Parser(src).parse().iterate(); fail(fields); }
+ catch( Exception e ) { assertEquals("'Point' is not fully initialized, field 'x' needs to be set in a constructor",e.getMessage()); }
+ }
}
@Test
diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/StoreNode.java
index 8f0725bb..870b8447 100644
--- a/chapter18/src/main/java/com/seaofnodes/simple/node/StoreNode.java
+++ b/chapter18/src/main/java/com/seaofnodes/simple/node/StoreNode.java
@@ -116,6 +116,8 @@ public class StoreNode extends MemOpNode {
public Parser.ParseException err() {
Parser.ParseException err = super.err();
if( err != null ) return err;
+ if( ptr()._type == Type.TOP )
+ return null; // Dead store
TypeMemPtr tmp = (TypeMemPtr)ptr()._type;
if( tmp._obj.field(_name)._final && !"[]".equals(_name) )
return Parser.error("Cannot modify final field '"+_name+"'",_loc);
diff --git a/chapter18/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter18/src/test/java/com/seaofnodes/simple/Chapter10Test.java
index 378e4f40..daeea36d 100644
--- a/chapter18/src/test/java/com/seaofnodes/simple/Chapter10Test.java
+++ b/chapter18/src/test/java/com/seaofnodes/simple/Chapter10Test.java
@@ -46,6 +46,7 @@ public class Chapter10Test {
public void testNullGuardErrors() {
for( String body : new String[] {
"return p.x;",
+ "if (p == null) return p.x; return -1;",
"if (!!points) return p.x; return -1;",
"if (!!p) { int x = p.x; } return p.x;"
} ) {
@@ -53,7 +54,10 @@ public class Chapter10Test {
new CodeGen(NULLABLE_POINT_SOURCE+body).parse().opto().typeCheck();
fail(body);
} catch( RuntimeException e ) {
- assertEquals(body,"Might be null accessing 'x'",e.getMessage());
+ // Known null is rejected during parsing in these chapters.
+ String expected = body.startsWith("if (p == null)")
+ ? "Accessing unknown field 'x' from 'null'" : "Might be null accessing 'x'";
+ assertEquals(body,expected,e.getMessage());
}
}
}
diff --git a/chapter18/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/chapter18/src/test/java/com/seaofnodes/simple/Chapter16Test.java
index ad3dc690..83d34b2e 100644
--- a/chapter18/src/test/java/com/seaofnodes/simple/Chapter16Test.java
+++ b/chapter18/src/test/java/com/seaofnodes/simple/Chapter16Test.java
@@ -144,14 +144,11 @@ return p;
@Test
public void testStructFinal3() {
- CodeGen code = new CodeGen("""
-struct Point { var x; var y; };
-Point p = new Point;
-p.x++;
-return p;
-""");
- try { code.parse().opto(); fail(); }
- catch( Exception e ) { assertEquals("'Point' is not fully initialized, field 'x' needs to be set in a constructor",e.getMessage()); }
+ for( String fields : new String[] { "var x; var y;", "int[] x;", "int[] !x;" } ) {
+ String src = "struct Point { "+fields+" }; Point p = new Point; p.x++; return p;";
+ try { new CodeGen(src).parse().opto(); fail(fields); }
+ catch( Exception e ) { assertEquals("'Point' is not fully initialized, field 'x' needs to be set in a constructor",e.getMessage()); }
+ }
}
@Test
diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/LoadNode.java
index c6c37d1a..15c5664d 100644
--- a/chapter19/src/main/java/com/seaofnodes/simple/node/LoadNode.java
+++ b/chapter19/src/main/java/com/seaofnodes/simple/node/LoadNode.java
@@ -139,6 +139,7 @@ public class LoadNode extends MemOpNode {
private Node zero(NewNode nnn) {
TypeStruct ts = nnn._ptr._obj;
Type zero = ts._fields[ts.findAlias(_alias)]._type.makeZero();
+ assert zero.isa(_type); // Catch an uninitialized non-null field
return castRO(new ConstantNode(zero).peephole());
}
diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/StoreNode.java
index 4e592235..cb374c58 100644
--- a/chapter19/src/main/java/com/seaofnodes/simple/node/StoreNode.java
+++ b/chapter19/src/main/java/com/seaofnodes/simple/node/StoreNode.java
@@ -101,6 +101,8 @@ public class StoreNode extends MemOpNode {
public Parser.ParseException err() {
Parser.ParseException err = super.err();
if( err != null ) return err;
+ if( ptr()._type == Type.TOP )
+ return null; // Dead store
TypeMemPtr tmp = (TypeMemPtr)ptr()._type;
if( tmp._obj.field(_name)._final && !_init )
return Parser.error("Cannot modify final field '"+_name+"'",_loc);
diff --git a/chapter19/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter19/src/test/java/com/seaofnodes/simple/Chapter10Test.java
index 378e4f40..daeea36d 100644
--- a/chapter19/src/test/java/com/seaofnodes/simple/Chapter10Test.java
+++ b/chapter19/src/test/java/com/seaofnodes/simple/Chapter10Test.java
@@ -46,6 +46,7 @@ public class Chapter10Test {
public void testNullGuardErrors() {
for( String body : new String[] {
"return p.x;",
+ "if (p == null) return p.x; return -1;",
"if (!!points) return p.x; return -1;",
"if (!!p) { int x = p.x; } return p.x;"
} ) {
@@ -53,7 +54,10 @@ public class Chapter10Test {
new CodeGen(NULLABLE_POINT_SOURCE+body).parse().opto().typeCheck();
fail(body);
} catch( RuntimeException e ) {
- assertEquals(body,"Might be null accessing 'x'",e.getMessage());
+ // Known null is rejected during parsing in these chapters.
+ String expected = body.startsWith("if (p == null)")
+ ? "Accessing unknown field 'x' from 'null'" : "Might be null accessing 'x'";
+ assertEquals(body,expected,e.getMessage());
}
}
}
diff --git a/chapter19/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/chapter19/src/test/java/com/seaofnodes/simple/Chapter16Test.java
index ad3dc690..83d34b2e 100644
--- a/chapter19/src/test/java/com/seaofnodes/simple/Chapter16Test.java
+++ b/chapter19/src/test/java/com/seaofnodes/simple/Chapter16Test.java
@@ -144,14 +144,11 @@ return p;
@Test
public void testStructFinal3() {
- CodeGen code = new CodeGen("""
-struct Point { var x; var y; };
-Point p = new Point;
-p.x++;
-return p;
-""");
- try { code.parse().opto(); fail(); }
- catch( Exception e ) { assertEquals("'Point' is not fully initialized, field 'x' needs to be set in a constructor",e.getMessage()); }
+ for( String fields : new String[] { "var x; var y;", "int[] x;", "int[] !x;" } ) {
+ String src = "struct Point { "+fields+" }; Point p = new Point; p.x++; return p;";
+ try { new CodeGen(src).parse().opto(); fail(fields); }
+ catch( Exception e ) { assertEquals("'Point' is not fully initialized, field 'x' needs to be set in a constructor",e.getMessage()); }
+ }
}
@Test
diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/LoadNode.java
index df362850..94c3e337 100644
--- a/chapter20/src/main/java/com/seaofnodes/simple/node/LoadNode.java
+++ b/chapter20/src/main/java/com/seaofnodes/simple/node/LoadNode.java
@@ -139,6 +139,7 @@ public class LoadNode extends MemOpNode {
private Node zero(NewNode nnn) {
TypeStruct ts = nnn._ptr._obj;
Type zero = ts._fields[ts.findAlias(_alias)]._type.makeZero();
+ assert zero.isa(_type); // Catch an uninitialized non-null field
return castRO(new ConstantNode(zero).peephole());
}
diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/StoreNode.java
index 4e592235..cb374c58 100644
--- a/chapter20/src/main/java/com/seaofnodes/simple/node/StoreNode.java
+++ b/chapter20/src/main/java/com/seaofnodes/simple/node/StoreNode.java
@@ -101,6 +101,8 @@ public class StoreNode extends MemOpNode {
public Parser.ParseException err() {
Parser.ParseException err = super.err();
if( err != null ) return err;
+ if( ptr()._type == Type.TOP )
+ return null; // Dead store
TypeMemPtr tmp = (TypeMemPtr)ptr()._type;
if( tmp._obj.field(_name)._final && !_init )
return Parser.error("Cannot modify final field '"+_name+"'",_loc);
diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter10Test.java
index 58ca9f5a..14cf6e01 100644
--- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter10Test.java
+++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter10Test.java
@@ -47,6 +47,7 @@ public class Chapter10Test {
public void testNullGuardErrors() {
for( String body : new String[] {
"return p.x;",
+ "if (p == null) return p.x; return -1;",
"if (!!points) return p.x; return -1;",
"if (!!p) { int x = p.x; } return p.x;"
} ) {
@@ -54,7 +55,10 @@ public class Chapter10Test {
new CodeGen(NULLABLE_POINT_SOURCE+body).parse().opto().typeCheck();
fail(body);
} catch( RuntimeException e ) {
- assertEquals(body,"Might be null accessing 'x'",e.getMessage());
+ // Known null is rejected during parsing in these chapters.
+ String expected = body.startsWith("if (p == null)")
+ ? "Accessing unknown field 'x' from 'null'" : "Might be null accessing 'x'";
+ assertEquals(body,expected,e.getMessage());
}
}
}
diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter16Test.java
index 930624e5..5899763f 100644
--- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter16Test.java
+++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter16Test.java
@@ -145,14 +145,11 @@ return p;
@Test
public void testStructFinal3() {
- CodeGen code = new CodeGen("""
-struct Point { var x; var y; };
-Point p = new Point;
-p.x++;
-return p;
-""");
- try { code.parse().opto(); fail(); }
- catch( Exception e ) { assertEquals("'Point' is not fully initialized, field 'x' needs to be set in a constructor",e.getMessage()); }
+ for( String fields : new String[] { "var x; var y;", "int[] x;", "int[] !x;" } ) {
+ String src = "struct Point { "+fields+" }; Point p = new Point; p.x++; return p;";
+ try { new CodeGen(src).parse().opto(); fail(fields); }
+ catch( Exception e ) { assertEquals("'Point' is not fully initialized, field 'x' needs to be set in a constructor",e.getMessage()); }
+ }
}
@Test
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/LoadNode.java
index 80bf989c..c8cca355 100644
--- a/chapter21/src/main/java/com/seaofnodes/simple/node/LoadNode.java
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/LoadNode.java
@@ -139,6 +139,7 @@ public class LoadNode extends MemOpNode {
private Node zero(NewNode nnn) {
TypeStruct ts = nnn._ptr._obj;
Type zero = ts._fields[ts.findAlias(_alias)]._type.makeZero();
+ assert zero.isa(_type); // Catch an uninitialized non-null field
return castRO(new ConstantNode(zero).peephole());
}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/StoreNode.java
index 8878193c..f15ff9bb 100644
--- a/chapter21/src/main/java/com/seaofnodes/simple/node/StoreNode.java
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/StoreNode.java
@@ -106,6 +106,8 @@ public class StoreNode extends MemOpNode {
public Parser.ParseException err() {
Parser.ParseException err = super.err();
if( err != null ) return err;
+ if( ptr()._type == Type.TOP )
+ return null; // Dead store
TypeMemPtr tmp = (TypeMemPtr)ptr()._type;
if( tmp._obj.field(_name)._final && !_init )
return Parser.error("Cannot modify final field '"+_name+"'",_loc);
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter21/src/test/java/com/seaofnodes/simple/Chapter10Test.java
index 58ca9f5a..14cf6e01 100644
--- a/chapter21/src/test/java/com/seaofnodes/simple/Chapter10Test.java
+++ b/chapter21/src/test/java/com/seaofnodes/simple/Chapter10Test.java
@@ -47,6 +47,7 @@ public class Chapter10Test {
public void testNullGuardErrors() {
for( String body : new String[] {
"return p.x;",
+ "if (p == null) return p.x; return -1;",
"if (!!points) return p.x; return -1;",
"if (!!p) { int x = p.x; } return p.x;"
} ) {
@@ -54,7 +55,10 @@ public class Chapter10Test {
new CodeGen(NULLABLE_POINT_SOURCE+body).parse().opto().typeCheck();
fail(body);
} catch( RuntimeException e ) {
- assertEquals(body,"Might be null accessing 'x'",e.getMessage());
+ // Known null is rejected during parsing in these chapters.
+ String expected = body.startsWith("if (p == null)")
+ ? "Accessing unknown field 'x' from 'null'" : "Might be null accessing 'x'";
+ assertEquals(body,expected,e.getMessage());
}
}
}
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/chapter21/src/test/java/com/seaofnodes/simple/Chapter16Test.java
index 930624e5..5899763f 100644
--- a/chapter21/src/test/java/com/seaofnodes/simple/Chapter16Test.java
+++ b/chapter21/src/test/java/com/seaofnodes/simple/Chapter16Test.java
@@ -145,14 +145,11 @@ return p;
@Test
public void testStructFinal3() {
- CodeGen code = new CodeGen("""
-struct Point { var x; var y; };
-Point p = new Point;
-p.x++;
-return p;
-""");
- try { code.parse().opto(); fail(); }
- catch( Exception e ) { assertEquals("'Point' is not fully initialized, field 'x' needs to be set in a constructor",e.getMessage()); }
+ for( String fields : new String[] { "var x; var y;", "int[] x;", "int[] !x;" } ) {
+ String src = "struct Point { "+fields+" }; Point p = new Point; p.x++; return p;";
+ try { new CodeGen(src).parse().opto(); fail(fields); }
+ catch( Exception e ) { assertEquals("'Point' is not fully initialized, field 'x' needs to be set in a constructor",e.getMessage()); }
+ }
}
@Test
diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/LoadNode.java
index 4f09400a..e0a6d09a 100644
--- a/chapter22/src/main/java/com/seaofnodes/simple/node/LoadNode.java
+++ b/chapter22/src/main/java/com/seaofnodes/simple/node/LoadNode.java
@@ -145,6 +145,7 @@ public class LoadNode extends MemOpNode {
private Node zero(NewNode nnn) {
TypeStruct ts = nnn._ptr._obj;
Type zero = ts._fields[ts.findAlias(_alias)]._type.makeZero();
+ assert zero.isa(_type); // Catch an uninitialized non-null field
return castRO(new ConstantNode(zero).peephole());
}
diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/StoreNode.java
index ce989826..e151911c 100644
--- a/chapter22/src/main/java/com/seaofnodes/simple/node/StoreNode.java
+++ b/chapter22/src/main/java/com/seaofnodes/simple/node/StoreNode.java
@@ -123,6 +123,8 @@ public class StoreNode extends MemOpNode {
public Parser.ParseException err() {
Parser.ParseException err = super.err();
if( err != null ) return err;
+ if( ptr()._type == Type.TOP )
+ return null; // Dead store
TypeMemPtr tmp = (TypeMemPtr)ptr()._type;
if( tmp._obj.field(_name)._final && !_init )
return Parser.error("Cannot modify final field '"+_name+"'",_loc);
diff --git a/chapter22/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter22/src/test/java/com/seaofnodes/simple/Chapter10Test.java
index 9b5d5da6..aa2cd941 100644
--- a/chapter22/src/test/java/com/seaofnodes/simple/Chapter10Test.java
+++ b/chapter22/src/test/java/com/seaofnodes/simple/Chapter10Test.java
@@ -47,6 +47,7 @@ public class Chapter10Test {
public void testNullGuardErrors() {
for( String body : new String[] {
"return p.x;",
+ "if (p == null) return p.x; return -1;",
"if (!!points) return p.x; return -1;",
"if (!!p) { int x = p.x; } return p.x;"
} ) {
@@ -54,7 +55,10 @@ public class Chapter10Test {
new CodeGen(NULLABLE_POINT_SOURCE+body).parse().opto().typeCheck();
fail(body);
} catch( RuntimeException e ) {
- assertEquals(body,"Might be null accessing 'x'",e.getMessage());
+ // Known null is rejected during parsing in these chapters.
+ String expected = body.startsWith("if (p == null)")
+ ? "Accessing unknown field 'x' from 'null'" : "Might be null accessing 'x'";
+ assertEquals(body,expected,e.getMessage());
}
}
}
diff --git a/chapter22/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/chapter22/src/test/java/com/seaofnodes/simple/Chapter16Test.java
index 58bb4148..e7163075 100644
--- a/chapter22/src/test/java/com/seaofnodes/simple/Chapter16Test.java
+++ b/chapter22/src/test/java/com/seaofnodes/simple/Chapter16Test.java
@@ -145,14 +145,11 @@ return p;
@Test
public void testStructFinal3() {
- CodeGen code = new CodeGen("""
-struct Point { var x; var y; };
-Point p = new Point;
-p.x++;
-return p;
-""");
- try { code.parse().opto(); fail(); }
- catch( Exception e ) { assertEquals("'Point' is not fully initialized, field 'x' needs to be set in a constructor",e.getMessage()); }
+ for( String fields : new String[] { "var x; var y;", "int[] x;", "int[] !x;" } ) {
+ String src = "struct Point { "+fields+" }; Point p = new Point; p.x++; return p;";
+ try { new CodeGen(src).parse().opto(); fail(fields); }
+ catch( Exception e ) { assertEquals("'Point' is not fully initialized, field 'x' needs to be set in a constructor",e.getMessage()); }
+ }
}
@Test
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/LoadNode.java
index 94b10027..e752c9ce 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/LoadNode.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/LoadNode.java
@@ -162,6 +162,7 @@ public class LoadNode extends MemOpNode {
private Node zero(NewNode nnn) {
TypeStruct ts = nnn._ptr._obj;
Type zero = ts._fields[ts.findAlias(_alias)]._t.makeZero();
+ assert zero.isa(_type); // Catch an uninitialized non-null field
return castRO(new ConstantNode(zero).peephole());
}
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/StoreNode.java
index 9c4bb4ef..74c27dc8 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/StoreNode.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/StoreNode.java
@@ -130,6 +130,8 @@ public class StoreNode extends MemOpNode {
public Parser.ParseException err() {
Parser.ParseException err = super.err();
if( err != null ) return err;
+ if( ptr()._type == Type.TOP )
+ return null; // Dead store
TypeMemPtr tmp = (TypeMemPtr)ptr()._type;
if( tmp._obj.field(_name)._final && !_init )
return Parser.error("Cannot modify final field '"+_name+"'",_loc);
diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter23/src/test/java/com/seaofnodes/simple/Chapter10Test.java
index d7a74f18..3c5a2913 100644
--- a/chapter23/src/test/java/com/seaofnodes/simple/Chapter10Test.java
+++ b/chapter23/src/test/java/com/seaofnodes/simple/Chapter10Test.java
@@ -63,6 +63,7 @@ public class Chapter10Test {
public void testNullGuardErrors() {
for( String body : new String[] {
"return p.x;",
+ "if (p == null) return p.x; return -1;",
"if (!!points) return p.x; return -1;",
"if (!!p) { int x = p.x; } return p.x;"
} ) {
@@ -70,7 +71,10 @@ public class Chapter10Test {
new CodeGen(NULLABLE_POINT_SOURCE+body).parse().opto().typeCheck();
fail(body);
} catch( RuntimeException e ) {
- assertEquals(body,"Might be null accessing 'x'",e.getMessage());
+ // Known null is rejected during parsing in these chapters.
+ String expected = body.startsWith("if (p == null)")
+ ? "Accessing unknown field 'x' from 'null'" : "Might be null accessing 'x'";
+ assertEquals(body,expected,e.getMessage());
}
}
}
diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/chapter23/src/test/java/com/seaofnodes/simple/Chapter16Test.java
index 2623fee5..310efc45 100644
--- a/chapter23/src/test/java/com/seaofnodes/simple/Chapter16Test.java
+++ b/chapter23/src/test/java/com/seaofnodes/simple/Chapter16Test.java
@@ -143,14 +143,11 @@ return p;
@Test
public void testStructFinal3() {
- CodeGen code = new CodeGen("""
-struct Point { var x; var y; };
-Point p = new Point;
-p.x++;
-return p;
-""");
- try { code.parse().opto(); fail(); }
- catch( Exception e ) { assertEquals("'Point' is not fully initialized, field 'x' needs to be set in a constructor",e.getMessage()); }
+ for( String fields : new String[] { "var x; var y;", "int[] x;", "int[] !x;" } ) {
+ String src = "struct Point { "+fields+" }; Point p = new Point; p.x++; return p;";
+ try { new CodeGen(src).parse().opto(); fail(fields); }
+ catch( Exception e ) { assertEquals("'Point' is not fully initialized, field 'x' needs to be set in a constructor",e.getMessage()); }
+ }
}
@Test
diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/ArithFNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/ArithFNode.java
index 7fa428c6..3c20fa6e 100644
--- a/chapter24/src/main/java/com/seaofnodes/simple/node/ArithFNode.java
+++ b/chapter24/src/main/java/com/seaofnodes/simple/node/ArithFNode.java
@@ -20,7 +20,7 @@ public abstract class ArithFNode extends Node {
public Type compute() {
Type t1 = in(1)._type, t2 = in(2)._type;
if( t1.isHigh() || t2.isHigh() )
- return TypeFloat.TOP;
+ return Type.TOP;
if( t1 instanceof TypeFloat f1 &&
t2 instanceof TypeFloat f2 ) {
if( f1.isConstant() && f2.isConstant() )
diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/ArithNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/ArithNode.java
index 6c147fe8..e9a60f50 100644
--- a/chapter24/src/main/java/com/seaofnodes/simple/node/ArithNode.java
+++ b/chapter24/src/main/java/com/seaofnodes/simple/node/ArithNode.java
@@ -26,10 +26,10 @@ public abstract class ArithNode extends Node {
// Generic airthmetic op math: high returns high; low returns low; 2
// constants fold; only 2 non-constants call specialized math.
@Override
- public final TypeInteger compute() {
+ public final Type compute() {
Type t1 = in(1)._type, t2 = in(2)._type;
if( t1.isHigh() || t2.isHigh() )
- return TypeInteger.TOP;
+ return Type.TOP;
if( t1 instanceof TypeInteger x &&
t2 instanceof TypeInteger y )
return x.isConstant() && y.isConstant()
diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/LoadNode.java
index b32484c2..5f28a39b 100644
--- a/chapter24/src/main/java/com/seaofnodes/simple/node/LoadNode.java
+++ b/chapter24/src/main/java/com/seaofnodes/simple/node/LoadNode.java
@@ -174,6 +174,7 @@ public class LoadNode extends MemOpNode {
private Node zero(NewNode nnn) {
TypeStruct ts = nnn._ptr._obj;
Type zero = ts._fields[ts.findAlias(_alias)]._t.makeZero();
+ assert zero.isa(_type); // Catch an uninitialized non-null field
return castRO(new ConstantNode(zero).peephole());
}
diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter24/src/test/java/com/seaofnodes/simple/Chapter10Test.java
index d7a74f18..3c5a2913 100644
--- a/chapter24/src/test/java/com/seaofnodes/simple/Chapter10Test.java
+++ b/chapter24/src/test/java/com/seaofnodes/simple/Chapter10Test.java
@@ -63,6 +63,7 @@ public class Chapter10Test {
public void testNullGuardErrors() {
for( String body : new String[] {
"return p.x;",
+ "if (p == null) return p.x; return -1;",
"if (!!points) return p.x; return -1;",
"if (!!p) { int x = p.x; } return p.x;"
} ) {
@@ -70,7 +71,10 @@ public class Chapter10Test {
new CodeGen(NULLABLE_POINT_SOURCE+body).parse().opto().typeCheck();
fail(body);
} catch( RuntimeException e ) {
- assertEquals(body,"Might be null accessing 'x'",e.getMessage());
+ // Known null is rejected during parsing in these chapters.
+ String expected = body.startsWith("if (p == null)")
+ ? "Accessing unknown field 'x' from 'null'" : "Might be null accessing 'x'";
+ assertEquals(body,expected,e.getMessage());
}
}
}
diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/chapter24/src/test/java/com/seaofnodes/simple/Chapter16Test.java
index 2623fee5..310efc45 100644
--- a/chapter24/src/test/java/com/seaofnodes/simple/Chapter16Test.java
+++ b/chapter24/src/test/java/com/seaofnodes/simple/Chapter16Test.java
@@ -143,14 +143,11 @@ return p;
@Test
public void testStructFinal3() {
- CodeGen code = new CodeGen("""
-struct Point { var x; var y; };
-Point p = new Point;
-p.x++;
-return p;
-""");
- try { code.parse().opto(); fail(); }
- catch( Exception e ) { assertEquals("'Point' is not fully initialized, field 'x' needs to be set in a constructor",e.getMessage()); }
+ for( String fields : new String[] { "var x; var y;", "int[] x;", "int[] !x;" } ) {
+ String src = "struct Point { "+fields+" }; Point p = new Point; p.x++; return p;";
+ try { new CodeGen(src).parse().opto(); fail(fields); }
+ catch( Exception e ) { assertEquals("'Point' is not fully initialized, field 'x' needs to be set in a constructor",e.getMessage()); }
+ }
}
@Test
diff --git a/chapter25/docs/examples/BubbleSort.smp b/chapter25/docs/examples/BubbleSort.smp
index ef92c29e..1b3b83e6 100644
--- a/chapter25/docs/examples/BubbleSort.smp
+++ b/chapter25/docs/examples/BubbleSort.smp
@@ -5,7 +5,7 @@
// Bubble sort, with reading (parsing) and writing (formatting) the data.
val sort = { sys.aryi64 nums ->
- var more = nums;
+ sys.aryi64?! more = nums;
while( more ) {
more = null;
for( var i = 0; i < nums.len-1; i++ )
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter25/src/main/java/com/seaofnodes/simple/IterPeeps.java
index a54eca73..e65ef6d9 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/IterPeeps.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/IterPeeps.java
@@ -104,7 +104,7 @@ public class IterPeeps {
while( (n=_work.pop()) != null ) {
if( n.isDead() ) continue;
_cnt++; // Useful for debugging, searching which peephole broke things
- var obs = code._obs;
+ var obs = code._obs; // Collect peephole neighbors for graph display
if( obs != null ) obs.before(n);
Node x = n.peepholeOpt();
if( n instanceof CallEndNode cend )
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/Parser.java b/chapter25/src/main/java/com/seaofnodes/simple/Parser.java
index 05685418..af5af514 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/Parser.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/Parser.java
@@ -101,6 +101,9 @@ public class Parser {
ScopeNode _returnScope; // Merge all the function exits here
Ary<FRefNode> _frefs; // Forward refs created during this parse
TypeStruct _ctorOpenStruct; // Open struct being initialized by an allocation constructor block.
+ // Constructor exit types survive scope teardown until all fields are known.
+ // A null entry denotes a constructor with no returning path.
+ private ArrayList<IdentityHashMap<Var,Type>> _ctorExits;
// Mapping from a type name to a Type. The string name matches
// `type.str()` call.
@@ -389,6 +392,19 @@ public class Parser {
smem._declaredType = smem._type = TypeMem.makePrivate(tself);
}
+ if( !upgrade ) {
+ IdentityHashMap<Var,Type> exit = null;
+ if( _returnScope!=null && !_returnScope.ctrl()._type.isHigh() ) {
+ exit = new IdentityHashMap<>();
+ int first = _scope._kinds.at(_scope.depth()-2)._lexSize+2;
+ for( int i=first; i<lex; i++ )
+ exit.put(_scope.var(i),_returnScope.in(i)._type);
+ }
+ _ctorExits.add(exit);
+ } else if( !isClz ) {
+ checkInitFields(tself,base);
+ }
+
// When can _returnScope be null here? A never-exit constructor will
// not have any returns, and thus no need to gather values and store
// them into the (never) constructed object
@@ -412,13 +428,11 @@ public class Parser {
Var v = _scope.var(vidx);
if( v._fref ) continue; // Forward refs not declared here
// NewNode's TOP field is the zero-code poison for a required
- // field. The parser Var retains the typed nullable state for
- // early-read diagnostics; only a user constructor writes memory.
- if( upgrade && v._uninit ) continue;
+ // field. Leave it alone unless the declaration body supplied a
+ // value; otherwise a user constructor must initialize it.
Node val = _returnScope.in(vidx);
- if( !upgrade && v._uninit && val._type instanceof TypeNil tn && tn.nullable() )
- throw error("'"+tself._name+"' is not fully initialized, field '"+
- fld._fname+"' is only partially set in the constructor",v._loc);
+ if( upgrade && v._uninit &&
+ !(val._type instanceof TypeNil tn && tn.notNull() && !val._type.isHigh()) ) continue;
// Store value into extended struct
Node prior = mmm.keep();
Node st = peep(new StoreNode(null, fld._fname, fld._alias, fld._t, null, prior, self, off(tself,fld._fname), val, fld._final));
@@ -437,6 +451,28 @@ public class Parser {
//_code.add(mmm);
}
+ // Validate each user constructor, or an implicit empty one, before the
+ // declaration scope goes away. Later fields use their declaration defaults.
+ private void checkInitFields(TypeStruct ts, int base) {
+ if( _returnScope==null || _returnScope.ctrl()._type.isHigh() ) return;
+ boolean implicit = _ctorExits.isEmpty();
+ if( implicit ) _ctorExits.add(new IdentityHashMap<>());
+ for( IdentityHashMap<Var,Type> exit : _ctorExits ) {
+ if( exit==null ) continue;
+ for( Field fld : ts._fields ) {
+ int idx = fieldVarIdx(base,fld._fname);
+ if( idx == -1 ) continue;
+ Var v = _scope.var(idx);
+ if( !v._uninit || v._fref ) continue;
+ Type t = exit.getOrDefault(v,_returnScope.in(idx)._type);
+ if( !(t instanceof TypeNil tn && tn.notNull() && !t.isHigh()) )
+ throw error("'"+ts._name+"' is not fully initialized, field '"+fld._fname+
+ (implicit ? "' needs to be set in a constructor" :
+ "' is only partially set in the constructor"),v._loc);
+ }
+ }
+ }
+
private int fieldVarIdx( int base, String name ) {
for( int i=base; i<_scope.nIns(); i++ )
if( _scope.var(i)._name == name )
@@ -460,8 +496,7 @@ public class Parser {
}
private boolean inExplicitConstructor() {
- if( !(_scope.klast() instanceof Kind.Func func) )
- return false;
+ Kind.Func func = (Kind.Func)_scope._kinds.at(_scope.enclosingFuncOrDecl());
return isExplicitConstructorName(func._name);
}
@@ -886,6 +921,12 @@ public class Parser {
_returnScope._vars.add(vexpr);
_returnScope.addDef(oldX);
}
+ } else if( isExplicitConstructorName(k._name) ) {
+ // Field bindings live in the enclosing declaration, not among the
+ // constructor's arguments/locals. Force lazy Phis on every exit.
+ int first = _scope._kinds.at(lexN-1)._lexSize+2;
+ for( int i=first; i<k._lexSize; i++ )
+ _scope.update(_scope.var(i),null);
}
// No prior merge point? Just clone and hang on to it
@@ -1268,6 +1309,8 @@ public class Parser {
// Parse a struct declaration (not an allocation); file-level is a class-init, and scope structs are normal
private ReturnNode parseStruct( boolean isClz, String typeName ) {
+ var oldExits = _ctorExits;
+ _ctorExits = new ArrayList<>();
// Record & restore global state set during parsing the <init> code
Node oldCtrl= _scope.ctrl().keep();
Node oldMem = _scope.mem ().keep();
@@ -1289,6 +1332,7 @@ public class Parser {
// Struct decls look like function bodies. Parse function body normally.
ReturnNode ret = _parseFunctionBody(fname,sig,loc(),ids);
+ _ctorExits = oldExits;
// Unwind global state.
ctrl(oldCtrl.unkeep());
mem (oldMem .unkeep());
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ArithNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ArithNode.java
index 60f00854..bb370a56 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/ArithNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ArithNode.java
@@ -110,12 +110,9 @@ public abstract class ArithNode extends Node implements ModeNode {
@Override
public final Type compute() {
Type t1 = in(1)._type, t2 = in(2)._type;
- if( t1==null || t2==null )
- return Type.TOP;
- if( t1.isHigh() || t2.isHigh() )
- return _mode==0 ? Type.TOP :
- _mode==1 ? TypeInteger.TOP :
- TypeFloat.F64.dual();
+ if( t1==null || t2==null ) return Type.TOP;
+ if( t1.isHigh() || t2.isHigh() ) return Type.TOP;
+ if( t1==Type.BOTTOM || t2==Type.BOTTOM ) return Type.BOTTOM;
byte mode = _mode==0 ? mode(t1,t2) : _mode;
if( mode==0 )
return CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal() ? Type.BOTTOM : Type.TOP;
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
index 3c6506b9..d2286c09 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
@@ -85,7 +85,7 @@ public class CallEndNode extends CFGNode implements MultiNode {
// Mid-fold, just take the one single callers' return type
if( _folding ) {
TypeTuple tt = (TypeTuple)in(1)._type;
- return tt.makeFrom(2,tt.ret().join(tfp._ret));
+ return tt.makeFrom(2,tt.ret());
}
// Here, if I can figure out I've linked *all* callers, then I can meet
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java
index d37655b6..bd80a74b 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java
@@ -48,12 +48,15 @@ public class LoadNode extends MemOpNode {
if( mem._t == Type.TOP )
return TypeScalar.TOP;
if( ptr0 == Type.NIL )
- return TypeScalar.TOP;
- if( !(ptr0 instanceof TypeMemPtr ptr) )
+ return Type.BOTTOM; // Load from nil is error
+ if( ptr0 == Type.BOTTOM )
+ return Type.BOTTOM; // Error in is error out
+ if( !(ptr0 instanceof TypeMemPtr tmp) )
return scalar(mem._t);
+ if( tmp.nullable() )
+ return Type.BOTTOM; // Load from nil-able is error
// Load field from object
- TypeMemPtr tmp = ptr;
Field pfld = tmp._obj.field(_name);
// No field? Open objects might yet get the field when falling;
// closed objects with missing field are an error.
@@ -110,11 +113,6 @@ public class LoadNode extends MemOpNode {
if( _alias == 1 )
return null;
- if( mem instanceof MemMergeNode merge ) {
- setDef(1,merge.alias(_alias));
- return this;
- }
-
// Simple Load-after-Store on same address.
if( mem instanceof StoreNode st &&
ptr == st.nnptr() &&
@@ -124,15 +122,16 @@ public class LoadNode extends MemOpNode {
return extend(st.val());
}
- //// Expose the same effective memory input already observed by compute.
- //if( mem != mem() ) {
- // for( Node ld : mem._outputs )
- // if( ld instanceof LoadNode )
- // CodeGen.CODE.add(ld);
- // if( mem instanceof BulkMemPhiNode ) CodeGen.CODE.add(mem);
- // setDef(1,mem);
- // return this;
- //}
+ if( mem instanceof MemMergeNode merge ) {
+ setDef(1,merge.alias(_alias));
+ // In support of uplifting control to a prior dominating load, if
+ // we move our memory, recheck other loads to see if they can hoist.
+ for( Node use : in(1).outs() )
+ if( use instanceof LoadNode ld && use != this && ptr == ld.ptr() )
+ CodeGen.CODE.add(ld);
+
+ return this;
+ }
// Uplift control to a prior dominating load.
for( Node memuse : mem._outputs )
@@ -167,18 +166,21 @@ public class LoadNode extends MemOpNode {
// Assume related
addDep(phi);
break outer;
- //case ConstantNode con:
- // // Load from constant memory
- // if( con._con instanceof TypeMem tmem )
+ case ConstantNode con:
+ // Load from constant memory
+ if( con._con instanceof TypeMem tmem )
+ throw Utils.TODO("need to see a test case");
// return ConstantNode.make(tmem._t);
- // break outer; // Assume shortly dead
+ break outer; // Assume shortly dead
case ProjNode mproj: // Memory projection
switch( mproj.in(0) ) {
case NewNode nnn1:
// Direct load from fresh zero/default-filled allocation.
Type decl = declaredType();
assert decl!=Type.BOTTOM;
- return ConstantNode.make(decl.makeZero());
+ Type zero = decl.makeZero();
+ assert zero.isa(_type); // Catch bug uninitialized null field
+ return ConstantNode.make(zero);
case CallEndNode cend: addDep(mproj); break outer; // TODO: Bypass no-alias call
default: throw Utils.TODO("Should not reach here");
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter25/src/main/java/com/seaofnodes/simple/node/Node.java
index e2148244..e77b0c0a 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/Node.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/Node.java
@@ -488,18 +488,8 @@ public abstract class Node implements Cloneable {
// Compute initial or improved Type
Type old = setType(compute());
- // Replace constant computations from non-constants with a constant
- // node. If peeps are disabled, still allow high Phis to collapse;
- // they typically come from dead Regions, and we want the Region to
- // collapse, which requires the Phis to die first.
+ // Replace constant computations from non-constants with a constant node.
if( !isConst() && !(this instanceof ParmNode parm && parm.inProgress()) &&
- // A high Load through a bad pointer still owns the source error;
- // do not replace it with an unlocated constant before typeCheck.
- !(this instanceof LoadNode && err()!=null) &&
- // A CallEnd can be temporarily high while optimistic call-graph
- // linking is still discovering its target Returns. Its result
- // projections must remain attached so they can sharpen later.
- !(this instanceof ProjNode proj && proj.in(0) instanceof CallEndNode && _type.isHigh()) &&
_type.isHighOrConst() )
return ConstantNode.make(_type).peephole();
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java
index 12a94906..2683ddf5 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java
@@ -260,7 +260,8 @@ public class StoreNode extends MemOpNode {
Parser.ParseException err = super.err();
if( err != null ) return err;
assert _size>0 || CodeGen.CODE._phase.ordinal() <= CodeGen.Phase.Opto.ordinal();
- assert ptr()._type != Type.TOP;
+ if( ptr()._type == Type.TOP ) // Dead store?
+ return null;
TypeMemPtr tmp = (TypeMemPtr)ptr()._type;
Field f = tmp._obj.field(_name);
if( f!=null && f._final && !_init )
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java
index 540f36ff..cfd213b7 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java
@@ -71,7 +71,7 @@ public class TypeMemPtr extends TypeNil {
/** Inferred mutable local: nullable, public and non-singleton. */
public TypeMemPtr makeVar() { return (TypeMemPtr)makeStorage(); }
/** Public-memory shape: nullable and non-singleton, preserving the referent. */
- @Override TypeMemPtr _makeStorage() { return make((byte)3,_obj._makeStorage(),false,true); }
+ @Override TypeMemPtr _makeStorage() { return make(_nil,_obj._makeStorage(),false,true); }
@Override TypeMemPtr makeFrom(byte nil) { return nil==_nil ? this : make(nil, _obj, _one, _pub); }
@Override public Type nonZero() { return makeFrom((byte)(_nil <= 1 ? 1 : 2)); }
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter10Test.java
index 029e28b8..f41a1904 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter10Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter10Test.java
@@ -68,6 +68,7 @@ public class Chapter10Test {
public void testNullGuardErrors() {
for( String body : new String[] {
"return p.x;",
+ "if (p == null) return p.x; return -1;",
"if (!!points) return p.x; return -1;",
"if (!!p) { int x = p.x; } return p.x;"
} ) {
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter15Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter15Test.java
index 4d273aff..24552062 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter15Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter15Test.java
@@ -182,7 +182,7 @@ return root;
public void testNestedStructAddMemProj() {
CodeGen code = new CodeGen(
"""
-struct _S { int a; int[] b; };
+struct _S { int a; int[] b; new _S = { int[] ary -> b=ary; }; };
return 0;
""");
code.parse().opto();
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter17Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter17Test.java
index faba6451..79aecbff 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter17Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter17Test.java
@@ -268,15 +268,38 @@ A x = new A;
@Test
public void testVar15() {
- CodeGen code = new CodeGen("""
-struct B {};
-struct A { B b; new A = { ->
- if (arg) b = new B; // Constructor ends with partial init of b
-}; };
-return (new A).b;
-""");
- try { code.parse().opto().typeCheck(); fail(); }
- catch( Exception e ) { assertEquals("'Test.A' is not fully initialized, field 'b' is only partially set in the constructor",e.getMessage()); }
+ for( String fields : new String[] {
+ "B b; new A = { -> if (arg) b = new B; };",
+ "B b; new A = { -> if (arg) return 0; b = new B; };",
+ "B b; new A = { B b -> b = new B; };", // Argument shadows field
+ "new A = { -> }; B b;", // Field declared later
+ } ) {
+ CodeGen code = new CodeGen("struct B {}; struct A { "+fields+" }; return 0;");
+ try { code.parse(); fail(fields); }
+ catch( Parser.ParseException e ) { assertEquals("'Test.A' is not fully initialized, field 'b' is only partially set in the constructor",e.getMessage()); }
+ }
+ }
+
+ @Test
+ public void testDefaultConstructorFields() {
+ for( String field : new String[] { "B b;", "B !b;" } ) {
+ CodeGen code = new CodeGen("struct B {}; struct A { "+field+" }; return new A;");
+ try { code.parse(); fail(field); }
+ catch( Parser.ParseException e ) { assertEquals("'Test.A' is not fully initialized, field 'b' needs to be set in a constructor",e.getMessage()); }
+ }
+ for( String fields : new String[] {
+ "B b = new B;",
+ "B b; b = new B;",
+ "new A = { -> }; B b = new B;",
+ "B b; new A = { -> if (arg) { b = new B; return 0; } b = new B; };",
+ } ) {
+ CodeGen code = new CodeGen("struct B {}; struct A { "+fields+" }; return new A.b != null;");
+ code.parse().opto().typeCheck();
+ assertEquals("1",Eval2.eval(code,0));
+ assertEquals("1",Eval2.eval(code,1));
+ }
+ // No returning path means no object can escape uninitialized.
+ new CodeGen("struct B {}; struct A { B b; new A = { -> while (true) {} }; }; return 0;").parse();
}
@Test
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter20Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter20Test.java
index a9f7dde7..5ea198ef 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter20Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter20Test.java
@@ -195,6 +195,7 @@ return ary[1] * 1000 + ary[3]; // 1 * 1000 + 6
struct String {
u8[] cs;
int _hashCode;
+ new String = { u8[] data -> cs=data; };
};
// Compare two Strings
@@ -222,9 +223,9 @@ val _hashCodeString = { String self ->
return hash;
};
""";
- testCPU(src,"x86_64_v2", "SystemV",18,null);
- testCPU(src,"riscv" , "SystemV", 7,null);
- testCPU(src,"arm" , "SystemV", 8,null);
+ testCPU(src,"x86_64_v2", "SystemV",21,null);
+ testCPU(src,"riscv" , "SystemV",10,null);
+ testCPU(src,"arm" , "SystemV",10,null);
}
@Test
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java
index 9f37be49..4307a3b6 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java
@@ -292,6 +292,7 @@ return v0;
struct String {
u8[] cs;
int _hashCode;
+ new String = { u8[] data -> cs=data; };
};
// Compare two Strings
@@ -319,9 +320,9 @@ val _hashCodeString = { String self ->
return hash;
};
""";
- testCPU(src,"x86_64_v2", "SystemV",18,null);
- testCPU(src,"riscv" , "SystemV", 7,null);
- testCPU(src,"arm" , "SystemV", 8,null);
+ testCPU(src,"x86_64_v2", "SystemV",21,null);
+ testCPU(src,"riscv" , "SystemV",10,null);
+ testCPU(src,"arm" , "SystemV",10,null);
}
@Test public void testStringExport() throws IOException {
@@ -330,6 +331,7 @@ val _hashCodeString = { String self ->
struct String {
u8[] cs;
int _hashCode;
+ new String = { u8[] data -> cs=data; };
};
// Compare two Strings
@@ -357,7 +359,7 @@ val _hashCodeString = { String self ->
return hash;
};
""";
- TestC.runC(src, "stringHash", "",18);
+ TestC.runC(src, "stringHash", "",21);
}
@Test public void testLoop2() {
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter25Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter25Test.java
index 0ac55a9e..41225097 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter25Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter25Test.java
@@ -29,16 +29,7 @@ public class Chapter25Test {
@Ignore
@Test
public void testJig() {
- String src =" while(1?!0:arg++) {";
- String src2 = """
-int v0=0^0;
- while(1) {
- arg=v0++&0;
- int v2=0;
- if(v0)
- if(v0) v2=0;
- }
- """;
+ String src = "struct I { int x; }; struct F { I p; }; return new F.p.x;";
CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
assertEquals("2",Eval2.eval(code,0));
}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/FuzzerWrap.java b/chapter25/src/test/java/com/seaofnodes/simple/FuzzerWrap.java
index 66bc4717..68bbe918 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/FuzzerWrap.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/FuzzerWrap.java
@@ -18,7 +18,7 @@ public class FuzzerWrap {
-148471672577312953L, // bulk mem
-5037182906211190034L, // dead Guard control
-6359653295501938199L, // monotonicity, escape is dead
- 6506797708065910879L, // missing ModeNode._mode is optimistic
+ 6506797708065910879L, // required field without a user constructor
650692970082394944L, // recursive bulk mem split
8335100609598232836L, // ternary result killed by region cleanup
2520958273643234516L, // nested guard merge
diff --git a/docs/chapter-backports.md b/docs/chapter-backports.md
index 7d383478..304e7472 100644
--- a/docs/chapter-backports.md
+++ b/docs/chapter-backports.md
@@ -13,6 +13,29 @@ renumbering chapters is deferred.
## Pending corrections
+- **Chapter 25 BOTTOM-on-error backport boundary (2026-09-28).** Cliff requested
+ investigating the earlier arithmetic contracts. In 24, changing ArithNode's
+ non-integer fallback to global BOTTOM breaks the valid `testCoRecur`: the
+ declaration `val az = x*2` refers to `x` defined later, and the allocation-time
+ check confuses its temporary BOTTOM with an unset field. Three arithmetic
+ diagnostics also become generic parser errors (`testNullRef5`, `testMaskFloat`,
+ `testSubZeroTypeError`). Full-suite evidence: `build/arithmetic-contract-24.log`.
+ Restoring the integer fallback fixes all four in a 40-test focused run
+ (`build/arithmetic-top-24.log`). The Java return signature itself has no typed
+ caller dependency. Further BOTTOM work is paused for Cliff's decision about
+ separating forward/unresolved expressions from initialization errors in the
+ earlier parser. Earlier loads also retain declared types on errors; do not
+ silently change that contract. The known-null regression is already rejected
+ during parsing in 10-24. Keep this separate from the constructor/memory fixes.
+
+- **Chapter 25 Load TODO, seed `-4628356252269023530`.** The regression wrapper
+ reaches the default TODO in `LoadNode.idealize`'s memory walk with worklist
+ seed 456. Reproduced on 2026-09-28 both with the constructor-check changes
+ and with the original HEAD Parser compiled separately against the current
+ node edits. This is independent of the missing-constructor diagnostic;
+ it remains in both the regression and open-failure seed lists. Logs:
+ `build/ctor-baseline-seed.log` and `build/ctor-check-tests.log`.
+
- **FunPtrNode lifetime backport, now next after the allocator review.**
`return {->42;};` and `return sys.io.p;` fail during relocation in Chapter 22
on all three targets at seed 123. Opto's name-based pruning equates no linked
@@ -28,16 +51,6 @@ renumbering chapters is deferred.
addresses. Do not enlarge relocation arrays or add an interim address scan.
Validate returned pointers by calling them, including library pointers, while
still deleting genuinely unused helpers. Keep module/escape machinery in 25.
-- **Chapter 25 null-dereference diagnostic.** This setup was accepted despite
- dereferencing null on the taken arm; investigate separately from B13:
-
- ```java
- struct Point { int x; new Point={int v -> x=v;}; };
- Point?[] !points=new Point?[2]; points[arg]=new Point(42);
- Point? p=points[1];
- if(p==null) return p.x; return -1;
- ```
-
- **Chapter 25 ARM extern data.** `i32 errno="C"; return 0;`, compiled through
Encoding with ARM/SystemV, reaches `arm.load_str_imm` with register -1 for the
extern-data value stored by `<clinit>`. Both the saved original and final
@@ -190,8 +203,18 @@ old revisions. Earlier detailed traces are in Git history; durable invariants
have been consolidated into the [AI notes](../skills/chapter25-codex-notes.md).
Unresolved reproductions have been promoted to the pending queue above.
+On 2026-09-28, the constructor/memory-check backport passed the full Make suites
+in every compiler snapshot from 10 through 24, including their default fuzzer
+wrappers (`build/error-backport-tests.log`). The destination baseline was also
+green (`build/error-backport-baseline.log`). The arithmetic contract experiment
+and its separate validation are recorded independently.
+
| Completed work | Scope and evidence retained |
|---|---|
+| Arithmetic high results at the SCCP boundary | Chapter 24 integer and float arithmetic now return global TOP for high operands, matching 25. Widened ArithNode.compute's Java return signature to Type; no caller requires TypeInteger. Retained the typed integer fallback for unresolved/error operands, for the parser dependency in the pending queue. The TOP change starts with SCCP in 24; earlier snapshots retain their typed contracts. Full Chapter 24 Make suite passed: 477 tests plus its fuzzer wrapper (`build/arithmetic-top-full-24.log`). |
+| Required-field and memory-check backport | Chapters 16-24 retain allocation-time checks for inline `new S { ... }` initialization. Chapter 17 had its TOP/unset-field check commented out; restored it. Expanded the existing missing-initializer regression through 24 (introduced its array-field cases in 16), and forwarded the known-null load regression to 10-24 using their existing parse-time diagnostic. Added the fresh-allocation zero-fold monotonicity assertion in 19-24 and the TOP-pointer store-diagnostic guard in 18-23 (already present in 24). Before 18, TOP pointers already produce a diagnostic before the store's pointer cast. Chapter 25's constructor-exit snapshots and implicit-constructor declaration check stay with its separate allocation/constructor model. Its CallEnd return and MemMerge load-dependency corrections already have equivalent behavior in the applicable earlier implementations; storage-type nullability machinery stays with 25. Global error-result type changes are paused in the queue above. |
+| Chapter 25 required constructor fields | Parser records each explicit constructor's merged exit field types by Var identity and checks them against the complete field list when the struct closes. With no user constructor, declaration defaults are checked as an implicit empty constructor. Never-returning constructors have no completion obligation; nested blocks retain constructor context. Declaration-body assignments to required fields now emit their initializing stores. Load's fresh-allocation zero fold is restored with a monotonicity guard. Existing constructor tests cover partial/early returns, shadowing, later fields, valid defaults and non-returning constructors; older unused-struct/String fixtures now provide constructors, with measured spill expectations updated. Seed `6506797708065910879` is rejected during parsing and passes its regression. On 2026-09-28, all 472 non-fuzzer tests passed across `build/ctor-check-tests.log` and `build/ctor-remaining-tests.log`; the full Make run remains red solely on the independent Load TODO seed above. Allocation-time initialization checking was lost in the August 10 rewrite (`51f1f8f4`); this restores the obligation in the parser without depending on optimized private memory. |
+| Chapter 25 null-dereference diagnostic | Existing `Chapter10Test.testNullGuardErrors` includes `if (p == null) return p.x; return -1;`. The earlier recursive constant-fold error guard was removed by Cliff in favor of erroneous nodes computing BOTTOM, with related eager-folding and monotonicity corrections. The earlier full-suite pass in `build/null-deref-arith-top.log` applies to the superseded guard implementation, not the current edits. The exposed uninitialized-field failure is fixed by the constructor checks recorded above. |
| Chapter 22 String without an explicit return | Backported 23's default-main teardown: when top-level control falls through, replace main's Start input with XCTRL and queue its users. Previously 22 omitted the return while retaining live control, leaving loop fragments with missing exits. Current seed 0 reproduced a missing-successor failure in Encoding; the earlier GCM changes had moved it past the originally recorded failure. The unchanged `Chapter20Test.testString` now also checks seed 0 through Encoding on x86/RISC-V/ARM, preserving its existing seed-123 allocator checks. Two Chapter09Test graph expectations now reflect removal of a default main with no live return; their sources are unchanged. Baseline and final full Chapter 22 suites passed (425 tests plus fuzzer), logged in `build/string-baseline.log` and `build/string-final.log`. Chapters 23-24 already contain this teardown; no later compiler changes were needed. |
| Infinite-loop evaluation | Eval2 now runs the missing loop-tree phase in 21 and explicitly follows NeverNode projection 0 in 21-25. Existing `testFcn9` expects timeout in 21-24. Full 18-25 Make suites passed on 2026-09-26 (`build/never-exit-tests.log` and `build/never-exit-final.log`). On 2026-09-27, Cliff requested removal of the frozen-source fixture, replay tests, related reductions, and seed-list entry as unnecessary test overhead; the evaluator fixes remain. |
| Always-on peepholes | Removed the global disable flag and branches in 2-18, the Chapter 18 parse overload, test toggles, and transpiler metadata/Rust output. Updated early graph expectations and READMEs; removed duplicate no-peephole scope tests. Chapter 8 fuzzing checks compilation/evaluation for failures; 9-18 compare worklist seeds 123/456 like later chapters. The Chapter 18 Load type workaround was unnecessary with normal forwarding and was removed. All 1-25 default Make suites passed across `build/always-peeps-tests.log` and `build/always-peeps-final.log`; later evaluator corrections are recorded above. The baseline was green in `build/always-peeps-baseline.log`. Direct checks exercised both worklist seeds in 9-18. The transpiler compiles and its focused grammar/Rust-output check passes; its existing repository-wide test stops at source-free chapter00 with `no source files`. |
@@ -201,7 +224,7 @@ Unresolved reproductions have been promoted to the pending queue above.
| Short type extrema | Memory, struct and memory-pointer BOT/TOP names from 10; function-pointer names from 18. Exact types use shortcuts in ordinary, nested and HTML prints; precise types retain their details. Full Make suites in 10-20 and type/frontend suites in 21-25 passed; canonical/nested print checks passed in 10-25. |
| Integer result types | Integer-op fallbacks in 4-13 and remaining unary cases through 17 retain integer types; 5's Phis meet their data-input types. Full Make suites in 4-17 passed, plus a chapter 5 snapshot check of Phi/Add types. Integer BOT starts in 4; later arithmetic already has typed fallbacks, with unresolved bimorphic modes preserved in 25. |
| B14 cyclic leaf-kind equality | 23-24 backport; 25 already correct. Cyclic equality starts in 23. `TypeTest.testCyclicLeafKinds` fails before the fix; earlier interned-child equality already checks kinds. Full affected suites passed. |
-| B13 / #246 null guards | Nested Not/null guard discovery from 10; short-circuit Phi availability in 23-24. Regression and full suites in 10-25 passed. Separate null-dereference diagnostic remains queued. |
+| B13 / #246 null guards | Nested Not/null guard discovery from 10; short-circuit Phi availability in 23-24. Regression and full suites in 10-25 passed. Separate Chapter 25 null-dereference diagnostic is recorded above. |
| B09 and #251 debug printing | Side-effect-free print/accessor paths from their first applicable chapters; `_` diagnostic naming, type interning allowed, layouts forbidden. Dead nodes use `isDead()`, not null `_inputs`, from 7. Original-printer negative checks and full affected suites passed. |
| #247 emulator stores | Full eight-byte writes in ARM/RISC-V emulators, 21-25. Independent byte-pattern/overwrite regressions failed before the fix; all affected suites passed. |
| B12 x86 immediate multiply | Dedicated destination-register encoder in 21; 22-25 already had it. Low/extended-register encoding regressions and full 21-25 suites passed. |
diff --git a/skills/chapter25-codex-notes.md b/skills/chapter25-codex-notes.md
index 96538d9d..ec2242dd 100644
--- a/skills/chapter25-codex-notes.md
+++ b/skills/chapter25-codex-notes.md
@@ -333,6 +333,12 @@ Consequences:
non-defaultable fields on every exit before the object escapes.
- Constructor field state is tracked in parser `Var` metadata. Early reads of
possibly-uninitialized fields are errors.
+- Check constructor completion in the parser, independent of allocation and
+ optimized memory. Save each constructor's merged exit field types by Var
+ identity and validate against the complete declaration at struct close.
+ No user constructor means an implicit empty constructor using declaration
+ defaults; a non-returning constructor has no completion obligation. Nested
+ blocks retain the enclosing constructor context, but nested functions do not.
- Recursive constructors may reasonably be rejected; constructor chaining must
preserve private-memory initialization state.