summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-09-25 15:28:21 -0700
committerCliff Click <cliffc@acm.org>2026-09-25 15:28:21 -0700
commit29e0127170dce8964d7f85d808d1ec07426d2789 (patch)
treef9174685beaf274f3324a08c68f2b0a7137309c2
parent12bde12b2e18c8b82b1510ac4305de66d1205e1a (diff)
downloadSimple-29e0127170dce8964d7f85d808d1ec07426d2789.tar.gz
Simple-29e0127170dce8964d7f85d808d1ec07426d2789.zip
Restrict subtraction zero peepholes to integer operandspr254-fixes
Require resolved integer mode in chapter 25 so zero subtraction preserves floating-point signed zero. Keep nonnumeric operands in the graph for subtraction diagnostics. Add signed-zero coverage from chapter 12 and null-subtraction diagnostics from chapter 22. Confirmed the regressions fail on the original PR. Full Make suites pass in chapters 4-25 (5462 tests), including fresh chapter 25 sys.o compilation.
-rw-r--r--chapter04/src/main/java/com/seaofnodes/simple/node/SubNode.java10
-rw-r--r--chapter05/src/main/java/com/seaofnodes/simple/node/SubNode.java10
-rw-r--r--chapter06/src/main/java/com/seaofnodes/simple/node/SubNode.java10
-rw-r--r--chapter07/src/main/java/com/seaofnodes/simple/node/SubNode.java10
-rw-r--r--chapter08/src/main/java/com/seaofnodes/simple/node/SubNode.java10
-rw-r--r--chapter09/src/main/java/com/seaofnodes/simple/node/SubNode.java10
-rw-r--r--chapter10/src/main/java/com/seaofnodes/simple/node/SubNode.java10
-rw-r--r--chapter11/src/main/java/com/seaofnodes/simple/node/SubNode.java10
-rw-r--r--chapter12/src/main/java/com/seaofnodes/simple/node/SubNode.java10
-rw-r--r--chapter12/src/test/java/com/seaofnodes/simple/Chapter12Test.java7
-rw-r--r--chapter13/src/main/java/com/seaofnodes/simple/node/SubNode.java10
-rw-r--r--chapter13/src/test/java/com/seaofnodes/simple/Chapter12Test.java7
-rw-r--r--chapter14/src/main/java/com/seaofnodes/simple/node/SubNode.java10
-rw-r--r--chapter14/src/test/java/com/seaofnodes/simple/Chapter12Test.java7
-rw-r--r--chapter15/src/main/java/com/seaofnodes/simple/node/SubNode.java10
-rw-r--r--chapter15/src/test/java/com/seaofnodes/simple/Chapter12Test.java7
-rw-r--r--chapter16/src/main/java/com/seaofnodes/simple/node/SubNode.java10
-rw-r--r--chapter16/src/test/java/com/seaofnodes/simple/Chapter12Test.java7
-rw-r--r--chapter17/src/main/java/com/seaofnodes/simple/node/SubNode.java10
-rw-r--r--chapter17/src/test/java/com/seaofnodes/simple/Chapter12Test.java7
-rw-r--r--chapter18/src/main/java/com/seaofnodes/simple/node/SubNode.java10
-rw-r--r--chapter18/src/test/java/com/seaofnodes/simple/Chapter12Test.java7
-rw-r--r--chapter19/src/main/java/com/seaofnodes/simple/node/SubNode.java10
-rw-r--r--chapter19/src/test/java/com/seaofnodes/simple/Chapter12Test.java7
-rw-r--r--chapter20/src/main/java/com/seaofnodes/simple/node/SubNode.java10
-rw-r--r--chapter20/src/test/java/com/seaofnodes/simple/Chapter12Test.java7
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/SubNode.java10
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/Chapter12Test.java7
-rw-r--r--chapter22/src/main/java/com/seaofnodes/simple/node/SubNode.java10
-rw-r--r--chapter22/src/test/java/com/seaofnodes/simple/Chapter12Test.java7
-rw-r--r--chapter22/src/test/java/com/seaofnodes/simple/Chapter22Test.java9
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/SubNode.java10
-rw-r--r--chapter23/src/test/java/com/seaofnodes/simple/Chapter12Test.java7
-rw-r--r--chapter23/src/test/java/com/seaofnodes/simple/Chapter22Test.java9
-rw-r--r--chapter24/src/main/java/com/seaofnodes/simple/node/SubNode.java10
-rw-r--r--chapter24/src/test/java/com/seaofnodes/simple/Chapter12Test.java7
-rw-r--r--chapter24/src/test/java/com/seaofnodes/simple/Chapter22Test.java9
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/SubNode.java10
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter12Test.java7
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter22Test.java9
-rw-r--r--docs/chapter-backports.md1
41 files changed, 245 insertions, 110 deletions
diff --git a/chapter04/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter04/src/main/java/com/seaofnodes/simple/node/SubNode.java
index 304092f4..c831a798 100644
--- a/chapter04/src/main/java/com/seaofnodes/simple/node/SubNode.java
+++ b/chapter04/src/main/java/com/seaofnodes/simple/node/SubNode.java
@@ -37,11 +37,11 @@ public class SubNode extends Node {
Type t1 = lhs._type;
Type t2 = rhs._type;
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
- return lhs;
-
- if( t1.isConstant() && t1 instanceof TypeInteger i && i.value()==0 )
- return new MinusNode(rhs);
+ // Keep invalid operands for type checking.
+ if( t1 instanceof TypeInteger x && t2 instanceof TypeInteger y ) {
+ if( y.isConstant() && y.value()==0 ) return lhs;
+ if( x.isConstant() && x.value()==0 ) return new MinusNode(rhs);
+ }
// x - (-y) is x+y
if( rhs instanceof MinusNode minus )
diff --git a/chapter05/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter05/src/main/java/com/seaofnodes/simple/node/SubNode.java
index fa777a30..bd43bc6c 100644
--- a/chapter05/src/main/java/com/seaofnodes/simple/node/SubNode.java
+++ b/chapter05/src/main/java/com/seaofnodes/simple/node/SubNode.java
@@ -37,11 +37,11 @@ public class SubNode extends Node {
Type t1 = lhs._type;
Type t2 = rhs._type;
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
- return lhs;
-
- if( t1.isConstant() && t1 instanceof TypeInteger i && i.value()==0 )
- return new MinusNode(rhs);
+ // Keep invalid operands for type checking.
+ if( t1 instanceof TypeInteger x && t2 instanceof TypeInteger y ) {
+ if( y.isConstant() && y.value()==0 ) return lhs;
+ if( x.isConstant() && x.value()==0 ) return new MinusNode(rhs);
+ }
// x - (-y) is x+y
if( rhs instanceof MinusNode minus )
diff --git a/chapter06/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter06/src/main/java/com/seaofnodes/simple/node/SubNode.java
index fa777a30..bd43bc6c 100644
--- a/chapter06/src/main/java/com/seaofnodes/simple/node/SubNode.java
+++ b/chapter06/src/main/java/com/seaofnodes/simple/node/SubNode.java
@@ -37,11 +37,11 @@ public class SubNode extends Node {
Type t1 = lhs._type;
Type t2 = rhs._type;
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
- return lhs;
-
- if( t1.isConstant() && t1 instanceof TypeInteger i && i.value()==0 )
- return new MinusNode(rhs);
+ // Keep invalid operands for type checking.
+ if( t1 instanceof TypeInteger x && t2 instanceof TypeInteger y ) {
+ if( y.isConstant() && y.value()==0 ) return lhs;
+ if( x.isConstant() && x.value()==0 ) return new MinusNode(rhs);
+ }
// x - (-y) is x+y
if( rhs instanceof MinusNode minus )
diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/SubNode.java
index 650b7856..421e659b 100644
--- a/chapter07/src/main/java/com/seaofnodes/simple/node/SubNode.java
+++ b/chapter07/src/main/java/com/seaofnodes/simple/node/SubNode.java
@@ -39,11 +39,11 @@ public class SubNode extends Node {
Type t1 = lhs._type;
Type t2 = rhs._type;
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
- return lhs;
-
- if( t1.isConstant() && t1 instanceof TypeInteger i && i.value()==0 )
- return new MinusNode(rhs);
+ // Keep invalid operands for type checking.
+ if( t1 instanceof TypeInteger x && t2 instanceof TypeInteger y ) {
+ if( y.isConstant() && y.value()==0 ) return lhs;
+ if( x.isConstant() && x.value()==0 ) return new MinusNode(rhs);
+ }
// x - (-y) is x+y
if( rhs instanceof MinusNode minus )
diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/SubNode.java
index 650b7856..421e659b 100644
--- a/chapter08/src/main/java/com/seaofnodes/simple/node/SubNode.java
+++ b/chapter08/src/main/java/com/seaofnodes/simple/node/SubNode.java
@@ -39,11 +39,11 @@ public class SubNode extends Node {
Type t1 = lhs._type;
Type t2 = rhs._type;
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
- return lhs;
-
- if( t1.isConstant() && t1 instanceof TypeInteger i && i.value()==0 )
- return new MinusNode(rhs);
+ // Keep invalid operands for type checking.
+ if( t1 instanceof TypeInteger x && t2 instanceof TypeInteger y ) {
+ if( y.isConstant() && y.value()==0 ) return lhs;
+ if( x.isConstant() && x.value()==0 ) return new MinusNode(rhs);
+ }
// x - (-y) is x+y
if( rhs instanceof MinusNode minus )
diff --git a/chapter09/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter09/src/main/java/com/seaofnodes/simple/node/SubNode.java
index 8fd6d3ed..eeec5e1d 100644
--- a/chapter09/src/main/java/com/seaofnodes/simple/node/SubNode.java
+++ b/chapter09/src/main/java/com/seaofnodes/simple/node/SubNode.java
@@ -42,11 +42,11 @@ public class SubNode extends Node {
Type t1 = lhs._type;
Type t2 = rhs._type;
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
- return lhs;
-
- if( t1.isConstant() && t1 instanceof TypeInteger i && i.value()==0 )
- return new MinusNode(rhs);
+ // Keep invalid operands for type checking.
+ if( t1 instanceof TypeInteger x && t2 instanceof TypeInteger y ) {
+ if( y.isConstant() && y.value()==0 ) return lhs;
+ if( x.isConstant() && x.value()==0 ) return new MinusNode(rhs);
+ }
// x - (-y) is x+y
if( rhs instanceof MinusNode minus )
diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/SubNode.java
index 8fd6d3ed..eeec5e1d 100644
--- a/chapter10/src/main/java/com/seaofnodes/simple/node/SubNode.java
+++ b/chapter10/src/main/java/com/seaofnodes/simple/node/SubNode.java
@@ -42,11 +42,11 @@ public class SubNode extends Node {
Type t1 = lhs._type;
Type t2 = rhs._type;
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
- return lhs;
-
- if( t1.isConstant() && t1 instanceof TypeInteger i && i.value()==0 )
- return new MinusNode(rhs);
+ // Keep invalid operands for type checking.
+ if( t1 instanceof TypeInteger x && t2 instanceof TypeInteger y ) {
+ if( y.isConstant() && y.value()==0 ) return lhs;
+ if( x.isConstant() && x.value()==0 ) return new MinusNode(rhs);
+ }
// x - (-y) is x+y
if( rhs instanceof MinusNode minus )
diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/SubNode.java
index e4494a5a..2c5bafca 100644
--- a/chapter11/src/main/java/com/seaofnodes/simple/node/SubNode.java
+++ b/chapter11/src/main/java/com/seaofnodes/simple/node/SubNode.java
@@ -43,11 +43,11 @@ public class SubNode extends Node {
Type t1 = lhs._type;
Type t2 = rhs._type;
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
- return lhs;
-
- if( t1.isConstant() && t1 instanceof TypeInteger i && i.value()==0 )
- return new MinusNode(rhs);
+ // Keep invalid operands for type checking.
+ if( t1 instanceof TypeInteger x && t2 instanceof TypeInteger y ) {
+ if( y.isConstant() && y.value()==0 ) return lhs;
+ if( x.isConstant() && x.value()==0 ) return new MinusNode(rhs);
+ }
// x - (-y) is x+y
if( rhs instanceof MinusNode minus )
diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/SubNode.java
index d55cbb4e..4ae302f0 100644
--- a/chapter12/src/main/java/com/seaofnodes/simple/node/SubNode.java
+++ b/chapter12/src/main/java/com/seaofnodes/simple/node/SubNode.java
@@ -44,11 +44,11 @@ public class SubNode extends Node {
Type t1 = lhs._type;
Type t2 = rhs._type;
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
- return lhs;
-
- if( t1.isConstant() && t1 instanceof TypeInteger i && i.value()==0 )
- return new MinusNode(rhs);
+ // Keep invalid operands for type checking.
+ if( t1 instanceof TypeInteger x && t2 instanceof TypeInteger y ) {
+ if( y.isConstant() && y.value()==0 ) return lhs;
+ if( x.isConstant() && x.value()==0 ) return new MinusNode(rhs);
+ }
// x - (-y) is x+y
if( rhs instanceof MinusNode minus )
diff --git a/chapter12/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter12/src/test/java/com/seaofnodes/simple/Chapter12Test.java
index d862df95..9da4a4b8 100644
--- a/chapter12/src/test/java/com/seaofnodes/simple/Chapter12Test.java
+++ b/chapter12/src/test/java/com/seaofnodes/simple/Chapter12Test.java
@@ -9,6 +9,13 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter12Test {
+ @Test public void testSubZeroFloat() {
+ var stop = new Parser("flt x = arg; return 0-x;").parse().iterate();
+ // Compare strings so +0.0 and -0.0 remain distinct.
+ assertEquals("0.0", Evaluator.evaluate(stop,0).toString());
+ assertEquals("-1.0", Evaluator.evaluate(stop,1).toString());
+ }
+
@Test public void testDeadNumericReturns() {
for( String[] test : new String[][] {
{"if(1) return 7; else return 2.5;", "7"},
diff --git a/chapter13/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/SubNode.java
index d55cbb4e..4ae302f0 100644
--- a/chapter13/src/main/java/com/seaofnodes/simple/node/SubNode.java
+++ b/chapter13/src/main/java/com/seaofnodes/simple/node/SubNode.java
@@ -44,11 +44,11 @@ public class SubNode extends Node {
Type t1 = lhs._type;
Type t2 = rhs._type;
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
- return lhs;
-
- if( t1.isConstant() && t1 instanceof TypeInteger i && i.value()==0 )
- return new MinusNode(rhs);
+ // Keep invalid operands for type checking.
+ if( t1 instanceof TypeInteger x && t2 instanceof TypeInteger y ) {
+ if( y.isConstant() && y.value()==0 ) return lhs;
+ if( x.isConstant() && x.value()==0 ) return new MinusNode(rhs);
+ }
// x - (-y) is x+y
if( rhs instanceof MinusNode minus )
diff --git a/chapter13/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter13/src/test/java/com/seaofnodes/simple/Chapter12Test.java
index 57455510..a1c7a92a 100644
--- a/chapter13/src/test/java/com/seaofnodes/simple/Chapter12Test.java
+++ b/chapter13/src/test/java/com/seaofnodes/simple/Chapter12Test.java
@@ -8,6 +8,13 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter12Test {
+ @Test public void testSubZeroFloat() {
+ var stop = new Parser("flt x = arg; return 0-x;").parse().iterate();
+ // Compare strings so +0.0 and -0.0 remain distinct.
+ assertEquals("0.0", Evaluator.evaluate(stop,0).toString());
+ assertEquals("-1.0", Evaluator.evaluate(stop,1).toString());
+ }
+
@Test public void testDeadNumericReturns() {
for( String[] test : new String[][] {
{"if(1) return 7; else return 2.5;", "7"},
diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/SubNode.java
index 38e28ba4..1bae995b 100644
--- a/chapter14/src/main/java/com/seaofnodes/simple/node/SubNode.java
+++ b/chapter14/src/main/java/com/seaofnodes/simple/node/SubNode.java
@@ -43,11 +43,11 @@ public class SubNode extends Node {
Type t1 = lhs._type;
Type t2 = rhs._type;
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
- return lhs;
-
- if( t1.isConstant() && t1 instanceof TypeInteger i && i.value()==0 )
- return new MinusNode(rhs);
+ // Keep invalid operands for type checking.
+ if( t1 instanceof TypeInteger x && t2 instanceof TypeInteger y ) {
+ if( y.isConstant() && y.value()==0 ) return lhs;
+ if( x.isConstant() && x.value()==0 ) return new MinusNode(rhs);
+ }
// x - (-y) is x+y
if( rhs instanceof MinusNode minus )
diff --git a/chapter14/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter14/src/test/java/com/seaofnodes/simple/Chapter12Test.java
index c8802f4c..4eaf81df 100644
--- a/chapter14/src/test/java/com/seaofnodes/simple/Chapter12Test.java
+++ b/chapter14/src/test/java/com/seaofnodes/simple/Chapter12Test.java
@@ -8,6 +8,13 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter12Test {
+ @Test public void testSubZeroFloat() {
+ var stop = new Parser("flt x = arg; return 0-x;").parse().iterate();
+ // Compare strings so +0.0 and -0.0 remain distinct.
+ assertEquals("0.0", Evaluator.evaluate(stop,0).toString());
+ assertEquals("-1.0", Evaluator.evaluate(stop,1).toString());
+ }
+
@Test public void testDeadNumericReturns() {
for( String[] test : new String[][] {
{"if(1) return 7; else return 2.5;", "7"},
diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/SubNode.java
index a433ca90..237e40ed 100644
--- a/chapter15/src/main/java/com/seaofnodes/simple/node/SubNode.java
+++ b/chapter15/src/main/java/com/seaofnodes/simple/node/SubNode.java
@@ -42,11 +42,11 @@ public class SubNode extends Node {
Type t1 = lhs._type;
Type t2 = rhs._type;
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
- return lhs;
-
- if( t1.isConstant() && t1 instanceof TypeInteger i && i.value()==0 )
- return new MinusNode(rhs);
+ // Keep invalid operands for type checking.
+ if( t1 instanceof TypeInteger x && t2 instanceof TypeInteger y ) {
+ if( y.isConstant() && y.value()==0 ) return lhs;
+ if( x.isConstant() && x.value()==0 ) return new MinusNode(rhs);
+ }
// x - (-y) is x+y
if( rhs instanceof MinusNode minus )
diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter12Test.java
index c8802f4c..4eaf81df 100644
--- a/chapter15/src/test/java/com/seaofnodes/simple/Chapter12Test.java
+++ b/chapter15/src/test/java/com/seaofnodes/simple/Chapter12Test.java
@@ -8,6 +8,13 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter12Test {
+ @Test public void testSubZeroFloat() {
+ var stop = new Parser("flt x = arg; return 0-x;").parse().iterate();
+ // Compare strings so +0.0 and -0.0 remain distinct.
+ assertEquals("0.0", Evaluator.evaluate(stop,0).toString());
+ assertEquals("-1.0", Evaluator.evaluate(stop,1).toString());
+ }
+
@Test public void testDeadNumericReturns() {
for( String[] test : new String[][] {
{"if(1) return 7; else return 2.5;", "7"},
diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/SubNode.java
index a433ca90..237e40ed 100644
--- a/chapter16/src/main/java/com/seaofnodes/simple/node/SubNode.java
+++ b/chapter16/src/main/java/com/seaofnodes/simple/node/SubNode.java
@@ -42,11 +42,11 @@ public class SubNode extends Node {
Type t1 = lhs._type;
Type t2 = rhs._type;
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
- return lhs;
-
- if( t1.isConstant() && t1 instanceof TypeInteger i && i.value()==0 )
- return new MinusNode(rhs);
+ // Keep invalid operands for type checking.
+ if( t1 instanceof TypeInteger x && t2 instanceof TypeInteger y ) {
+ if( y.isConstant() && y.value()==0 ) return lhs;
+ if( x.isConstant() && x.value()==0 ) return new MinusNode(rhs);
+ }
// x - (-y) is x+y
if( rhs instanceof MinusNode minus )
diff --git a/chapter16/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter16/src/test/java/com/seaofnodes/simple/Chapter12Test.java
index 4db27407..c93f905d 100644
--- a/chapter16/src/test/java/com/seaofnodes/simple/Chapter12Test.java
+++ b/chapter16/src/test/java/com/seaofnodes/simple/Chapter12Test.java
@@ -8,6 +8,13 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter12Test {
+ @Test public void testSubZeroFloat() {
+ var stop = new Parser("flt x = arg; return 0-x;").parse().iterate();
+ // Compare strings so +0.0 and -0.0 remain distinct.
+ assertEquals("0.0", Evaluator.evaluate(stop,0).toString());
+ assertEquals("-1.0", Evaluator.evaluate(stop,1).toString());
+ }
+
@Test public void testDeadNumericReturns() {
for( String[] test : new String[][] {
{"if(1) return 7; else return 2.5;", "7"},
diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/SubNode.java
index 044d0c62..2f2a7a05 100644
--- a/chapter17/src/main/java/com/seaofnodes/simple/node/SubNode.java
+++ b/chapter17/src/main/java/com/seaofnodes/simple/node/SubNode.java
@@ -47,11 +47,11 @@ public class SubNode extends Node {
Type t1 = lhs._type;
Type t2 = rhs._type;
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
- return lhs;
-
- if( t1.isConstant() && t1 instanceof TypeInteger i && i.value()==0 )
- return new MinusNode(rhs);
+ // Keep invalid operands for type checking.
+ if( t1 instanceof TypeInteger x && t2 instanceof TypeInteger y ) {
+ if( y.isConstant() && y.value()==0 ) return lhs;
+ if( x.isConstant() && x.value()==0 ) return new MinusNode(rhs);
+ }
// x - (-y) is x+y
if( rhs instanceof MinusNode minus )
diff --git a/chapter17/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter17/src/test/java/com/seaofnodes/simple/Chapter12Test.java
index e81f9845..f7efde96 100644
--- a/chapter17/src/test/java/com/seaofnodes/simple/Chapter12Test.java
+++ b/chapter17/src/test/java/com/seaofnodes/simple/Chapter12Test.java
@@ -8,6 +8,13 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter12Test {
+ @Test public void testSubZeroFloat() {
+ var stop = new Parser("flt x = arg; return 0-x;").parse().iterate();
+ // Compare strings so +0.0 and -0.0 remain distinct.
+ assertEquals("0.0", Evaluator.evaluate(stop,0).toString());
+ assertEquals("-1.0", Evaluator.evaluate(stop,1).toString());
+ }
+
@Test public void testDeadNumericReturns() {
for( String[] test : new String[][] {
{"if(1) return 7; else return 2.5;", "7"},
diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/SubNode.java
index 299f33b8..034f2fb1 100644
--- a/chapter18/src/main/java/com/seaofnodes/simple/node/SubNode.java
+++ b/chapter18/src/main/java/com/seaofnodes/simple/node/SubNode.java
@@ -46,11 +46,11 @@ public class SubNode extends Node {
Type t1 = lhs._type;
Type t2 = rhs._type;
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
- return lhs;
-
- if( t1.isConstant() && t1 instanceof TypeInteger i && i.value()==0 )
- return new MinusNode(rhs);
+ // Keep invalid operands for type checking.
+ if( t1 instanceof TypeInteger x && t2 instanceof TypeInteger y ) {
+ if( y.isConstant() && y.value()==0 ) return lhs;
+ if( x.isConstant() && x.value()==0 ) return new MinusNode(rhs);
+ }
// x - (-y) is x+y
if( rhs instanceof MinusNode minus )
diff --git a/chapter18/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter18/src/test/java/com/seaofnodes/simple/Chapter12Test.java
index 43345809..1e7bfc6b 100644
--- a/chapter18/src/test/java/com/seaofnodes/simple/Chapter12Test.java
+++ b/chapter18/src/test/java/com/seaofnodes/simple/Chapter12Test.java
@@ -8,6 +8,13 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter12Test {
+ @Test public void testSubZeroFloat() {
+ var code = new CodeGen("flt x = arg; return 0-x;").parse().opto().typeCheck();
+ // Compare strings so +0.0 and -0.0 remain distinct.
+ assertEquals("0.0", Eval2.eval(code,0));
+ assertEquals("-1.0", Eval2.eval(code,1));
+ }
+
@Test public void testDeadNumericReturns() {
for( String[] test : new String[][] {
{"if(1) return 7; else return 2.5;", "7"},
diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/SubNode.java
index 299f33b8..034f2fb1 100644
--- a/chapter19/src/main/java/com/seaofnodes/simple/node/SubNode.java
+++ b/chapter19/src/main/java/com/seaofnodes/simple/node/SubNode.java
@@ -46,11 +46,11 @@ public class SubNode extends Node {
Type t1 = lhs._type;
Type t2 = rhs._type;
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
- return lhs;
-
- if( t1.isConstant() && t1 instanceof TypeInteger i && i.value()==0 )
- return new MinusNode(rhs);
+ // Keep invalid operands for type checking.
+ if( t1 instanceof TypeInteger x && t2 instanceof TypeInteger y ) {
+ if( y.isConstant() && y.value()==0 ) return lhs;
+ if( x.isConstant() && x.value()==0 ) return new MinusNode(rhs);
+ }
// x - (-y) is x+y
if( rhs instanceof MinusNode minus )
diff --git a/chapter19/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter19/src/test/java/com/seaofnodes/simple/Chapter12Test.java
index 43345809..1e7bfc6b 100644
--- a/chapter19/src/test/java/com/seaofnodes/simple/Chapter12Test.java
+++ b/chapter19/src/test/java/com/seaofnodes/simple/Chapter12Test.java
@@ -8,6 +8,13 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter12Test {
+ @Test public void testSubZeroFloat() {
+ var code = new CodeGen("flt x = arg; return 0-x;").parse().opto().typeCheck();
+ // Compare strings so +0.0 and -0.0 remain distinct.
+ assertEquals("0.0", Eval2.eval(code,0));
+ assertEquals("-1.0", Eval2.eval(code,1));
+ }
+
@Test public void testDeadNumericReturns() {
for( String[] test : new String[][] {
{"if(1) return 7; else return 2.5;", "7"},
diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/SubNode.java
index 299f33b8..034f2fb1 100644
--- a/chapter20/src/main/java/com/seaofnodes/simple/node/SubNode.java
+++ b/chapter20/src/main/java/com/seaofnodes/simple/node/SubNode.java
@@ -46,11 +46,11 @@ public class SubNode extends Node {
Type t1 = lhs._type;
Type t2 = rhs._type;
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
- return lhs;
-
- if( t1.isConstant() && t1 instanceof TypeInteger i && i.value()==0 )
- return new MinusNode(rhs);
+ // Keep invalid operands for type checking.
+ if( t1 instanceof TypeInteger x && t2 instanceof TypeInteger y ) {
+ if( y.isConstant() && y.value()==0 ) return lhs;
+ if( x.isConstant() && x.value()==0 ) return new MinusNode(rhs);
+ }
// x - (-y) is x+y
if( rhs instanceof MinusNode minus )
diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter12Test.java
index 21759b78..c50be559 100644
--- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter12Test.java
+++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter12Test.java
@@ -7,6 +7,13 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter12Test {
+ @Test public void testSubZeroFloat() {
+ var code = new CodeGen("flt x = arg; return 0-x;").parse().opto().typeCheck();
+ // Compare strings so +0.0 and -0.0 remain distinct.
+ assertEquals("0.0", Eval2.eval(code,0));
+ assertEquals("-1.0", Eval2.eval(code,1));
+ }
+
@Test public void testDeadNumericReturns() {
for( String[] test : new String[][] {
{"if(1) return 7; else return 2.5;", "7"},
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/SubNode.java
index 5ba02dc5..3b3a6321 100644
--- a/chapter21/src/main/java/com/seaofnodes/simple/node/SubNode.java
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/SubNode.java
@@ -46,11 +46,11 @@ public class SubNode extends Node {
Type t1 = lhs._type;
Type t2 = rhs._type;
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
- return lhs;
-
- if( t1.isConstant() && t1 instanceof TypeInteger i && i.value()==0 )
- return new MinusNode(rhs);
+ // Keep invalid operands for type checking.
+ if( t1 instanceof TypeInteger x && t2 instanceof TypeInteger y ) {
+ if( y.isConstant() && y.value()==0 ) return lhs;
+ if( x.isConstant() && x.value()==0 ) return new MinusNode(rhs);
+ }
// x - (-y) is x+y
if( rhs instanceof MinusNode minus )
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter21/src/test/java/com/seaofnodes/simple/Chapter12Test.java
index 21759b78..c50be559 100644
--- a/chapter21/src/test/java/com/seaofnodes/simple/Chapter12Test.java
+++ b/chapter21/src/test/java/com/seaofnodes/simple/Chapter12Test.java
@@ -7,6 +7,13 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter12Test {
+ @Test public void testSubZeroFloat() {
+ var code = new CodeGen("flt x = arg; return 0-x;").parse().opto().typeCheck();
+ // Compare strings so +0.0 and -0.0 remain distinct.
+ assertEquals("0.0", Eval2.eval(code,0));
+ assertEquals("-1.0", Eval2.eval(code,1));
+ }
+
@Test public void testDeadNumericReturns() {
for( String[] test : new String[][] {
{"if(1) return 7; else return 2.5;", "7"},
diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/SubNode.java
index 6dbd55cb..2c4fed39 100644
--- a/chapter22/src/main/java/com/seaofnodes/simple/node/SubNode.java
+++ b/chapter22/src/main/java/com/seaofnodes/simple/node/SubNode.java
@@ -32,11 +32,11 @@ public class SubNode extends ArithNode {
Type t1 = lhs._type;
Type t2 = rhs._type;
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
- return lhs;
-
- if( t1.isConstant() && t1 instanceof TypeInteger i && i.value()==0 )
- return new MinusNode(rhs);
+ // Keep invalid operands for type checking.
+ if( t1 instanceof TypeInteger x && t2 instanceof TypeInteger y ) {
+ if( y.isConstant() && y.value()==0 ) return lhs;
+ if( x.isConstant() && x.value()==0 ) return new MinusNode(rhs);
+ }
// x - (-y) is x+y
if( rhs instanceof MinusNode minus )
diff --git a/chapter22/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter22/src/test/java/com/seaofnodes/simple/Chapter12Test.java
index 21759b78..c50be559 100644
--- a/chapter22/src/test/java/com/seaofnodes/simple/Chapter12Test.java
+++ b/chapter22/src/test/java/com/seaofnodes/simple/Chapter12Test.java
@@ -7,6 +7,13 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter12Test {
+ @Test public void testSubZeroFloat() {
+ var code = new CodeGen("flt x = arg; return 0-x;").parse().opto().typeCheck();
+ // Compare strings so +0.0 and -0.0 remain distinct.
+ assertEquals("0.0", Eval2.eval(code,0));
+ assertEquals("-1.0", Eval2.eval(code,1));
+ }
+
@Test public void testDeadNumericReturns() {
for( String[] test : new String[][] {
{"if(1) return 7; else return 2.5;", "7"},
diff --git a/chapter22/src/test/java/com/seaofnodes/simple/Chapter22Test.java b/chapter22/src/test/java/com/seaofnodes/simple/Chapter22Test.java
index 369e365e..8d0f4c65 100644
--- a/chapter22/src/test/java/com/seaofnodes/simple/Chapter22Test.java
+++ b/chapter22/src/test/java/com/seaofnodes/simple/Chapter22Test.java
@@ -13,6 +13,15 @@ import org.junit.Test;
import static org.junit.Assert.*;
public class Chapter22Test {
+ @Test public void testSubZeroTypeError() {
+ try {
+ new CodeGen("return null-0;").parse().opto().typeCheck();
+ fail("Subtraction must reject null even when the other operand is zero");
+ } catch( Parser.ParseException e ) {
+ assertEquals("Cannot '-' null",e.getMessage());
+ }
+ }
+
@Test public void testInfiniteReturn() {
String src = "struct S { int i; }; S !s = new S; while(1) s.i++; return s.i;";
testCPU(src,"x86_64_v2","SystemV",0,"return Top;");
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/SubNode.java
index 6dbd55cb..2c4fed39 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/SubNode.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/SubNode.java
@@ -32,11 +32,11 @@ public class SubNode extends ArithNode {
Type t1 = lhs._type;
Type t2 = rhs._type;
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
- return lhs;
-
- if( t1.isConstant() && t1 instanceof TypeInteger i && i.value()==0 )
- return new MinusNode(rhs);
+ // Keep invalid operands for type checking.
+ if( t1 instanceof TypeInteger x && t2 instanceof TypeInteger y ) {
+ if( y.isConstant() && y.value()==0 ) return lhs;
+ if( x.isConstant() && x.value()==0 ) return new MinusNode(rhs);
+ }
// x - (-y) is x+y
if( rhs instanceof MinusNode minus )
diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter23/src/test/java/com/seaofnodes/simple/Chapter12Test.java
index 21759b78..c50be559 100644
--- a/chapter23/src/test/java/com/seaofnodes/simple/Chapter12Test.java
+++ b/chapter23/src/test/java/com/seaofnodes/simple/Chapter12Test.java
@@ -7,6 +7,13 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter12Test {
+ @Test public void testSubZeroFloat() {
+ var code = new CodeGen("flt x = arg; return 0-x;").parse().opto().typeCheck();
+ // Compare strings so +0.0 and -0.0 remain distinct.
+ assertEquals("0.0", Eval2.eval(code,0));
+ assertEquals("-1.0", Eval2.eval(code,1));
+ }
+
@Test public void testDeadNumericReturns() {
for( String[] test : new String[][] {
{"if(1) return 7; else return 2.5;", "7"},
diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Chapter22Test.java b/chapter23/src/test/java/com/seaofnodes/simple/Chapter22Test.java
index a3e9a016..bca06f0c 100644
--- a/chapter23/src/test/java/com/seaofnodes/simple/Chapter22Test.java
+++ b/chapter23/src/test/java/com/seaofnodes/simple/Chapter22Test.java
@@ -13,6 +13,15 @@ import org.junit.Test;
import static org.junit.Assert.*;
public class Chapter22Test {
+ @Test public void testSubZeroTypeError() {
+ try {
+ new CodeGen("return null-0;").parse().opto().typeCheck();
+ fail("Subtraction must reject null even when the other operand is zero");
+ } catch( Parser.ParseException e ) {
+ assertEquals("Cannot '-' null",e.getMessage());
+ }
+ }
+
@Test public void testInfiniteReturn() {
String src = "struct S { int i; }; S !s = new S; while(1) s.i++; return s.i;";
testCPU(src,"x86_64_v2","SystemV",0,"return Top;");
diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/SubNode.java
index 6dbd55cb..2c4fed39 100644
--- a/chapter24/src/main/java/com/seaofnodes/simple/node/SubNode.java
+++ b/chapter24/src/main/java/com/seaofnodes/simple/node/SubNode.java
@@ -32,11 +32,11 @@ public class SubNode extends ArithNode {
Type t1 = lhs._type;
Type t2 = rhs._type;
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
- return lhs;
-
- if( t1.isConstant() && t1 instanceof TypeInteger i && i.value()==0 )
- return new MinusNode(rhs);
+ // Keep invalid operands for type checking.
+ if( t1 instanceof TypeInteger x && t2 instanceof TypeInteger y ) {
+ if( y.isConstant() && y.value()==0 ) return lhs;
+ if( x.isConstant() && x.value()==0 ) return new MinusNode(rhs);
+ }
// x - (-y) is x+y
if( rhs instanceof MinusNode minus )
diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter24/src/test/java/com/seaofnodes/simple/Chapter12Test.java
index 21759b78..c50be559 100644
--- a/chapter24/src/test/java/com/seaofnodes/simple/Chapter12Test.java
+++ b/chapter24/src/test/java/com/seaofnodes/simple/Chapter12Test.java
@@ -7,6 +7,13 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter12Test {
+ @Test public void testSubZeroFloat() {
+ var code = new CodeGen("flt x = arg; return 0-x;").parse().opto().typeCheck();
+ // Compare strings so +0.0 and -0.0 remain distinct.
+ assertEquals("0.0", Eval2.eval(code,0));
+ assertEquals("-1.0", Eval2.eval(code,1));
+ }
+
@Test public void testDeadNumericReturns() {
for( String[] test : new String[][] {
{"if(1) return 7; else return 2.5;", "7"},
diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Chapter22Test.java b/chapter24/src/test/java/com/seaofnodes/simple/Chapter22Test.java
index 7c2f4f69..e4eb03c0 100644
--- a/chapter24/src/test/java/com/seaofnodes/simple/Chapter22Test.java
+++ b/chapter24/src/test/java/com/seaofnodes/simple/Chapter22Test.java
@@ -13,6 +13,15 @@ import org.junit.Test;
import static org.junit.Assert.*;
public class Chapter22Test {
+ @Test public void testSubZeroTypeError() {
+ try {
+ new CodeGen("return null-0;").parse().opto().typeCheck();
+ fail("Subtraction must reject null even when the other operand is zero");
+ } catch( Parser.ParseException e ) {
+ assertEquals("Cannot '-' null",e.getMessage());
+ }
+ }
+
@Test public void testInfiniteReturn() {
String src = "struct S { int i; }; S !s = new S; while(1) s.i++; return s.i;";
testCPU(src,"x86_64_v2","SystemV",0,"return Top;");
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/SubNode.java
index c294ab5c..8eb1f4cc 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/SubNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/SubNode.java
@@ -35,11 +35,11 @@ public class SubNode extends ArithNode {
Type t1 = lhs._type;
Type t2 = rhs._type;
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
- return lhs;
-
- if( t1.isConstant() && t1 instanceof TypeInteger i && i.value()==0 )
- return new MinusNode(rhs);
+ // Keep invalid operands and unresolved numeric modes for type checking.
+ if( _mode==1 && t1 instanceof TypeInteger x && t2 instanceof TypeInteger y ) {
+ if( y.isConstant() && y.value()==0 ) return lhs;
+ if( x.isConstant() && x.value()==0 ) return new MinusNode(rhs);
+ }
// x - (-y) is x+y
if( rhs instanceof MinusNode minus )
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter12Test.java
index 4b2d48d8..0f4bb0b4 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter12Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter12Test.java
@@ -5,6 +5,13 @@ import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class Chapter12Test {
+ @Test public void testSubZeroFloat() {
+ var code = new CodeGen("flt x = arg; return 0-x;").parse().opto().typeCheck();
+ // Compare strings so +0.0 and -0.0 remain distinct.
+ assertEquals("0.0", Eval2.eval(code,0));
+ assertEquals("-1.0", Eval2.eval(code,1));
+ }
+
@Test public void testDeadNumericReturns() {
for( String[] test : new String[][] {
{"if(1) return 7; else return 2.5;", "7"},
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter22Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter22Test.java
index fde523cf..4b036e62 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter22Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter22Test.java
@@ -11,6 +11,15 @@ import org.junit.Test;
import static org.junit.Assert.*;
public class Chapter22Test {
+ @Test public void testSubZeroTypeError() {
+ try {
+ new CodeGen("return null-0;").parse().opto().typeCheck();
+ fail("Subtraction must reject null even when the other operand is zero");
+ } catch( Parser.ParseException e ) {
+ assertEquals("Cannot null - 0",e.getMessage());
+ }
+ }
+
@Test
public void testJig() throws IOException {
diff --git a/docs/chapter-backports.md b/docs/chapter-backports.md
index 0f20d0b3..3cbd93c1 100644
--- a/docs/chapter-backports.md
+++ b/docs/chapter-backports.md
@@ -218,6 +218,7 @@ Unresolved reproductions have been promoted to the pending queue above.
| Completed work | Scope and evidence retained |
|---|---|
+| #254 subtraction zero identities | Require integer operands in 4-25 and resolved integer mode in 25. Preserve floating-point signed zero and subtraction diagnostics. `Chapter12Test.testSubZeroFloat` runs in 12-25; `Chapter22Test.testSubZeroTypeError` runs in 22-25. Against the original PR, the signed-zero test fails in 25 and the diagnostic test fails in 22-25. Full Make suites passed in every chapter 4-25 on 2026-09-25, including fresh `sys.o` compilation in 25. |
| 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. |