summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-10-05 09:35:31 -0700
committerCliff Click <cliffc@acm.org>2026-10-05 09:35:31 -0700
commit3e950abc9eb2bcafbb36761ace5fcf5707a503a3 (patch)
tree263244543dc0a63d454b16521a30ccb4526f58e1
parent9b3ad43f7aa2ec6d5f61fb9ff3c432101a5fba52 (diff)
downloadSimple-3e950abc9eb2bcafbb36761ace5fcf5707a503a3.tar.gz
Simple-3e950abc9eb2bcafbb36761ace5fcf5707a503a3.zip
Partial integer updateabandoned
-rw-r--r--chapter14/src/main/java/com/seaofnodes/simple/node/AddNode.java2
-rw-r--r--chapter14/src/main/java/com/seaofnodes/simple/node/AndNode.java2
-rw-r--r--chapter14/src/main/java/com/seaofnodes/simple/node/DivNode.java2
-rw-r--r--chapter14/src/main/java/com/seaofnodes/simple/node/MulNode.java2
-rw-r--r--chapter14/src/main/java/com/seaofnodes/simple/node/OrNode.java2
-rw-r--r--chapter14/src/main/java/com/seaofnodes/simple/node/SarNode.java2
-rw-r--r--chapter14/src/main/java/com/seaofnodes/simple/node/ShlNode.java2
-rw-r--r--chapter14/src/main/java/com/seaofnodes/simple/node/ShrNode.java10
-rw-r--r--chapter14/src/main/java/com/seaofnodes/simple/node/XorNode.java2
-rw-r--r--chapter14/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java76
-rw-r--r--chapter15/src/main/java/com/seaofnodes/simple/node/AddNode.java2
-rw-r--r--chapter15/src/main/java/com/seaofnodes/simple/node/AndNode.java2
-rw-r--r--chapter15/src/main/java/com/seaofnodes/simple/node/DivNode.java2
-rw-r--r--chapter15/src/main/java/com/seaofnodes/simple/node/MulNode.java2
-rw-r--r--chapter15/src/main/java/com/seaofnodes/simple/node/OrNode.java2
-rw-r--r--chapter15/src/main/java/com/seaofnodes/simple/node/SarNode.java2
-rw-r--r--chapter15/src/main/java/com/seaofnodes/simple/node/ShlNode.java2
-rw-r--r--chapter15/src/main/java/com/seaofnodes/simple/node/ShrNode.java2
-rw-r--r--chapter15/src/main/java/com/seaofnodes/simple/node/XorNode.java2
-rw-r--r--chapter15/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java76
-rw-r--r--chapter16/src/main/java/com/seaofnodes/simple/node/AddNode.java2
-rw-r--r--chapter16/src/main/java/com/seaofnodes/simple/node/AndNode.java2
-rw-r--r--chapter16/src/main/java/com/seaofnodes/simple/node/DivNode.java2
-rw-r--r--chapter16/src/main/java/com/seaofnodes/simple/node/MulNode.java2
-rw-r--r--chapter16/src/main/java/com/seaofnodes/simple/node/OrNode.java2
-rw-r--r--chapter16/src/main/java/com/seaofnodes/simple/node/SarNode.java2
-rw-r--r--chapter16/src/main/java/com/seaofnodes/simple/node/ShlNode.java2
-rw-r--r--chapter16/src/main/java/com/seaofnodes/simple/node/ShrNode.java2
-rw-r--r--chapter16/src/main/java/com/seaofnodes/simple/node/XorNode.java2
-rw-r--r--chapter16/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java76
-rw-r--r--chapter17/src/main/java/com/seaofnodes/simple/node/AddNode.java2
-rw-r--r--chapter17/src/main/java/com/seaofnodes/simple/node/AndNode.java2
-rw-r--r--chapter17/src/main/java/com/seaofnodes/simple/node/DivNode.java2
-rw-r--r--chapter17/src/main/java/com/seaofnodes/simple/node/MulNode.java2
-rw-r--r--chapter17/src/main/java/com/seaofnodes/simple/node/OrNode.java2
-rw-r--r--chapter17/src/main/java/com/seaofnodes/simple/node/SarNode.java2
-rw-r--r--chapter17/src/main/java/com/seaofnodes/simple/node/ShlNode.java2
-rw-r--r--chapter17/src/main/java/com/seaofnodes/simple/node/ShrNode.java2
-rw-r--r--chapter17/src/main/java/com/seaofnodes/simple/node/XorNode.java2
-rw-r--r--chapter17/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java76
-rw-r--r--chapter18/src/main/java/com/seaofnodes/simple/node/AddNode.java2
-rw-r--r--chapter18/src/main/java/com/seaofnodes/simple/node/AndNode.java2
-rw-r--r--chapter18/src/main/java/com/seaofnodes/simple/node/DivNode.java2
-rw-r--r--chapter18/src/main/java/com/seaofnodes/simple/node/MinusNode.java2
-rw-r--r--chapter18/src/main/java/com/seaofnodes/simple/node/MulNode.java2
-rw-r--r--chapter18/src/main/java/com/seaofnodes/simple/node/OrNode.java2
-rw-r--r--chapter18/src/main/java/com/seaofnodes/simple/node/SarNode.java2
-rw-r--r--chapter18/src/main/java/com/seaofnodes/simple/node/ShlNode.java2
-rw-r--r--chapter18/src/main/java/com/seaofnodes/simple/node/ShrNode.java2
-rw-r--r--chapter18/src/main/java/com/seaofnodes/simple/node/XorNode.java2
-rw-r--r--chapter18/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java92
-rw-r--r--chapter19/src/main/java/com/seaofnodes/simple/node/AddNode.java2
-rw-r--r--chapter19/src/main/java/com/seaofnodes/simple/node/AndNode.java2
-rw-r--r--chapter19/src/main/java/com/seaofnodes/simple/node/DivNode.java2
-rw-r--r--chapter19/src/main/java/com/seaofnodes/simple/node/MinusNode.java2
-rw-r--r--chapter19/src/main/java/com/seaofnodes/simple/node/MulNode.java2
-rw-r--r--chapter19/src/main/java/com/seaofnodes/simple/node/OrNode.java2
-rw-r--r--chapter19/src/main/java/com/seaofnodes/simple/node/SarNode.java2
-rw-r--r--chapter19/src/main/java/com/seaofnodes/simple/node/ShlNode.java2
-rw-r--r--chapter19/src/main/java/com/seaofnodes/simple/node/ShrNode.java2
-rw-r--r--chapter19/src/main/java/com/seaofnodes/simple/node/XorNode.java2
-rw-r--r--chapter19/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java92
-rw-r--r--chapter20/src/main/java/com/seaofnodes/simple/node/AddNode.java2
-rw-r--r--chapter20/src/main/java/com/seaofnodes/simple/node/DivNode.java2
-rw-r--r--chapter20/src/main/java/com/seaofnodes/simple/node/MinusNode.java2
-rw-r--r--chapter20/src/main/java/com/seaofnodes/simple/node/MulNode.java2
-rw-r--r--chapter20/src/main/java/com/seaofnodes/simple/node/OrNode.java2
-rw-r--r--chapter20/src/main/java/com/seaofnodes/simple/node/SarNode.java2
-rw-r--r--chapter20/src/main/java/com/seaofnodes/simple/node/ShlNode.java2
-rw-r--r--chapter20/src/main/java/com/seaofnodes/simple/node/ShrNode.java2
-rw-r--r--chapter20/src/main/java/com/seaofnodes/simple/node/XorNode.java2
-rw-r--r--chapter20/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java93
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/AddNode.java2
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/DivNode.java2
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/MinusNode.java2
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/MulNode.java2
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/OrNode.java2
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/SarNode.java2
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/ShlNode.java2
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/ShrNode.java2
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/XorNode.java2
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java93
-rw-r--r--chapter22/src/main/java/com/seaofnodes/simple/node/AddNode.java2
-rw-r--r--chapter22/src/main/java/com/seaofnodes/simple/node/DivNode.java2
-rw-r--r--chapter22/src/main/java/com/seaofnodes/simple/node/MinusNode.java2
-rw-r--r--chapter22/src/main/java/com/seaofnodes/simple/node/MulNode.java2
-rw-r--r--chapter22/src/main/java/com/seaofnodes/simple/node/OrNode.java2
-rw-r--r--chapter22/src/main/java/com/seaofnodes/simple/node/SarNode.java2
-rw-r--r--chapter22/src/main/java/com/seaofnodes/simple/node/ShlNode.java2
-rw-r--r--chapter22/src/main/java/com/seaofnodes/simple/node/ShrNode.java2
-rw-r--r--chapter22/src/main/java/com/seaofnodes/simple/node/XorNode.java2
-rw-r--r--chapter22/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java93
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/AddNode.java2
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/DivNode.java2
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/MinusNode.java2
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/MulNode.java2
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/OrNode.java2
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/SarNode.java2
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/ShlNode.java2
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/ShrNode.java2
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/XorNode.java2
-rw-r--r--chapter23/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java93
-rw-r--r--chapter24/src/main/java/com/seaofnodes/simple/node/AddNode.java2
-rw-r--r--chapter24/src/main/java/com/seaofnodes/simple/node/DivNode.java2
-rw-r--r--chapter24/src/main/java/com/seaofnodes/simple/node/MinusNode.java2
-rw-r--r--chapter24/src/main/java/com/seaofnodes/simple/node/MulNode.java2
-rw-r--r--chapter24/src/main/java/com/seaofnodes/simple/node/OrNode.java2
-rw-r--r--chapter24/src/main/java/com/seaofnodes/simple/node/SarNode.java2
-rw-r--r--chapter24/src/main/java/com/seaofnodes/simple/node/ShlNode.java2
-rw-r--r--chapter24/src/main/java/com/seaofnodes/simple/node/ShrNode.java2
-rw-r--r--chapter24/src/main/java/com/seaofnodes/simple/node/XorNode.java2
-rw-r--r--chapter24/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java93
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/AddNode.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/DivNode.java4
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/MinusNode.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/MulNode.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/SarNode.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ShlNode.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ShrNode.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/XorNode.java2
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/FuzzerWrap.java1
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java113
123 files changed, 1178 insertions, 119 deletions
diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/AddNode.java
index bd1a9dcd..6d48f408 100644
--- a/chapter14/src/main/java/com/seaofnodes/simple/node/AddNode.java
+++ b/chapter14/src/main/java/com/seaofnodes/simple/node/AddNode.java
@@ -47,7 +47,7 @@ public class AddNode extends Node {
// Add of 0. We do not check for (0+x) because this will already
// canonicalize to (x+0)
- if( t2 instanceof TypeInteger i && i.isConstant() && i.value()==0 )
+ if( t2 instanceof TypeInteger i && i.isConstant() && i.value()==0 && lhs._type instanceof TypeInteger )
return lhs;
// Add of same to a multiply by 2
diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/AndNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/AndNode.java
index 350859db..b15a2b2f 100644
--- a/chapter14/src/main/java/com/seaofnodes/simple/node/AndNode.java
+++ b/chapter14/src/main/java/com/seaofnodes/simple/node/AndNode.java
@@ -42,7 +42,7 @@ public class AndNode extends Node {
// And of -1. We do not check for (-1&x) because this will already
// canonicalize to (x&-1)
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==-1 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==-1 && lhs._type instanceof TypeInteger )
return lhs;
// Move constants to RHS: con*arg becomes arg*con
diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/DivNode.java
index da9571db..54e32dbc 100644
--- a/chapter14/src/main/java/com/seaofnodes/simple/node/DivNode.java
+++ b/chapter14/src/main/java/com/seaofnodes/simple/node/DivNode.java
@@ -37,7 +37,7 @@ public class DivNode extends Node {
@Override
public Node idealize() {
// Div of 1.
- if( in(2)._type.isConstant() && in(2)._type instanceof TypeInteger i && i.value()==1 )
+ if( in(2)._type.isConstant() && in(2)._type instanceof TypeInteger i && i.value()==1 && in(1)._type instanceof TypeInteger )
return in(1);
return null;
}
diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/MulNode.java
index 6f679462..2b2d4bd2 100644
--- a/chapter14/src/main/java/com/seaofnodes/simple/node/MulNode.java
+++ b/chapter14/src/main/java/com/seaofnodes/simple/node/MulNode.java
@@ -41,7 +41,7 @@ public class MulNode extends Node {
// Mul of 1. We do not check for (1*x) because this will already
// canonicalize to (x*1)
- if ( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==1 )
+ if ( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==1 && lhs._type instanceof TypeInteger )
return lhs;
// Move constants to RHS: con*arg becomes arg*con
diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/OrNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/OrNode.java
index e18996b7..de564d19 100644
--- a/chapter14/src/main/java/com/seaofnodes/simple/node/OrNode.java
+++ b/chapter14/src/main/java/com/seaofnodes/simple/node/OrNode.java
@@ -39,7 +39,7 @@ public class OrNode extends Node {
// Or of 0. We do not check for (0|x) because this will already
// canonicalize to (x|0)
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && lhs._type instanceof TypeInteger )
return lhs;
// Move constants to RHS: con*arg becomes arg*con
diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/SarNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/SarNode.java
index 881a3812..1fac6a25 100644
--- a/chapter14/src/main/java/com/seaofnodes/simple/node/SarNode.java
+++ b/chapter14/src/main/java/com/seaofnodes/simple/node/SarNode.java
@@ -40,7 +40,7 @@ public class SarNode extends Node {
Type t2 = rhs._type;
// Sar of 0.
- if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && lhs._type instanceof TypeInteger )
return lhs;
// TODO: x >> 3 >> (y ? 1 : 2) ==> x >> (y ? 4 : 5)
diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/ShlNode.java
index 605c5d86..e22256ee 100644
--- a/chapter14/src/main/java/com/seaofnodes/simple/node/ShlNode.java
+++ b/chapter14/src/main/java/com/seaofnodes/simple/node/ShlNode.java
@@ -37,7 +37,7 @@ public class ShlNode extends Node {
Type t2 = rhs._type;
// Shl of 0.
- if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && lhs._type instanceof TypeInteger )
return lhs;
// TODO: x << 3 << (y ? 1 : 2) ==> x << (y ? 4 : 5)
diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/ShrNode.java
index bed18d2b..de6e56f1 100644
--- a/chapter14/src/main/java/com/seaofnodes/simple/node/ShrNode.java
+++ b/chapter14/src/main/java/com/seaofnodes/simple/node/ShrNode.java
@@ -26,14 +26,6 @@ public class ShrNode extends Node {
in(2)._type instanceof TypeInteger i2) {
if( i1.isConstant() && i2.isConstant() )
return TypeInteger.constant(i1.value()>>>i2.value());
- if( i2._min < 0 || i2._max >= 64 )
- return TypeInteger.BOT;
- // Zero shifting a negative makes a larger positive
- // so get the endpoints correct.
- long s1 = i1._min>>>i2._min;
- long s2 = i1._max>>>i2._min;
- boolean wrap = i1._min < 0 && i1._max >=0;
- return TypeInteger.make(wrap ? 0 : Math.min(s1,s2),Math.max(s1,s2));
}
return TypeInteger.BOT;
}
@@ -45,7 +37,7 @@ public class ShrNode extends Node {
Type t2 = rhs._type;
// Shr of 0.
- if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && lhs._type instanceof TypeInteger )
return lhs;
// TODO: x >>> 3 >>> (y ? 1 : 2) ==> x >>> (y ? 4 : 5)
diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/XorNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/XorNode.java
index 3978f048..077e7611 100644
--- a/chapter14/src/main/java/com/seaofnodes/simple/node/XorNode.java
+++ b/chapter14/src/main/java/com/seaofnodes/simple/node/XorNode.java
@@ -39,7 +39,7 @@ public class XorNode extends Node {
// Xor of 0. We do not check for (0^x) because this will already
// canonicalize to (x^0)
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && lhs._type instanceof TypeInteger )
return lhs;
// Move constants to RHS: con*arg becomes arg*con
diff --git a/chapter14/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java b/chapter14/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java
new file mode 100644
index 00000000..97650f25
--- /dev/null
+++ b/chapter14/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java
@@ -0,0 +1,76 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class IntegerIdentityTest {
+ private static final String[] OPS = {"And","Or","Xor","Shl","Sar","Shr","Add","Mul","Div"};
+
+ @Test public void testIdentityWaitsForIntegerInput() {
+ new Parser("return 0;").parse();
+ for( String name : OPS ) {
+ Node lhs = input(Type.BOTTOM);
+ Node rhs = input(TypeInteger.constant(identity(name)));
+ Node op = op(name,lhs,rhs);
+ assertFalse(name,lhs._type.isa(op.compute()));
+ assertNull(name,op.idealize());
+ // Model a provisional input lifting to int. The same fold can now fire.
+ lhs._type = TypeInteger.BOT;
+ assertSame(name,lhs,op.idealize());
+ assertTrue(name,lhs._type.isa(op.compute()));
+ }
+ }
+
+ @Test public void testNonIntegerIdentityCannotEraseTheOperator() {
+ new Parser("return 0;").parse();
+ for( Type type : new Type[]{TypeFloat.constant(3.5)} )
+ for( String name : OPS ) {
+ Node lhs=input(type);
+ Node op=op(name,lhs,input(TypeInteger.constant(identity(name))));
+ assertNull(name,op.idealize());
+ }
+ }
+
+ @Test public void testIntegerIdentitiesAndMaskedShiftsStillFold() {
+ new Parser("return 0;").parse();
+ for( String name : OPS )
+ for( Type type : new Type[]{TypeInteger.BOT,TypeInteger.make(2,7),TypeInteger.constant(5)} ) {
+ Node lhs=input(type);
+ long[] identities = name.equals("And") ? new long[]{-1} :
+ name.equals("Shl") || name.equals("Sar") || name.equals("Shr") ? new long[]{0,64,-64} : new long[]{identity(name)};
+ for( long identity : identities ) {
+ Node op=op(name,lhs,input(TypeInteger.constant(identity)));
+ assertSame(name,lhs,op.idealize());
+ assertTrue(name,lhs._type.isa(op.compute()));
+ }
+ }
+ }
+
+ // These tests exercise idealize directly, before GVN or constant folding.
+ private static Node input(Type type) {
+ Node n = new ConstantNode(type);
+ n._type=type;
+ return n;
+ }
+
+ private static long identity(String name) {
+ return name.equals("And") ? -1 : name.equals("Mul") || name.equals("Div") ? 1 : 0;
+ }
+
+ private static Node op(String name, Node lhs, Node rhs) {
+ return switch(name) {
+ case "And" -> new AndNode(lhs,rhs);
+ case "Or" -> new OrNode(lhs,rhs);
+ case "Xor" -> new XorNode(lhs,rhs);
+ case "Shl" -> new ShlNode(lhs,rhs);
+ case "Sar" -> new SarNode(lhs,rhs);
+ case "Shr" -> new ShrNode(lhs,rhs);
+ case "Add" -> new AddNode(lhs,rhs);
+ case "Mul" -> new MulNode(lhs,rhs);
+ case "Div" -> new DivNode(lhs,rhs);
+ default -> throw new AssertionError(name);
+ };
+ }
+}
diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/AddNode.java
index 97d2bb9b..ea5fdfdf 100644
--- a/chapter15/src/main/java/com/seaofnodes/simple/node/AddNode.java
+++ b/chapter15/src/main/java/com/seaofnodes/simple/node/AddNode.java
@@ -48,7 +48,7 @@ public class AddNode extends Node {
// Add of 0. We do not check for (0+x) because this will already
// canonicalize to (x+0)
- if( t2 instanceof TypeInteger i && i.isConstant() && i.value()==0 )
+ if( t2 instanceof TypeInteger i && i.isConstant() && i.value()==0 && lhs._type instanceof TypeInteger )
return lhs;
// Add of same to a multiply by 2
diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/AndNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/AndNode.java
index 7e4b898b..a170cc7e 100644
--- a/chapter15/src/main/java/com/seaofnodes/simple/node/AndNode.java
+++ b/chapter15/src/main/java/com/seaofnodes/simple/node/AndNode.java
@@ -41,7 +41,7 @@ public class AndNode extends Node {
// And of -1. We do not check for (-1&x) because this will already
// canonicalize to (x&-1)
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==-1 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==-1 && lhs._type instanceof TypeInteger )
return lhs;
// Move constants to RHS: con*arg becomes arg*con
diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/DivNode.java
index 76e98678..232390b2 100644
--- a/chapter15/src/main/java/com/seaofnodes/simple/node/DivNode.java
+++ b/chapter15/src/main/java/com/seaofnodes/simple/node/DivNode.java
@@ -37,7 +37,7 @@ public class DivNode extends Node {
@Override
public Node idealize() {
// Div of 1.
- if( in(2)._type == TypeInteger.TRUE )
+ if( in(2)._type == TypeInteger.TRUE && in(1)._type instanceof TypeInteger )
return in(1);
return null;
}
diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/MulNode.java
index 6f679462..2b2d4bd2 100644
--- a/chapter15/src/main/java/com/seaofnodes/simple/node/MulNode.java
+++ b/chapter15/src/main/java/com/seaofnodes/simple/node/MulNode.java
@@ -41,7 +41,7 @@ public class MulNode extends Node {
// Mul of 1. We do not check for (1*x) because this will already
// canonicalize to (x*1)
- if ( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==1 )
+ if ( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==1 && lhs._type instanceof TypeInteger )
return lhs;
// Move constants to RHS: con*arg becomes arg*con
diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/OrNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/OrNode.java
index 1b32d4fb..2c54e86c 100644
--- a/chapter15/src/main/java/com/seaofnodes/simple/node/OrNode.java
+++ b/chapter15/src/main/java/com/seaofnodes/simple/node/OrNode.java
@@ -38,7 +38,7 @@ public class OrNode extends Node {
// Or of 0. We do not check for (0|x) because this will already
// canonicalize to (x|0)
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && lhs._type instanceof TypeInteger )
return lhs;
// Move constants to RHS: con*arg becomes arg*con
diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/SarNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/SarNode.java
index ba99fde8..c1a34f9a 100644
--- a/chapter15/src/main/java/com/seaofnodes/simple/node/SarNode.java
+++ b/chapter15/src/main/java/com/seaofnodes/simple/node/SarNode.java
@@ -40,7 +40,7 @@ public class SarNode extends Node {
Type t2 = rhs._type;
// Sar of 0.
- if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && lhs._type instanceof TypeInteger )
return lhs;
// TODO: x >> 3 >> (y ? 1 : 2) ==> x >> (y ? 4 : 5)
diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/ShlNode.java
index 28e6dd14..97fd7d0f 100644
--- a/chapter15/src/main/java/com/seaofnodes/simple/node/ShlNode.java
+++ b/chapter15/src/main/java/com/seaofnodes/simple/node/ShlNode.java
@@ -36,7 +36,7 @@ public class ShlNode extends Node {
if( rhs._type instanceof TypeInteger shl && shl.isConstant() ) {
// Shl of 0.
- if( (shl.value()&63)==0 )
+ if( (shl.value()&63)==0 && lhs._type instanceof TypeInteger )
return lhs;
// (x + c) << i => (x << i) + (c << i)
if( lhs instanceof AddNode add && add.in(2)._type instanceof TypeInteger c && c.isConstant() ) {
diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/ShrNode.java
index 16d78ebf..f5539d2f 100644
--- a/chapter15/src/main/java/com/seaofnodes/simple/node/ShrNode.java
+++ b/chapter15/src/main/java/com/seaofnodes/simple/node/ShrNode.java
@@ -36,7 +36,7 @@ public class ShrNode extends Node {
Type t2 = rhs._type;
// Shr of 0.
- if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && lhs._type instanceof TypeInteger )
return lhs;
// TODO: x >>> 3 >>> (y ? 1 : 2) ==> x >>> (y ? 4 : 5)
diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/XorNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/XorNode.java
index 30a88dca..b46f4791 100644
--- a/chapter15/src/main/java/com/seaofnodes/simple/node/XorNode.java
+++ b/chapter15/src/main/java/com/seaofnodes/simple/node/XorNode.java
@@ -38,7 +38,7 @@ public class XorNode extends Node {
// Xor of 0. We do not check for (0^x) because this will already
// canonicalize to (x^0)
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && lhs._type instanceof TypeInteger )
return lhs;
// Move constants to RHS: con*arg becomes arg*con
diff --git a/chapter15/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java b/chapter15/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java
new file mode 100644
index 00000000..97650f25
--- /dev/null
+++ b/chapter15/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java
@@ -0,0 +1,76 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class IntegerIdentityTest {
+ private static final String[] OPS = {"And","Or","Xor","Shl","Sar","Shr","Add","Mul","Div"};
+
+ @Test public void testIdentityWaitsForIntegerInput() {
+ new Parser("return 0;").parse();
+ for( String name : OPS ) {
+ Node lhs = input(Type.BOTTOM);
+ Node rhs = input(TypeInteger.constant(identity(name)));
+ Node op = op(name,lhs,rhs);
+ assertFalse(name,lhs._type.isa(op.compute()));
+ assertNull(name,op.idealize());
+ // Model a provisional input lifting to int. The same fold can now fire.
+ lhs._type = TypeInteger.BOT;
+ assertSame(name,lhs,op.idealize());
+ assertTrue(name,lhs._type.isa(op.compute()));
+ }
+ }
+
+ @Test public void testNonIntegerIdentityCannotEraseTheOperator() {
+ new Parser("return 0;").parse();
+ for( Type type : new Type[]{TypeFloat.constant(3.5)} )
+ for( String name : OPS ) {
+ Node lhs=input(type);
+ Node op=op(name,lhs,input(TypeInteger.constant(identity(name))));
+ assertNull(name,op.idealize());
+ }
+ }
+
+ @Test public void testIntegerIdentitiesAndMaskedShiftsStillFold() {
+ new Parser("return 0;").parse();
+ for( String name : OPS )
+ for( Type type : new Type[]{TypeInteger.BOT,TypeInteger.make(2,7),TypeInteger.constant(5)} ) {
+ Node lhs=input(type);
+ long[] identities = name.equals("And") ? new long[]{-1} :
+ name.equals("Shl") || name.equals("Sar") || name.equals("Shr") ? new long[]{0,64,-64} : new long[]{identity(name)};
+ for( long identity : identities ) {
+ Node op=op(name,lhs,input(TypeInteger.constant(identity)));
+ assertSame(name,lhs,op.idealize());
+ assertTrue(name,lhs._type.isa(op.compute()));
+ }
+ }
+ }
+
+ // These tests exercise idealize directly, before GVN or constant folding.
+ private static Node input(Type type) {
+ Node n = new ConstantNode(type);
+ n._type=type;
+ return n;
+ }
+
+ private static long identity(String name) {
+ return name.equals("And") ? -1 : name.equals("Mul") || name.equals("Div") ? 1 : 0;
+ }
+
+ private static Node op(String name, Node lhs, Node rhs) {
+ return switch(name) {
+ case "And" -> new AndNode(lhs,rhs);
+ case "Or" -> new OrNode(lhs,rhs);
+ case "Xor" -> new XorNode(lhs,rhs);
+ case "Shl" -> new ShlNode(lhs,rhs);
+ case "Sar" -> new SarNode(lhs,rhs);
+ case "Shr" -> new ShrNode(lhs,rhs);
+ case "Add" -> new AddNode(lhs,rhs);
+ case "Mul" -> new MulNode(lhs,rhs);
+ case "Div" -> new DivNode(lhs,rhs);
+ default -> throw new AssertionError(name);
+ };
+ }
+}
diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/AddNode.java
index 97d2bb9b..ea5fdfdf 100644
--- a/chapter16/src/main/java/com/seaofnodes/simple/node/AddNode.java
+++ b/chapter16/src/main/java/com/seaofnodes/simple/node/AddNode.java
@@ -48,7 +48,7 @@ public class AddNode extends Node {
// Add of 0. We do not check for (0+x) because this will already
// canonicalize to (x+0)
- if( t2 instanceof TypeInteger i && i.isConstant() && i.value()==0 )
+ if( t2 instanceof TypeInteger i && i.isConstant() && i.value()==0 && lhs._type instanceof TypeInteger )
return lhs;
// Add of same to a multiply by 2
diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/AndNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/AndNode.java
index 17a4e0ca..0fb73339 100644
--- a/chapter16/src/main/java/com/seaofnodes/simple/node/AndNode.java
+++ b/chapter16/src/main/java/com/seaofnodes/simple/node/AndNode.java
@@ -44,7 +44,7 @@ public class AndNode extends Node {
// And of -1. We do not check for (-1&x) because this will already
// canonicalize to (x&-1)
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==-1 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==-1 && lhs._type instanceof TypeInteger )
return lhs;
// Move constants to RHS: con*arg becomes arg*con
diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/DivNode.java
index 76e98678..232390b2 100644
--- a/chapter16/src/main/java/com/seaofnodes/simple/node/DivNode.java
+++ b/chapter16/src/main/java/com/seaofnodes/simple/node/DivNode.java
@@ -37,7 +37,7 @@ public class DivNode extends Node {
@Override
public Node idealize() {
// Div of 1.
- if( in(2)._type == TypeInteger.TRUE )
+ if( in(2)._type == TypeInteger.TRUE && in(1)._type instanceof TypeInteger )
return in(1);
return null;
}
diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/MulNode.java
index 949af27a..728ec2c0 100644
--- a/chapter16/src/main/java/com/seaofnodes/simple/node/MulNode.java
+++ b/chapter16/src/main/java/com/seaofnodes/simple/node/MulNode.java
@@ -43,7 +43,7 @@ public class MulNode extends Node {
// Mul of 1. We do not check for (1*x) because this will already
// canonicalize to (x*1)
- if ( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==1 )
+ if ( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==1 && lhs._type instanceof TypeInteger )
return lhs;
// Move constants to RHS: con*arg becomes arg*con
diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/OrNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/OrNode.java
index b572346f..d3e982f4 100644
--- a/chapter16/src/main/java/com/seaofnodes/simple/node/OrNode.java
+++ b/chapter16/src/main/java/com/seaofnodes/simple/node/OrNode.java
@@ -41,7 +41,7 @@ public class OrNode extends Node {
// Or of 0. We do not check for (0|x) because this will already
// canonicalize to (x|0)
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && lhs._type instanceof TypeInteger )
return lhs;
// Move constants to RHS: con*arg becomes arg*con
diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/SarNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/SarNode.java
index 5dc26ad3..1d843828 100644
--- a/chapter16/src/main/java/com/seaofnodes/simple/node/SarNode.java
+++ b/chapter16/src/main/java/com/seaofnodes/simple/node/SarNode.java
@@ -45,7 +45,7 @@ public class SarNode extends Node {
Type t2 = rhs._type;
// Sar of 0.
- if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && lhs._type instanceof TypeInteger )
return lhs;
// TODO: x >> 3 >> (y ? 1 : 2) ==> x >> (y ? 4 : 5)
diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/ShlNode.java
index b67d985a..bffe5b98 100644
--- a/chapter16/src/main/java/com/seaofnodes/simple/node/ShlNode.java
+++ b/chapter16/src/main/java/com/seaofnodes/simple/node/ShlNode.java
@@ -41,7 +41,7 @@ public class ShlNode extends Node {
if( rhs._type instanceof TypeInteger shl && shl.isConstant() ) {
// Shl of 0.
- if( (shl.value()&63)==0 )
+ if( (shl.value()&63)==0 && lhs._type instanceof TypeInteger )
return lhs;
// (x + c) << i => (x << i) + (c << i)
if( lhs instanceof AddNode add && add.in(2)._type instanceof TypeInteger c && c.isConstant() && c!=TypeInteger.ZERO ) {
diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/ShrNode.java
index 22eb45a0..19929139 100644
--- a/chapter16/src/main/java/com/seaofnodes/simple/node/ShrNode.java
+++ b/chapter16/src/main/java/com/seaofnodes/simple/node/ShrNode.java
@@ -41,7 +41,7 @@ public class ShrNode extends Node {
Type t2 = rhs._type;
// Shr of 0.
- if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && lhs._type instanceof TypeInteger )
return lhs;
// TODO: x >>> 3 >>> (y ? 1 : 2) ==> x >>> (y ? 4 : 5)
diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/XorNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/XorNode.java
index 191933f9..d53a1b4e 100644
--- a/chapter16/src/main/java/com/seaofnodes/simple/node/XorNode.java
+++ b/chapter16/src/main/java/com/seaofnodes/simple/node/XorNode.java
@@ -41,7 +41,7 @@ public class XorNode extends Node {
// Xor of 0. We do not check for (0^x) because this will already
// canonicalize to (x^0)
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && lhs._type instanceof TypeInteger )
return lhs;
// Move constants to RHS: con*arg becomes arg*con
diff --git a/chapter16/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java b/chapter16/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java
new file mode 100644
index 00000000..97650f25
--- /dev/null
+++ b/chapter16/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java
@@ -0,0 +1,76 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class IntegerIdentityTest {
+ private static final String[] OPS = {"And","Or","Xor","Shl","Sar","Shr","Add","Mul","Div"};
+
+ @Test public void testIdentityWaitsForIntegerInput() {
+ new Parser("return 0;").parse();
+ for( String name : OPS ) {
+ Node lhs = input(Type.BOTTOM);
+ Node rhs = input(TypeInteger.constant(identity(name)));
+ Node op = op(name,lhs,rhs);
+ assertFalse(name,lhs._type.isa(op.compute()));
+ assertNull(name,op.idealize());
+ // Model a provisional input lifting to int. The same fold can now fire.
+ lhs._type = TypeInteger.BOT;
+ assertSame(name,lhs,op.idealize());
+ assertTrue(name,lhs._type.isa(op.compute()));
+ }
+ }
+
+ @Test public void testNonIntegerIdentityCannotEraseTheOperator() {
+ new Parser("return 0;").parse();
+ for( Type type : new Type[]{TypeFloat.constant(3.5)} )
+ for( String name : OPS ) {
+ Node lhs=input(type);
+ Node op=op(name,lhs,input(TypeInteger.constant(identity(name))));
+ assertNull(name,op.idealize());
+ }
+ }
+
+ @Test public void testIntegerIdentitiesAndMaskedShiftsStillFold() {
+ new Parser("return 0;").parse();
+ for( String name : OPS )
+ for( Type type : new Type[]{TypeInteger.BOT,TypeInteger.make(2,7),TypeInteger.constant(5)} ) {
+ Node lhs=input(type);
+ long[] identities = name.equals("And") ? new long[]{-1} :
+ name.equals("Shl") || name.equals("Sar") || name.equals("Shr") ? new long[]{0,64,-64} : new long[]{identity(name)};
+ for( long identity : identities ) {
+ Node op=op(name,lhs,input(TypeInteger.constant(identity)));
+ assertSame(name,lhs,op.idealize());
+ assertTrue(name,lhs._type.isa(op.compute()));
+ }
+ }
+ }
+
+ // These tests exercise idealize directly, before GVN or constant folding.
+ private static Node input(Type type) {
+ Node n = new ConstantNode(type);
+ n._type=type;
+ return n;
+ }
+
+ private static long identity(String name) {
+ return name.equals("And") ? -1 : name.equals("Mul") || name.equals("Div") ? 1 : 0;
+ }
+
+ private static Node op(String name, Node lhs, Node rhs) {
+ return switch(name) {
+ case "And" -> new AndNode(lhs,rhs);
+ case "Or" -> new OrNode(lhs,rhs);
+ case "Xor" -> new XorNode(lhs,rhs);
+ case "Shl" -> new ShlNode(lhs,rhs);
+ case "Sar" -> new SarNode(lhs,rhs);
+ case "Shr" -> new ShrNode(lhs,rhs);
+ case "Add" -> new AddNode(lhs,rhs);
+ case "Mul" -> new MulNode(lhs,rhs);
+ case "Div" -> new DivNode(lhs,rhs);
+ default -> throw new AssertionError(name);
+ };
+ }
+}
diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/AddNode.java
index 4007df91..766b5262 100644
--- a/chapter17/src/main/java/com/seaofnodes/simple/node/AddNode.java
+++ b/chapter17/src/main/java/com/seaofnodes/simple/node/AddNode.java
@@ -49,7 +49,7 @@ public class AddNode extends Node {
// Add of 0. We do not check for (0+x) because this will already
// canonicalize to (x+0)
- if( t2 instanceof TypeInteger i && i.isConstant() && i.value()==0 )
+ if( t2 instanceof TypeInteger i && i.isConstant() && i.value()==0 && lhs._type instanceof TypeInteger )
return lhs;
// Add of same to a multiply by 2
diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/AndNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/AndNode.java
index 17a4e0ca..0fb73339 100644
--- a/chapter17/src/main/java/com/seaofnodes/simple/node/AndNode.java
+++ b/chapter17/src/main/java/com/seaofnodes/simple/node/AndNode.java
@@ -44,7 +44,7 @@ public class AndNode extends Node {
// And of -1. We do not check for (-1&x) because this will already
// canonicalize to (x&-1)
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==-1 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==-1 && lhs._type instanceof TypeInteger )
return lhs;
// Move constants to RHS: con*arg becomes arg*con
diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/DivNode.java
index 76e98678..232390b2 100644
--- a/chapter17/src/main/java/com/seaofnodes/simple/node/DivNode.java
+++ b/chapter17/src/main/java/com/seaofnodes/simple/node/DivNode.java
@@ -37,7 +37,7 @@ public class DivNode extends Node {
@Override
public Node idealize() {
// Div of 1.
- if( in(2)._type == TypeInteger.TRUE )
+ if( in(2)._type == TypeInteger.TRUE && in(1)._type instanceof TypeInteger )
return in(1);
return null;
}
diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/MulNode.java
index 949af27a..728ec2c0 100644
--- a/chapter17/src/main/java/com/seaofnodes/simple/node/MulNode.java
+++ b/chapter17/src/main/java/com/seaofnodes/simple/node/MulNode.java
@@ -43,7 +43,7 @@ public class MulNode extends Node {
// Mul of 1. We do not check for (1*x) because this will already
// canonicalize to (x*1)
- if ( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==1 )
+ if ( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==1 && lhs._type instanceof TypeInteger )
return lhs;
// Move constants to RHS: con*arg becomes arg*con
diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/OrNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/OrNode.java
index b572346f..d3e982f4 100644
--- a/chapter17/src/main/java/com/seaofnodes/simple/node/OrNode.java
+++ b/chapter17/src/main/java/com/seaofnodes/simple/node/OrNode.java
@@ -41,7 +41,7 @@ public class OrNode extends Node {
// Or of 0. We do not check for (0|x) because this will already
// canonicalize to (x|0)
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && lhs._type instanceof TypeInteger )
return lhs;
// Move constants to RHS: con*arg becomes arg*con
diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/SarNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/SarNode.java
index 5dc26ad3..1d843828 100644
--- a/chapter17/src/main/java/com/seaofnodes/simple/node/SarNode.java
+++ b/chapter17/src/main/java/com/seaofnodes/simple/node/SarNode.java
@@ -45,7 +45,7 @@ public class SarNode extends Node {
Type t2 = rhs._type;
// Sar of 0.
- if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && lhs._type instanceof TypeInteger )
return lhs;
// TODO: x >> 3 >> (y ? 1 : 2) ==> x >> (y ? 4 : 5)
diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/ShlNode.java
index 6fd951e4..b95abcdd 100644
--- a/chapter17/src/main/java/com/seaofnodes/simple/node/ShlNode.java
+++ b/chapter17/src/main/java/com/seaofnodes/simple/node/ShlNode.java
@@ -41,7 +41,7 @@ public class ShlNode extends Node {
if( rhs._type instanceof TypeInteger shl && shl.isConstant() ) {
// Shl of 0.
- if( (shl.value()&63)==0 )
+ if( (shl.value()&63)==0 && lhs._type instanceof TypeInteger )
return lhs;
// (x + c) << i => (x << i) + (c << i)
if( lhs instanceof AddNode add && add.addDep(this).in(2)._type instanceof TypeInteger c && c.isConstant() && c!=TypeInteger.ZERO ) {
diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/ShrNode.java
index 779193de..4095a0de 100644
--- a/chapter17/src/main/java/com/seaofnodes/simple/node/ShrNode.java
+++ b/chapter17/src/main/java/com/seaofnodes/simple/node/ShrNode.java
@@ -41,7 +41,7 @@ public class ShrNode extends Node {
Type t2 = rhs._type;
// Shr of 0.
- if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && lhs._type instanceof TypeInteger )
return lhs;
// TODO: x >>> 3 >>> (y ? 1 : 2) ==> x >>> (y ? 4 : 5)
diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/XorNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/XorNode.java
index 191933f9..d53a1b4e 100644
--- a/chapter17/src/main/java/com/seaofnodes/simple/node/XorNode.java
+++ b/chapter17/src/main/java/com/seaofnodes/simple/node/XorNode.java
@@ -41,7 +41,7 @@ public class XorNode extends Node {
// Xor of 0. We do not check for (0^x) because this will already
// canonicalize to (x^0)
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && lhs._type instanceof TypeInteger )
return lhs;
// Move constants to RHS: con*arg becomes arg*con
diff --git a/chapter17/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java b/chapter17/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java
new file mode 100644
index 00000000..97650f25
--- /dev/null
+++ b/chapter17/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java
@@ -0,0 +1,76 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class IntegerIdentityTest {
+ private static final String[] OPS = {"And","Or","Xor","Shl","Sar","Shr","Add","Mul","Div"};
+
+ @Test public void testIdentityWaitsForIntegerInput() {
+ new Parser("return 0;").parse();
+ for( String name : OPS ) {
+ Node lhs = input(Type.BOTTOM);
+ Node rhs = input(TypeInteger.constant(identity(name)));
+ Node op = op(name,lhs,rhs);
+ assertFalse(name,lhs._type.isa(op.compute()));
+ assertNull(name,op.idealize());
+ // Model a provisional input lifting to int. The same fold can now fire.
+ lhs._type = TypeInteger.BOT;
+ assertSame(name,lhs,op.idealize());
+ assertTrue(name,lhs._type.isa(op.compute()));
+ }
+ }
+
+ @Test public void testNonIntegerIdentityCannotEraseTheOperator() {
+ new Parser("return 0;").parse();
+ for( Type type : new Type[]{TypeFloat.constant(3.5)} )
+ for( String name : OPS ) {
+ Node lhs=input(type);
+ Node op=op(name,lhs,input(TypeInteger.constant(identity(name))));
+ assertNull(name,op.idealize());
+ }
+ }
+
+ @Test public void testIntegerIdentitiesAndMaskedShiftsStillFold() {
+ new Parser("return 0;").parse();
+ for( String name : OPS )
+ for( Type type : new Type[]{TypeInteger.BOT,TypeInteger.make(2,7),TypeInteger.constant(5)} ) {
+ Node lhs=input(type);
+ long[] identities = name.equals("And") ? new long[]{-1} :
+ name.equals("Shl") || name.equals("Sar") || name.equals("Shr") ? new long[]{0,64,-64} : new long[]{identity(name)};
+ for( long identity : identities ) {
+ Node op=op(name,lhs,input(TypeInteger.constant(identity)));
+ assertSame(name,lhs,op.idealize());
+ assertTrue(name,lhs._type.isa(op.compute()));
+ }
+ }
+ }
+
+ // These tests exercise idealize directly, before GVN or constant folding.
+ private static Node input(Type type) {
+ Node n = new ConstantNode(type);
+ n._type=type;
+ return n;
+ }
+
+ private static long identity(String name) {
+ return name.equals("And") ? -1 : name.equals("Mul") || name.equals("Div") ? 1 : 0;
+ }
+
+ private static Node op(String name, Node lhs, Node rhs) {
+ return switch(name) {
+ case "And" -> new AndNode(lhs,rhs);
+ case "Or" -> new OrNode(lhs,rhs);
+ case "Xor" -> new XorNode(lhs,rhs);
+ case "Shl" -> new ShlNode(lhs,rhs);
+ case "Sar" -> new SarNode(lhs,rhs);
+ case "Shr" -> new ShrNode(lhs,rhs);
+ case "Add" -> new AddNode(lhs,rhs);
+ case "Mul" -> new MulNode(lhs,rhs);
+ case "Div" -> new DivNode(lhs,rhs);
+ default -> throw new AssertionError(name);
+ };
+ }
+}
diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/AddNode.java
index 21f8ad87..5bcdca2e 100644
--- a/chapter18/src/main/java/com/seaofnodes/simple/node/AddNode.java
+++ b/chapter18/src/main/java/com/seaofnodes/simple/node/AddNode.java
@@ -49,7 +49,7 @@ public class AddNode extends Node {
// Add of 0. We do not check for (0+x) because this will already
// canonicalize to (x+0)
- if( t2 == TypeInteger.ZERO )
+ if( t2 == TypeInteger.ZERO && lhs._type instanceof TypeInteger )
return lhs;
// Add of same to a multiply by 2
diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/AndNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/AndNode.java
index 36e3a949..461a8f9b 100644
--- a/chapter18/src/main/java/com/seaofnodes/simple/node/AndNode.java
+++ b/chapter18/src/main/java/com/seaofnodes/simple/node/AndNode.java
@@ -39,7 +39,7 @@ public class AndNode extends LogicalNode {
// And of -1. We do not check for (-1&x) because this will already
// canonicalize to (x&-1)
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==-1 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==-1 && lhs._type instanceof TypeInteger )
return lhs;
// Move constants to RHS: con*arg becomes arg*con
diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/DivNode.java
index 0d5ed297..82cafdea 100644
--- a/chapter18/src/main/java/com/seaofnodes/simple/node/DivNode.java
+++ b/chapter18/src/main/java/com/seaofnodes/simple/node/DivNode.java
@@ -36,7 +36,7 @@ public class DivNode extends Node {
@Override
public Node idealize() {
// Div of 1.
- if( in(2)._type == TypeInteger.TRUE )
+ if( in(2)._type == TypeInteger.TRUE && in(1)._type instanceof TypeInteger )
return in(1);
return null;
}
diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/MinusNode.java
index dd2cbd26..dafdb6b4 100644
--- a/chapter18/src/main/java/com/seaofnodes/simple/node/MinusNode.java
+++ b/chapter18/src/main/java/com/seaofnodes/simple/node/MinusNode.java
@@ -31,7 +31,7 @@ public class MinusNode extends Node {
@Override
public Node idealize() {
// -(-x) is x
- if( in(1) instanceof MinusNode minus )
+ if( in(1) instanceof MinusNode minus && minus.in(1)._type instanceof TypeInteger )
return minus.in(1);
return null;
diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/MulNode.java
index a1544d3d..cc670f13 100644
--- a/chapter18/src/main/java/com/seaofnodes/simple/node/MulNode.java
+++ b/chapter18/src/main/java/com/seaofnodes/simple/node/MulNode.java
@@ -43,7 +43,7 @@ public class MulNode extends Node {
// Mul of 1. We do not check for (1*x) because this will already
// canonicalize to (x*1)
- if ( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==1 )
+ if ( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==1 && lhs._type instanceof TypeInteger )
return lhs;
// Move constants to RHS: con*arg becomes arg*con
diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/OrNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/OrNode.java
index 9b03dc5d..dfa32378 100644
--- a/chapter18/src/main/java/com/seaofnodes/simple/node/OrNode.java
+++ b/chapter18/src/main/java/com/seaofnodes/simple/node/OrNode.java
@@ -36,7 +36,7 @@ public class OrNode extends LogicalNode {
// Or of 0. We do not check for (0|x) because this will already
// canonicalize to (x|0)
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && lhs._type instanceof TypeInteger )
return lhs;
// Move constants to RHS: con*arg becomes arg*con
diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/SarNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/SarNode.java
index 4a71ec06..cb48f1c8 100644
--- a/chapter18/src/main/java/com/seaofnodes/simple/node/SarNode.java
+++ b/chapter18/src/main/java/com/seaofnodes/simple/node/SarNode.java
@@ -39,7 +39,7 @@ public class SarNode extends LogicalNode {
Type t2 = rhs._type;
// Sar of 0.
- if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && lhs._type instanceof TypeInteger )
return lhs;
// TODO: x >> 3 >> (y ? 1 : 2) ==> x >> (y ? 4 : 5)
diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/ShlNode.java
index 21dbd503..ee2e8e54 100644
--- a/chapter18/src/main/java/com/seaofnodes/simple/node/ShlNode.java
+++ b/chapter18/src/main/java/com/seaofnodes/simple/node/ShlNode.java
@@ -35,7 +35,7 @@ public class ShlNode extends LogicalNode {
if( rhs._type instanceof TypeInteger shl && shl.isConstant() ) {
// Shl of 0.
- if( (shl.value()&63)==0 )
+ if( (shl.value()&63)==0 && lhs._type instanceof TypeInteger )
return lhs;
// (x + c) << i => (x << i) + (c << i)
if( lhs instanceof AddNode add && add.addDep(this).in(2)._type instanceof TypeInteger c && c.isConstant() ) {
diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/ShrNode.java
index 789fb8a8..97673d9a 100644
--- a/chapter18/src/main/java/com/seaofnodes/simple/node/ShrNode.java
+++ b/chapter18/src/main/java/com/seaofnodes/simple/node/ShrNode.java
@@ -35,7 +35,7 @@ public class ShrNode extends LogicalNode {
Type t2 = rhs._type;
// Shr of 0.
- if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && lhs._type instanceof TypeInteger )
return lhs;
// TODO: x >>> 3 >>> (y ? 1 : 2) ==> x >>> (y ? 4 : 5)
diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/XorNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/XorNode.java
index 37d7147d..03d7bf3d 100644
--- a/chapter18/src/main/java/com/seaofnodes/simple/node/XorNode.java
+++ b/chapter18/src/main/java/com/seaofnodes/simple/node/XorNode.java
@@ -35,7 +35,7 @@ public class XorNode extends LogicalNode {
// Xor of 0. We do not check for (0^x) because this will already
// canonicalize to (x^0)
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && lhs._type instanceof TypeInteger )
return lhs;
// Move constants to RHS: con*arg becomes arg*con
diff --git a/chapter18/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java b/chapter18/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java
new file mode 100644
index 00000000..bd9ef779
--- /dev/null
+++ b/chapter18/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java
@@ -0,0 +1,92 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class IntegerIdentityTest {
+ private static final String[] OPS = {"And","Or","Xor","Shl","Sar","Shr","Add","Mul","Div"};
+
+ @Test public void testIdentityWaitsForIntegerInput() {
+ new CodeGen("return 0;").parse();
+ for( String name : OPS ) {
+ Node lhs = input(Type.BOTTOM);
+ Node rhs = input(TypeInteger.constant(identity(name)));
+ Node op = op(name,lhs,rhs);
+ assertFalse(name,lhs._type.isa(op.compute()));
+ assertNull(name,op.idealize());
+ // Model a provisional input lifting to int. The same fold can now fire.
+ lhs._type = TypeInteger.BOT;
+ assertSame(name,lhs,op.idealize());
+ assertTrue(name,lhs._type.isa(op.compute()));
+ }
+ }
+
+ @Test public void testNonIntegerIdentityCannotEraseTheOperator() {
+ new CodeGen("return 0;").parse();
+ for( Type type : new Type[]{TypeFloat.constant(3.5)} )
+ for( String name : OPS ) {
+ Node lhs=input(type);
+ Node op=op(name,lhs,input(TypeInteger.constant(identity(name))));
+ assertNull(name,op.idealize());
+ }
+ }
+
+ @Test public void testIntegerIdentitiesAndMaskedShiftsStillFold() {
+ new CodeGen("return 0;").parse();
+ for( String name : OPS )
+ for( Type type : new Type[]{TypeInteger.BOT,TypeInteger.make(2,7),TypeInteger.constant(5)} ) {
+ Node lhs=input(type);
+ long[] identities = name.equals("And") ? new long[]{-1} :
+ name.equals("Shl") || name.equals("Sar") || name.equals("Shr") ? new long[]{0,64,-64} : new long[]{identity(name)};
+ for( long identity : identities ) {
+ Node op=op(name,lhs,input(TypeInteger.constant(identity)));
+ assertSame(name,lhs,op.idealize());
+ assertTrue(name,lhs._type.isa(op.compute()));
+ }
+ }
+ }
+
+ @Test public void testDoubleNegationWaitsForIntegerInput() {
+ new CodeGen("return 0;").parse();
+ for( Type type : new Type[]{Type.BOTTOM,TypeFloat.constant(3.5)} ) {
+ Node lhs=input(type);
+ Node inner=new MinusNode(lhs);
+ inner._type=inner.compute();
+ Node outer=new MinusNode(inner);
+ assertFalse(lhs._type.isa(outer.compute()));
+ assertNull(outer.idealize());
+ lhs._type=TypeInteger.make(2,7);
+ inner._type=inner.compute();
+ assertSame(lhs,outer.idealize());
+ assertTrue(lhs._type.isa(outer.compute()));
+ }
+ }
+
+ // These tests exercise idealize directly, before GVN or constant folding.
+ private static Node input(Type type) {
+ Node n = new ConstantNode(type);
+ n._type=type;
+ return n;
+ }
+
+ private static long identity(String name) {
+ return name.equals("And") ? -1 : name.equals("Mul") || name.equals("Div") ? 1 : 0;
+ }
+
+ private static Node op(String name, Node lhs, Node rhs) {
+ return switch(name) {
+ case "And" -> new AndNode(null,lhs,rhs);
+ case "Or" -> new OrNode(null,lhs,rhs);
+ case "Xor" -> new XorNode(null,lhs,rhs);
+ case "Shl" -> new ShlNode(null,lhs,rhs);
+ case "Sar" -> new SarNode(null,lhs,rhs);
+ case "Shr" -> new ShrNode(null,lhs,rhs);
+ case "Add" -> new AddNode(lhs,rhs);
+ case "Mul" -> new MulNode(lhs,rhs);
+ case "Div" -> new DivNode(lhs,rhs);
+ default -> throw new AssertionError(name);
+ };
+ }
+}
diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/AddNode.java
index 5a42e2e8..cdc09027 100644
--- a/chapter19/src/main/java/com/seaofnodes/simple/node/AddNode.java
+++ b/chapter19/src/main/java/com/seaofnodes/simple/node/AddNode.java
@@ -49,7 +49,7 @@ public class AddNode extends Node {
// Add of 0. We do not check for (0+x) because this will already
// canonicalize to (x+0)
- if( t2 == TypeInteger.ZERO )
+ if( t2 == TypeInteger.ZERO && lhs._type instanceof TypeInteger )
return lhs;
// Add of same to a multiply by 2
diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/AndNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/AndNode.java
index 36e3a949..461a8f9b 100644
--- a/chapter19/src/main/java/com/seaofnodes/simple/node/AndNode.java
+++ b/chapter19/src/main/java/com/seaofnodes/simple/node/AndNode.java
@@ -39,7 +39,7 @@ public class AndNode extends LogicalNode {
// And of -1. We do not check for (-1&x) because this will already
// canonicalize to (x&-1)
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==-1 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==-1 && lhs._type instanceof TypeInteger )
return lhs;
// Move constants to RHS: con*arg becomes arg*con
diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/DivNode.java
index 0d5ed297..82cafdea 100644
--- a/chapter19/src/main/java/com/seaofnodes/simple/node/DivNode.java
+++ b/chapter19/src/main/java/com/seaofnodes/simple/node/DivNode.java
@@ -36,7 +36,7 @@ public class DivNode extends Node {
@Override
public Node idealize() {
// Div of 1.
- if( in(2)._type == TypeInteger.TRUE )
+ if( in(2)._type == TypeInteger.TRUE && in(1)._type instanceof TypeInteger )
return in(1);
return null;
}
diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/MinusNode.java
index dd2cbd26..dafdb6b4 100644
--- a/chapter19/src/main/java/com/seaofnodes/simple/node/MinusNode.java
+++ b/chapter19/src/main/java/com/seaofnodes/simple/node/MinusNode.java
@@ -31,7 +31,7 @@ public class MinusNode extends Node {
@Override
public Node idealize() {
// -(-x) is x
- if( in(1) instanceof MinusNode minus )
+ if( in(1) instanceof MinusNode minus && minus.in(1)._type instanceof TypeInteger )
return minus.in(1);
return null;
diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/MulNode.java
index 53472994..7593d3a0 100644
--- a/chapter19/src/main/java/com/seaofnodes/simple/node/MulNode.java
+++ b/chapter19/src/main/java/com/seaofnodes/simple/node/MulNode.java
@@ -50,7 +50,7 @@ public class MulNode extends Node {
// Mul of 1. We do not check for (1*x) because this will already
// canonicalize to (x*1)
long c = i.value();
- if( c==1 ) return lhs;
+ if( c==1 ) return lhs._type instanceof TypeInteger ? lhs : null;
if( c==0 ) return Parser.ZERO;
// Mul by a power of 2, +/-1. Bit patterns more complex than this
// are unlikely to win on an X86 vs the normal "imul", and so
diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/OrNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/OrNode.java
index 9b03dc5d..dfa32378 100644
--- a/chapter19/src/main/java/com/seaofnodes/simple/node/OrNode.java
+++ b/chapter19/src/main/java/com/seaofnodes/simple/node/OrNode.java
@@ -36,7 +36,7 @@ public class OrNode extends LogicalNode {
// Or of 0. We do not check for (0|x) because this will already
// canonicalize to (x|0)
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && lhs._type instanceof TypeInteger )
return lhs;
// Move constants to RHS: con*arg becomes arg*con
diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/SarNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/SarNode.java
index 4a71ec06..cb48f1c8 100644
--- a/chapter19/src/main/java/com/seaofnodes/simple/node/SarNode.java
+++ b/chapter19/src/main/java/com/seaofnodes/simple/node/SarNode.java
@@ -39,7 +39,7 @@ public class SarNode extends LogicalNode {
Type t2 = rhs._type;
// Sar of 0.
- if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && lhs._type instanceof TypeInteger )
return lhs;
// TODO: x >> 3 >> (y ? 1 : 2) ==> x >> (y ? 4 : 5)
diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/ShlNode.java
index 21dbd503..ee2e8e54 100644
--- a/chapter19/src/main/java/com/seaofnodes/simple/node/ShlNode.java
+++ b/chapter19/src/main/java/com/seaofnodes/simple/node/ShlNode.java
@@ -35,7 +35,7 @@ public class ShlNode extends LogicalNode {
if( rhs._type instanceof TypeInteger shl && shl.isConstant() ) {
// Shl of 0.
- if( (shl.value()&63)==0 )
+ if( (shl.value()&63)==0 && lhs._type instanceof TypeInteger )
return lhs;
// (x + c) << i => (x << i) + (c << i)
if( lhs instanceof AddNode add && add.addDep(this).in(2)._type instanceof TypeInteger c && c.isConstant() ) {
diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/ShrNode.java
index 789fb8a8..97673d9a 100644
--- a/chapter19/src/main/java/com/seaofnodes/simple/node/ShrNode.java
+++ b/chapter19/src/main/java/com/seaofnodes/simple/node/ShrNode.java
@@ -35,7 +35,7 @@ public class ShrNode extends LogicalNode {
Type t2 = rhs._type;
// Shr of 0.
- if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && lhs._type instanceof TypeInteger )
return lhs;
// TODO: x >>> 3 >>> (y ? 1 : 2) ==> x >>> (y ? 4 : 5)
diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/XorNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/XorNode.java
index 37d7147d..03d7bf3d 100644
--- a/chapter19/src/main/java/com/seaofnodes/simple/node/XorNode.java
+++ b/chapter19/src/main/java/com/seaofnodes/simple/node/XorNode.java
@@ -35,7 +35,7 @@ public class XorNode extends LogicalNode {
// Xor of 0. We do not check for (0^x) because this will already
// canonicalize to (x^0)
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && lhs._type instanceof TypeInteger )
return lhs;
// Move constants to RHS: con*arg becomes arg*con
diff --git a/chapter19/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java b/chapter19/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java
new file mode 100644
index 00000000..bd9ef779
--- /dev/null
+++ b/chapter19/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java
@@ -0,0 +1,92 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class IntegerIdentityTest {
+ private static final String[] OPS = {"And","Or","Xor","Shl","Sar","Shr","Add","Mul","Div"};
+
+ @Test public void testIdentityWaitsForIntegerInput() {
+ new CodeGen("return 0;").parse();
+ for( String name : OPS ) {
+ Node lhs = input(Type.BOTTOM);
+ Node rhs = input(TypeInteger.constant(identity(name)));
+ Node op = op(name,lhs,rhs);
+ assertFalse(name,lhs._type.isa(op.compute()));
+ assertNull(name,op.idealize());
+ // Model a provisional input lifting to int. The same fold can now fire.
+ lhs._type = TypeInteger.BOT;
+ assertSame(name,lhs,op.idealize());
+ assertTrue(name,lhs._type.isa(op.compute()));
+ }
+ }
+
+ @Test public void testNonIntegerIdentityCannotEraseTheOperator() {
+ new CodeGen("return 0;").parse();
+ for( Type type : new Type[]{TypeFloat.constant(3.5)} )
+ for( String name : OPS ) {
+ Node lhs=input(type);
+ Node op=op(name,lhs,input(TypeInteger.constant(identity(name))));
+ assertNull(name,op.idealize());
+ }
+ }
+
+ @Test public void testIntegerIdentitiesAndMaskedShiftsStillFold() {
+ new CodeGen("return 0;").parse();
+ for( String name : OPS )
+ for( Type type : new Type[]{TypeInteger.BOT,TypeInteger.make(2,7),TypeInteger.constant(5)} ) {
+ Node lhs=input(type);
+ long[] identities = name.equals("And") ? new long[]{-1} :
+ name.equals("Shl") || name.equals("Sar") || name.equals("Shr") ? new long[]{0,64,-64} : new long[]{identity(name)};
+ for( long identity : identities ) {
+ Node op=op(name,lhs,input(TypeInteger.constant(identity)));
+ assertSame(name,lhs,op.idealize());
+ assertTrue(name,lhs._type.isa(op.compute()));
+ }
+ }
+ }
+
+ @Test public void testDoubleNegationWaitsForIntegerInput() {
+ new CodeGen("return 0;").parse();
+ for( Type type : new Type[]{Type.BOTTOM,TypeFloat.constant(3.5)} ) {
+ Node lhs=input(type);
+ Node inner=new MinusNode(lhs);
+ inner._type=inner.compute();
+ Node outer=new MinusNode(inner);
+ assertFalse(lhs._type.isa(outer.compute()));
+ assertNull(outer.idealize());
+ lhs._type=TypeInteger.make(2,7);
+ inner._type=inner.compute();
+ assertSame(lhs,outer.idealize());
+ assertTrue(lhs._type.isa(outer.compute()));
+ }
+ }
+
+ // These tests exercise idealize directly, before GVN or constant folding.
+ private static Node input(Type type) {
+ Node n = new ConstantNode(type);
+ n._type=type;
+ return n;
+ }
+
+ private static long identity(String name) {
+ return name.equals("And") ? -1 : name.equals("Mul") || name.equals("Div") ? 1 : 0;
+ }
+
+ private static Node op(String name, Node lhs, Node rhs) {
+ return switch(name) {
+ case "And" -> new AndNode(null,lhs,rhs);
+ case "Or" -> new OrNode(null,lhs,rhs);
+ case "Xor" -> new XorNode(null,lhs,rhs);
+ case "Shl" -> new ShlNode(null,lhs,rhs);
+ case "Sar" -> new SarNode(null,lhs,rhs);
+ case "Shr" -> new ShrNode(null,lhs,rhs);
+ case "Add" -> new AddNode(lhs,rhs);
+ case "Mul" -> new MulNode(lhs,rhs);
+ case "Div" -> new DivNode(lhs,rhs);
+ default -> throw new AssertionError(name);
+ };
+ }
+}
diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/AddNode.java
index bc65e79a..61ea528c 100644
--- a/chapter20/src/main/java/com/seaofnodes/simple/node/AddNode.java
+++ b/chapter20/src/main/java/com/seaofnodes/simple/node/AddNode.java
@@ -53,7 +53,7 @@ public class AddNode extends Node {
// Add of 0. We do not check for (0+x) because this will already
// canonicalize to (x+0)
- if( t2 == TypeInteger.ZERO )
+ if( t2 == TypeInteger.ZERO && lhs._type instanceof TypeInteger )
return lhs;
// Add of same to a multiply by 2
diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/DivNode.java
index 0d5ed297..82cafdea 100644
--- a/chapter20/src/main/java/com/seaofnodes/simple/node/DivNode.java
+++ b/chapter20/src/main/java/com/seaofnodes/simple/node/DivNode.java
@@ -36,7 +36,7 @@ public class DivNode extends Node {
@Override
public Node idealize() {
// Div of 1.
- if( in(2)._type == TypeInteger.TRUE )
+ if( in(2)._type == TypeInteger.TRUE && in(1)._type instanceof TypeInteger )
return in(1);
return null;
}
diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/MinusNode.java
index dd2cbd26..dafdb6b4 100644
--- a/chapter20/src/main/java/com/seaofnodes/simple/node/MinusNode.java
+++ b/chapter20/src/main/java/com/seaofnodes/simple/node/MinusNode.java
@@ -31,7 +31,7 @@ public class MinusNode extends Node {
@Override
public Node idealize() {
// -(-x) is x
- if( in(1) instanceof MinusNode minus )
+ if( in(1) instanceof MinusNode minus && minus.in(1)._type instanceof TypeInteger )
return minus.in(1);
return null;
diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/MulNode.java
index 80f8c319..90e6e173 100644
--- a/chapter20/src/main/java/com/seaofnodes/simple/node/MulNode.java
+++ b/chapter20/src/main/java/com/seaofnodes/simple/node/MulNode.java
@@ -50,7 +50,7 @@ public class MulNode extends Node {
// Mul of 1. We do not check for (1*x) because this will already
// canonicalize to (x*1)
long c = i.value();
- if( c==1 ) return lhs;
+ if( c==1 ) return lhs._type instanceof TypeInteger ? lhs : null;
if( c==0 ) return Parser.ZERO;
// Mul by a power of 2, +/-1. Bit patterns more complex than this
// are unlikely to win on an X86 vs the normal "imul", and so
diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/OrNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/OrNode.java
index 9b03dc5d..dfa32378 100644
--- a/chapter20/src/main/java/com/seaofnodes/simple/node/OrNode.java
+++ b/chapter20/src/main/java/com/seaofnodes/simple/node/OrNode.java
@@ -36,7 +36,7 @@ public class OrNode extends LogicalNode {
// Or of 0. We do not check for (0|x) because this will already
// canonicalize to (x|0)
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && lhs._type instanceof TypeInteger )
return lhs;
// Move constants to RHS: con*arg becomes arg*con
diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/SarNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/SarNode.java
index 4a71ec06..cb48f1c8 100644
--- a/chapter20/src/main/java/com/seaofnodes/simple/node/SarNode.java
+++ b/chapter20/src/main/java/com/seaofnodes/simple/node/SarNode.java
@@ -39,7 +39,7 @@ public class SarNode extends LogicalNode {
Type t2 = rhs._type;
// Sar of 0.
- if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && lhs._type instanceof TypeInteger )
return lhs;
// TODO: x >> 3 >> (y ? 1 : 2) ==> x >> (y ? 4 : 5)
diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/ShlNode.java
index 21dbd503..ee2e8e54 100644
--- a/chapter20/src/main/java/com/seaofnodes/simple/node/ShlNode.java
+++ b/chapter20/src/main/java/com/seaofnodes/simple/node/ShlNode.java
@@ -35,7 +35,7 @@ public class ShlNode extends LogicalNode {
if( rhs._type instanceof TypeInteger shl && shl.isConstant() ) {
// Shl of 0.
- if( (shl.value()&63)==0 )
+ if( (shl.value()&63)==0 && lhs._type instanceof TypeInteger )
return lhs;
// (x + c) << i => (x << i) + (c << i)
if( lhs instanceof AddNode add && add.addDep(this).in(2)._type instanceof TypeInteger c && c.isConstant() ) {
diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/ShrNode.java
index 789fb8a8..97673d9a 100644
--- a/chapter20/src/main/java/com/seaofnodes/simple/node/ShrNode.java
+++ b/chapter20/src/main/java/com/seaofnodes/simple/node/ShrNode.java
@@ -35,7 +35,7 @@ public class ShrNode extends LogicalNode {
Type t2 = rhs._type;
// Shr of 0.
- if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && lhs._type instanceof TypeInteger )
return lhs;
// TODO: x >>> 3 >>> (y ? 1 : 2) ==> x >>> (y ? 4 : 5)
diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/XorNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/XorNode.java
index 37d7147d..03d7bf3d 100644
--- a/chapter20/src/main/java/com/seaofnodes/simple/node/XorNode.java
+++ b/chapter20/src/main/java/com/seaofnodes/simple/node/XorNode.java
@@ -35,7 +35,7 @@ public class XorNode extends LogicalNode {
// Xor of 0. We do not check for (0^x) because this will already
// canonicalize to (x^0)
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && lhs._type instanceof TypeInteger )
return lhs;
// Move constants to RHS: con*arg becomes arg*con
diff --git a/chapter20/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java b/chapter20/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java
new file mode 100644
index 00000000..b4893ec2
--- /dev/null
+++ b/chapter20/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java
@@ -0,0 +1,93 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class IntegerIdentityTest {
+ private static final String[] OPS = {"And","Or","Xor","Shl","Sar","Shr","Add","Mul","Div"};
+
+ @Test public void testIdentityWaitsForIntegerInput() {
+ new CodeGen("return 0;").parse();
+ for( String name : OPS ) {
+ Node lhs = input(Type.BOTTOM);
+ Node rhs = input(TypeInteger.constant(identity(name)));
+ Node op = op(name,lhs,rhs);
+ assertFalse(name,lhs._type.isa(op.compute()));
+ assertNull(name,op.idealize());
+ // Model a provisional input lifting to int. The same fold can now fire.
+ lhs._type = TypeInteger.BOT;
+ assertSame(name,lhs,op.idealize());
+ assertTrue(name,lhs._type.isa(op.compute()));
+ }
+ }
+
+ @Test public void testNonIntegerIdentityCannotEraseTheOperator() {
+ new CodeGen("return 0;").parse();
+ for( Type type : new Type[]{TypeFloat.constant(3.5)} )
+ for( String name : OPS ) {
+ Node lhs=input(type);
+ Node op=op(name,lhs,input(TypeInteger.constant(identity(name))));
+ assertNull(name,op.idealize());
+ }
+ }
+
+ @Test public void testIntegerIdentitiesAndMaskedShiftsStillFold() {
+ new CodeGen("return 0;").parse();
+ for( String name : OPS )
+ for( Type type : new Type[]{TypeInteger.BOT,TypeInteger.make(2,7),TypeInteger.constant(5)} ) {
+ Node lhs=input(type);
+ long[] identities = name.equals("And") ? new long[]{-1} :
+ name.equals("Shl") || name.equals("Sar") || name.equals("Shr") ? new long[]{0,64,-64} : new long[]{identity(name)};
+ for( long identity : identities ) {
+ Node op=op(name,lhs,input(TypeInteger.constant(identity)));
+ assertSame(name,lhs,op.idealize());
+ assertTrue(name,lhs._type.isa(op.compute()));
+ }
+ }
+ }
+
+ @Test public void testDoubleNegationWaitsForIntegerInput() {
+ new CodeGen("return 0;").parse();
+ for( Type type : new Type[]{Type.BOTTOM,TypeFloat.constant(3.5)} ) {
+ Node lhs=input(type);
+ Node inner=new MinusNode(lhs);
+ inner._type=inner.compute();
+ Node outer=new MinusNode(inner);
+ assertFalse(lhs._type.isa(outer.compute()));
+ assertNull(outer.idealize());
+ lhs._type=TypeInteger.make(2,7);
+ inner._type=inner.compute();
+ assertSame(lhs,outer.idealize());
+ assertTrue(lhs._type.isa(outer.compute()));
+ }
+ }
+
+ // These tests exercise idealize directly, before GVN or constant folding.
+ private static Node input(Type type) {
+ Node n = new ConstantNode(type);
+ n._type=type;
+ return n;
+ }
+
+ private static long identity(String name) {
+ return name.equals("And") ? -1 : name.equals("Mul") || name.equals("Div") ? 1 : 0;
+ }
+
+ private static Node op(String name, Node lhs, Node rhs) {
+ return switch(name) {
+ case "And" -> new AndNode(null,lhs,rhs);
+ case "Or" -> new OrNode(null,lhs,rhs);
+ case "Xor" -> new XorNode(null,lhs,rhs);
+ case "Shl" -> new ShlNode(null,lhs,rhs);
+ case "Sar" -> new SarNode(null,lhs,rhs);
+ case "Shr" -> new ShrNode(null,lhs,rhs);
+ case "Add" -> new AddNode(lhs,rhs);
+ case "Mul" -> new MulNode(lhs,rhs);
+ case "Div" -> new DivNode(lhs,rhs);
+ default -> throw new AssertionError(name);
+ };
+ }
+}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/AddNode.java
index dd128e76..2a993e66 100644
--- a/chapter21/src/main/java/com/seaofnodes/simple/node/AddNode.java
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/AddNode.java
@@ -53,7 +53,7 @@ public class AddNode extends Node {
// Add of 0. We do not check for (0+x) because this will already
// canonicalize to (x+0)
- if( t2 == TypeInteger.ZERO )
+ if( t2 == TypeInteger.ZERO && lhs._type instanceof TypeInteger )
return lhs;
// Add of same to a multiply by 2
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/DivNode.java
index 41518311..64b4d378 100644
--- a/chapter21/src/main/java/com/seaofnodes/simple/node/DivNode.java
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/DivNode.java
@@ -36,7 +36,7 @@ public class DivNode extends Node {
@Override
public Node idealize() {
// Div of 1.
- if( in(2)._type == TypeInteger.TRUE )
+ if( in(2)._type == TypeInteger.TRUE && in(1)._type instanceof TypeInteger )
return in(1);
return null;
}
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/MinusNode.java
index e06e0c69..0d087d22 100644
--- a/chapter21/src/main/java/com/seaofnodes/simple/node/MinusNode.java
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/MinusNode.java
@@ -31,7 +31,7 @@ public class MinusNode extends Node {
@Override
public Node idealize() {
// -(-x) is x
- if( in(1) instanceof MinusNode minus )
+ if( in(1) instanceof MinusNode minus && minus.in(1)._type instanceof TypeInteger )
return minus.in(1);
return null;
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/MulNode.java
index 384dbace..1ff4054d 100644
--- a/chapter21/src/main/java/com/seaofnodes/simple/node/MulNode.java
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/MulNode.java
@@ -50,7 +50,7 @@ public class MulNode extends Node {
// Mul of 1. We do not check for (1*x) because this will already
// canonicalize to (x*1)
long c = i.value();
- if( c==1 ) return lhs;
+ if( c==1 ) return lhs._type instanceof TypeInteger ? lhs : null;
if( c==0 ) return Parser.ZERO;
// Mul by a power of 2, +/-1. Bit patterns more complex than this
// are unlikely to win on an X86 vs the normal "imul", and so
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/OrNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/OrNode.java
index 9b03dc5d..dfa32378 100644
--- a/chapter21/src/main/java/com/seaofnodes/simple/node/OrNode.java
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/OrNode.java
@@ -36,7 +36,7 @@ public class OrNode extends LogicalNode {
// Or of 0. We do not check for (0|x) because this will already
// canonicalize to (x|0)
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && lhs._type instanceof TypeInteger )
return lhs;
// Move constants to RHS: con*arg becomes arg*con
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/SarNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/SarNode.java
index 4a71ec06..cb48f1c8 100644
--- a/chapter21/src/main/java/com/seaofnodes/simple/node/SarNode.java
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/SarNode.java
@@ -39,7 +39,7 @@ public class SarNode extends LogicalNode {
Type t2 = rhs._type;
// Sar of 0.
- if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && lhs._type instanceof TypeInteger )
return lhs;
// TODO: x >> 3 >> (y ? 1 : 2) ==> x >> (y ? 4 : 5)
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/ShlNode.java
index 82e01841..e2da7e73 100644
--- a/chapter21/src/main/java/com/seaofnodes/simple/node/ShlNode.java
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/ShlNode.java
@@ -35,7 +35,7 @@ public class ShlNode extends LogicalNode {
if( rhs._type instanceof TypeInteger shl && shl.isConstant() ) {
// Shl of 0.
- if( (shl.value()&63)==0 )
+ if( (shl.value()&63)==0 && lhs._type instanceof TypeInteger )
return lhs;
// (x + c) << i => (x << i) + (c << i)
if( lhs instanceof AddNode add && addDep(add).in(2)._type instanceof TypeInteger c && c.isConstant() ) {
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/ShrNode.java
index 789fb8a8..97673d9a 100644
--- a/chapter21/src/main/java/com/seaofnodes/simple/node/ShrNode.java
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/ShrNode.java
@@ -35,7 +35,7 @@ public class ShrNode extends LogicalNode {
Type t2 = rhs._type;
// Shr of 0.
- if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && lhs._type instanceof TypeInteger )
return lhs;
// TODO: x >>> 3 >>> (y ? 1 : 2) ==> x >>> (y ? 4 : 5)
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/XorNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/XorNode.java
index 37d7147d..03d7bf3d 100644
--- a/chapter21/src/main/java/com/seaofnodes/simple/node/XorNode.java
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/XorNode.java
@@ -35,7 +35,7 @@ public class XorNode extends LogicalNode {
// Xor of 0. We do not check for (0^x) because this will already
// canonicalize to (x^0)
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && lhs._type instanceof TypeInteger )
return lhs;
// Move constants to RHS: con*arg becomes arg*con
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java b/chapter21/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java
new file mode 100644
index 00000000..b4893ec2
--- /dev/null
+++ b/chapter21/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java
@@ -0,0 +1,93 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class IntegerIdentityTest {
+ private static final String[] OPS = {"And","Or","Xor","Shl","Sar","Shr","Add","Mul","Div"};
+
+ @Test public void testIdentityWaitsForIntegerInput() {
+ new CodeGen("return 0;").parse();
+ for( String name : OPS ) {
+ Node lhs = input(Type.BOTTOM);
+ Node rhs = input(TypeInteger.constant(identity(name)));
+ Node op = op(name,lhs,rhs);
+ assertFalse(name,lhs._type.isa(op.compute()));
+ assertNull(name,op.idealize());
+ // Model a provisional input lifting to int. The same fold can now fire.
+ lhs._type = TypeInteger.BOT;
+ assertSame(name,lhs,op.idealize());
+ assertTrue(name,lhs._type.isa(op.compute()));
+ }
+ }
+
+ @Test public void testNonIntegerIdentityCannotEraseTheOperator() {
+ new CodeGen("return 0;").parse();
+ for( Type type : new Type[]{TypeFloat.constant(3.5)} )
+ for( String name : OPS ) {
+ Node lhs=input(type);
+ Node op=op(name,lhs,input(TypeInteger.constant(identity(name))));
+ assertNull(name,op.idealize());
+ }
+ }
+
+ @Test public void testIntegerIdentitiesAndMaskedShiftsStillFold() {
+ new CodeGen("return 0;").parse();
+ for( String name : OPS )
+ for( Type type : new Type[]{TypeInteger.BOT,TypeInteger.make(2,7),TypeInteger.constant(5)} ) {
+ Node lhs=input(type);
+ long[] identities = name.equals("And") ? new long[]{-1} :
+ name.equals("Shl") || name.equals("Sar") || name.equals("Shr") ? new long[]{0,64,-64} : new long[]{identity(name)};
+ for( long identity : identities ) {
+ Node op=op(name,lhs,input(TypeInteger.constant(identity)));
+ assertSame(name,lhs,op.idealize());
+ assertTrue(name,lhs._type.isa(op.compute()));
+ }
+ }
+ }
+
+ @Test public void testDoubleNegationWaitsForIntegerInput() {
+ new CodeGen("return 0;").parse();
+ for( Type type : new Type[]{Type.BOTTOM,TypeFloat.constant(3.5)} ) {
+ Node lhs=input(type);
+ Node inner=new MinusNode(lhs);
+ inner._type=inner.compute();
+ Node outer=new MinusNode(inner);
+ assertFalse(lhs._type.isa(outer.compute()));
+ assertNull(outer.idealize());
+ lhs._type=TypeInteger.make(2,7);
+ inner._type=inner.compute();
+ assertSame(lhs,outer.idealize());
+ assertTrue(lhs._type.isa(outer.compute()));
+ }
+ }
+
+ // These tests exercise idealize directly, before GVN or constant folding.
+ private static Node input(Type type) {
+ Node n = new ConstantNode(type);
+ n._type=type;
+ return n;
+ }
+
+ private static long identity(String name) {
+ return name.equals("And") ? -1 : name.equals("Mul") || name.equals("Div") ? 1 : 0;
+ }
+
+ private static Node op(String name, Node lhs, Node rhs) {
+ return switch(name) {
+ case "And" -> new AndNode(null,lhs,rhs);
+ case "Or" -> new OrNode(null,lhs,rhs);
+ case "Xor" -> new XorNode(null,lhs,rhs);
+ case "Shl" -> new ShlNode(null,lhs,rhs);
+ case "Sar" -> new SarNode(null,lhs,rhs);
+ case "Shr" -> new ShrNode(null,lhs,rhs);
+ case "Add" -> new AddNode(lhs,rhs);
+ case "Mul" -> new MulNode(lhs,rhs);
+ case "Div" -> new DivNode(lhs,rhs);
+ default -> throw new AssertionError(name);
+ };
+ }
+}
diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/AddNode.java
index 0f472829..3a5528ef 100644
--- a/chapter22/src/main/java/com/seaofnodes/simple/node/AddNode.java
+++ b/chapter22/src/main/java/com/seaofnodes/simple/node/AddNode.java
@@ -32,7 +32,7 @@ public class AddNode extends ArithNode {
// Add of 0. We do not check for (0+x) because this will already
// canonicalize to (x+0)
- if( t2 == TypeInteger.ZERO )
+ if( t2 == TypeInteger.ZERO && lhs._type instanceof TypeInteger )
return lhs;
// Add of same to a multiply by 2
diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/DivNode.java
index 0de1c946..7b5ae16c 100644
--- a/chapter22/src/main/java/com/seaofnodes/simple/node/DivNode.java
+++ b/chapter22/src/main/java/com/seaofnodes/simple/node/DivNode.java
@@ -18,7 +18,7 @@ public class DivNode extends ArithNode {
@Override
public Node idealize() {
// Div of 1.
- if( in(2)._type == TypeInteger.TRUE )
+ if( in(2)._type == TypeInteger.TRUE && in(1)._type instanceof TypeInteger )
return in(1);
return super.idealize();
}
diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/MinusNode.java
index 059d0d00..47b8864c 100644
--- a/chapter22/src/main/java/com/seaofnodes/simple/node/MinusNode.java
+++ b/chapter22/src/main/java/com/seaofnodes/simple/node/MinusNode.java
@@ -31,7 +31,7 @@ public class MinusNode extends Node {
@Override
public Node idealize() {
// -(-x) is x
- if( in(1) instanceof MinusNode minus )
+ if( in(1) instanceof MinusNode minus && minus.in(1)._type instanceof TypeInteger )
return minus.in(1);
return null;
diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/MulNode.java
index debc2c56..6e1dc053 100644
--- a/chapter22/src/main/java/com/seaofnodes/simple/node/MulNode.java
+++ b/chapter22/src/main/java/com/seaofnodes/simple/node/MulNode.java
@@ -32,7 +32,7 @@ public class MulNode extends ArithNode {
// Mul of 1. We do not check for (1*x) because this will already
// canonicalize to (x*1)
long c = i.value();
- if( c==1 ) return lhs;
+ if( c==1 ) return lhs._type instanceof TypeInteger ? lhs : null;
if( c==0 ) return Parser.ZERO;
// Mul by a power of 2, +/-1. Bit patterns more complex than this
// are unlikely to win on an X86 vs the normal "imul", and so
diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/OrNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/OrNode.java
index ac0d8454..96e5bc2e 100644
--- a/chapter22/src/main/java/com/seaofnodes/simple/node/OrNode.java
+++ b/chapter22/src/main/java/com/seaofnodes/simple/node/OrNode.java
@@ -26,7 +26,7 @@ public class OrNode extends ArithNode {
// Or of 0. We do not check for (0|x) because this will already
// canonicalize to (x|0)
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && lhs._type instanceof TypeInteger )
return lhs;
// Move constants to RHS: con*arg becomes arg*con
diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/SarNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/SarNode.java
index ee15fa65..b46b8f76 100644
--- a/chapter22/src/main/java/com/seaofnodes/simple/node/SarNode.java
+++ b/chapter22/src/main/java/com/seaofnodes/simple/node/SarNode.java
@@ -27,7 +27,7 @@ public class SarNode extends ArithNode {
Type t2 = rhs._type;
// Sar of 0.
- if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && lhs._type instanceof TypeInteger )
return lhs;
// TODO: x >> 3 >> (y ? 1 : 2) ==> x >> (y ? 4 : 5)
diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/ShlNode.java
index 2557bf6b..fbeabd5f 100644
--- a/chapter22/src/main/java/com/seaofnodes/simple/node/ShlNode.java
+++ b/chapter22/src/main/java/com/seaofnodes/simple/node/ShlNode.java
@@ -32,7 +32,7 @@ public class ShlNode extends ArithNode {
if( rhs._type instanceof TypeInteger shl && shl.isConstant() ) {
// Shl of 0.
- if( (shl.value()&63)==0 )
+ if( (shl.value()&63)==0 && lhs._type instanceof TypeInteger )
return lhs;
// (x + c) << i => (x << i) + (c << i)
if( lhs instanceof AddNode add && addDep(add).in(2)._type instanceof TypeInteger c && c.isConstant() ) {
diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/ShrNode.java
index 269f6b7f..ee6f6821 100644
--- a/chapter22/src/main/java/com/seaofnodes/simple/node/ShrNode.java
+++ b/chapter22/src/main/java/com/seaofnodes/simple/node/ShrNode.java
@@ -23,7 +23,7 @@ public class ShrNode extends ArithNode {
Type t2 = rhs._type;
// Shr of 0.
- if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && lhs._type instanceof TypeInteger )
return lhs;
// TODO: x >>> 3 >>> (y ? 1 : 2) ==> x >>> (y ? 4 : 5)
diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/XorNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/XorNode.java
index fdb4863e..38986c85 100644
--- a/chapter22/src/main/java/com/seaofnodes/simple/node/XorNode.java
+++ b/chapter22/src/main/java/com/seaofnodes/simple/node/XorNode.java
@@ -25,7 +25,7 @@ public class XorNode extends ArithNode {
// Xor of 0. We do not check for (0^x) because this will already
// canonicalize to (x^0)
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && lhs._type instanceof TypeInteger )
return lhs;
// Move constants to RHS: con*arg becomes arg*con
diff --git a/chapter22/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java b/chapter22/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java
new file mode 100644
index 00000000..b4893ec2
--- /dev/null
+++ b/chapter22/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java
@@ -0,0 +1,93 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class IntegerIdentityTest {
+ private static final String[] OPS = {"And","Or","Xor","Shl","Sar","Shr","Add","Mul","Div"};
+
+ @Test public void testIdentityWaitsForIntegerInput() {
+ new CodeGen("return 0;").parse();
+ for( String name : OPS ) {
+ Node lhs = input(Type.BOTTOM);
+ Node rhs = input(TypeInteger.constant(identity(name)));
+ Node op = op(name,lhs,rhs);
+ assertFalse(name,lhs._type.isa(op.compute()));
+ assertNull(name,op.idealize());
+ // Model a provisional input lifting to int. The same fold can now fire.
+ lhs._type = TypeInteger.BOT;
+ assertSame(name,lhs,op.idealize());
+ assertTrue(name,lhs._type.isa(op.compute()));
+ }
+ }
+
+ @Test public void testNonIntegerIdentityCannotEraseTheOperator() {
+ new CodeGen("return 0;").parse();
+ for( Type type : new Type[]{TypeFloat.constant(3.5)} )
+ for( String name : OPS ) {
+ Node lhs=input(type);
+ Node op=op(name,lhs,input(TypeInteger.constant(identity(name))));
+ assertNull(name,op.idealize());
+ }
+ }
+
+ @Test public void testIntegerIdentitiesAndMaskedShiftsStillFold() {
+ new CodeGen("return 0;").parse();
+ for( String name : OPS )
+ for( Type type : new Type[]{TypeInteger.BOT,TypeInteger.make(2,7),TypeInteger.constant(5)} ) {
+ Node lhs=input(type);
+ long[] identities = name.equals("And") ? new long[]{-1} :
+ name.equals("Shl") || name.equals("Sar") || name.equals("Shr") ? new long[]{0,64,-64} : new long[]{identity(name)};
+ for( long identity : identities ) {
+ Node op=op(name,lhs,input(TypeInteger.constant(identity)));
+ assertSame(name,lhs,op.idealize());
+ assertTrue(name,lhs._type.isa(op.compute()));
+ }
+ }
+ }
+
+ @Test public void testDoubleNegationWaitsForIntegerInput() {
+ new CodeGen("return 0;").parse();
+ for( Type type : new Type[]{Type.BOTTOM,TypeFloat.constant(3.5)} ) {
+ Node lhs=input(type);
+ Node inner=new MinusNode(lhs);
+ inner._type=inner.compute();
+ Node outer=new MinusNode(inner);
+ assertFalse(lhs._type.isa(outer.compute()));
+ assertNull(outer.idealize());
+ lhs._type=TypeInteger.make(2,7);
+ inner._type=inner.compute();
+ assertSame(lhs,outer.idealize());
+ assertTrue(lhs._type.isa(outer.compute()));
+ }
+ }
+
+ // These tests exercise idealize directly, before GVN or constant folding.
+ private static Node input(Type type) {
+ Node n = new ConstantNode(type);
+ n._type=type;
+ return n;
+ }
+
+ private static long identity(String name) {
+ return name.equals("And") ? -1 : name.equals("Mul") || name.equals("Div") ? 1 : 0;
+ }
+
+ private static Node op(String name, Node lhs, Node rhs) {
+ return switch(name) {
+ case "And" -> new AndNode(null,lhs,rhs);
+ case "Or" -> new OrNode(null,lhs,rhs);
+ case "Xor" -> new XorNode(null,lhs,rhs);
+ case "Shl" -> new ShlNode(null,lhs,rhs);
+ case "Sar" -> new SarNode(null,lhs,rhs);
+ case "Shr" -> new ShrNode(null,lhs,rhs);
+ case "Add" -> new AddNode(lhs,rhs);
+ case "Mul" -> new MulNode(lhs,rhs);
+ case "Div" -> new DivNode(lhs,rhs);
+ default -> throw new AssertionError(name);
+ };
+ }
+}
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/AddNode.java
index c5ba14de..dfc7d7b3 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/AddNode.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/AddNode.java
@@ -29,7 +29,7 @@ public class AddNode extends ArithNode {
// Add of 0. We do not check for (0+x) because this will already
// canonicalize to (x+0)
- if( t2 == TypeInteger.ZERO )
+ if( t2 == TypeInteger.ZERO && lhs._type instanceof TypeInteger )
return lhs;
// Add of same to a multiply by 2
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/DivNode.java
index 0de1c946..7b5ae16c 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/DivNode.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/DivNode.java
@@ -18,7 +18,7 @@ public class DivNode extends ArithNode {
@Override
public Node idealize() {
// Div of 1.
- if( in(2)._type == TypeInteger.TRUE )
+ if( in(2)._type == TypeInteger.TRUE && in(1)._type instanceof TypeInteger )
return in(1);
return super.idealize();
}
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/MinusNode.java
index f26ce37e..e5c75041 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/MinusNode.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/MinusNode.java
@@ -30,7 +30,7 @@ public class MinusNode extends Node {
@Override
public Node idealize() {
// -(-x) is x
- if( in(1) instanceof MinusNode minus )
+ if( in(1) instanceof MinusNode minus && minus.in(1)._type instanceof TypeInteger )
return minus.in(1);
return null;
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/MulNode.java
index debc2c56..6e1dc053 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/MulNode.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/MulNode.java
@@ -32,7 +32,7 @@ public class MulNode extends ArithNode {
// Mul of 1. We do not check for (1*x) because this will already
// canonicalize to (x*1)
long c = i.value();
- if( c==1 ) return lhs;
+ if( c==1 ) return lhs._type instanceof TypeInteger ? lhs : null;
if( c==0 ) return Parser.ZERO;
// Mul by a power of 2, +/-1. Bit patterns more complex than this
// are unlikely to win on an X86 vs the normal "imul", and so
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/OrNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/OrNode.java
index ac0d8454..96e5bc2e 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/OrNode.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/OrNode.java
@@ -26,7 +26,7 @@ public class OrNode extends ArithNode {
// Or of 0. We do not check for (0|x) because this will already
// canonicalize to (x|0)
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && lhs._type instanceof TypeInteger )
return lhs;
// Move constants to RHS: con*arg becomes arg*con
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/SarNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/SarNode.java
index ee15fa65..b46b8f76 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/SarNode.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/SarNode.java
@@ -27,7 +27,7 @@ public class SarNode extends ArithNode {
Type t2 = rhs._type;
// Sar of 0.
- if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && lhs._type instanceof TypeInteger )
return lhs;
// TODO: x >> 3 >> (y ? 1 : 2) ==> x >> (y ? 4 : 5)
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/ShlNode.java
index 2557bf6b..fbeabd5f 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/ShlNode.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/ShlNode.java
@@ -32,7 +32,7 @@ public class ShlNode extends ArithNode {
if( rhs._type instanceof TypeInteger shl && shl.isConstant() ) {
// Shl of 0.
- if( (shl.value()&63)==0 )
+ if( (shl.value()&63)==0 && lhs._type instanceof TypeInteger )
return lhs;
// (x + c) << i => (x << i) + (c << i)
if( lhs instanceof AddNode add && addDep(add).in(2)._type instanceof TypeInteger c && c.isConstant() ) {
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/ShrNode.java
index 269f6b7f..ee6f6821 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/ShrNode.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/ShrNode.java
@@ -23,7 +23,7 @@ public class ShrNode extends ArithNode {
Type t2 = rhs._type;
// Shr of 0.
- if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && lhs._type instanceof TypeInteger )
return lhs;
// TODO: x >>> 3 >>> (y ? 1 : 2) ==> x >>> (y ? 4 : 5)
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/XorNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/XorNode.java
index fdb4863e..38986c85 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/XorNode.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/XorNode.java
@@ -25,7 +25,7 @@ public class XorNode extends ArithNode {
// Xor of 0. We do not check for (0^x) because this will already
// canonicalize to (x^0)
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && lhs._type instanceof TypeInteger )
return lhs;
// Move constants to RHS: con*arg becomes arg*con
diff --git a/chapter23/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java b/chapter23/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java
new file mode 100644
index 00000000..b4893ec2
--- /dev/null
+++ b/chapter23/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java
@@ -0,0 +1,93 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class IntegerIdentityTest {
+ private static final String[] OPS = {"And","Or","Xor","Shl","Sar","Shr","Add","Mul","Div"};
+
+ @Test public void testIdentityWaitsForIntegerInput() {
+ new CodeGen("return 0;").parse();
+ for( String name : OPS ) {
+ Node lhs = input(Type.BOTTOM);
+ Node rhs = input(TypeInteger.constant(identity(name)));
+ Node op = op(name,lhs,rhs);
+ assertFalse(name,lhs._type.isa(op.compute()));
+ assertNull(name,op.idealize());
+ // Model a provisional input lifting to int. The same fold can now fire.
+ lhs._type = TypeInteger.BOT;
+ assertSame(name,lhs,op.idealize());
+ assertTrue(name,lhs._type.isa(op.compute()));
+ }
+ }
+
+ @Test public void testNonIntegerIdentityCannotEraseTheOperator() {
+ new CodeGen("return 0;").parse();
+ for( Type type : new Type[]{TypeFloat.constant(3.5)} )
+ for( String name : OPS ) {
+ Node lhs=input(type);
+ Node op=op(name,lhs,input(TypeInteger.constant(identity(name))));
+ assertNull(name,op.idealize());
+ }
+ }
+
+ @Test public void testIntegerIdentitiesAndMaskedShiftsStillFold() {
+ new CodeGen("return 0;").parse();
+ for( String name : OPS )
+ for( Type type : new Type[]{TypeInteger.BOT,TypeInteger.make(2,7),TypeInteger.constant(5)} ) {
+ Node lhs=input(type);
+ long[] identities = name.equals("And") ? new long[]{-1} :
+ name.equals("Shl") || name.equals("Sar") || name.equals("Shr") ? new long[]{0,64,-64} : new long[]{identity(name)};
+ for( long identity : identities ) {
+ Node op=op(name,lhs,input(TypeInteger.constant(identity)));
+ assertSame(name,lhs,op.idealize());
+ assertTrue(name,lhs._type.isa(op.compute()));
+ }
+ }
+ }
+
+ @Test public void testDoubleNegationWaitsForIntegerInput() {
+ new CodeGen("return 0;").parse();
+ for( Type type : new Type[]{Type.BOTTOM,TypeFloat.constant(3.5)} ) {
+ Node lhs=input(type);
+ Node inner=new MinusNode(lhs);
+ inner._type=inner.compute();
+ Node outer=new MinusNode(inner);
+ assertFalse(lhs._type.isa(outer.compute()));
+ assertNull(outer.idealize());
+ lhs._type=TypeInteger.make(2,7);
+ inner._type=inner.compute();
+ assertSame(lhs,outer.idealize());
+ assertTrue(lhs._type.isa(outer.compute()));
+ }
+ }
+
+ // These tests exercise idealize directly, before GVN or constant folding.
+ private static Node input(Type type) {
+ Node n = new ConstantNode(type);
+ n._type=type;
+ return n;
+ }
+
+ private static long identity(String name) {
+ return name.equals("And") ? -1 : name.equals("Mul") || name.equals("Div") ? 1 : 0;
+ }
+
+ private static Node op(String name, Node lhs, Node rhs) {
+ return switch(name) {
+ case "And" -> new AndNode(null,lhs,rhs);
+ case "Or" -> new OrNode(null,lhs,rhs);
+ case "Xor" -> new XorNode(null,lhs,rhs);
+ case "Shl" -> new ShlNode(null,lhs,rhs);
+ case "Sar" -> new SarNode(null,lhs,rhs);
+ case "Shr" -> new ShrNode(null,lhs,rhs);
+ case "Add" -> new AddNode(lhs,rhs);
+ case "Mul" -> new MulNode(lhs,rhs);
+ case "Div" -> new DivNode(lhs,rhs);
+ default -> throw new AssertionError(name);
+ };
+ }
+}
diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/AddNode.java
index c7fd4daa..2bbe1053 100644
--- a/chapter24/src/main/java/com/seaofnodes/simple/node/AddNode.java
+++ b/chapter24/src/main/java/com/seaofnodes/simple/node/AddNode.java
@@ -29,7 +29,7 @@ public class AddNode extends ArithNode {
// Add of 0. We do not check for (0+x) because this will already
// canonicalize to (x+0)
- if( t2 == TypeInteger.ZERO )
+ if( t2 == TypeInteger.ZERO && lhs._type instanceof TypeInteger )
return lhs;
// Add of same to a multiply by 2
diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/DivNode.java
index 0de1c946..7b5ae16c 100644
--- a/chapter24/src/main/java/com/seaofnodes/simple/node/DivNode.java
+++ b/chapter24/src/main/java/com/seaofnodes/simple/node/DivNode.java
@@ -18,7 +18,7 @@ public class DivNode extends ArithNode {
@Override
public Node idealize() {
// Div of 1.
- if( in(2)._type == TypeInteger.TRUE )
+ if( in(2)._type == TypeInteger.TRUE && in(1)._type instanceof TypeInteger )
return in(1);
return super.idealize();
}
diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/MinusNode.java
index 3ab74625..bca8c6ed 100644
--- a/chapter24/src/main/java/com/seaofnodes/simple/node/MinusNode.java
+++ b/chapter24/src/main/java/com/seaofnodes/simple/node/MinusNode.java
@@ -30,7 +30,7 @@ public class MinusNode extends Node {
@Override
public Node idealize() {
// -(-x) is x
- if( in(1) instanceof MinusNode minus )
+ if( in(1) instanceof MinusNode minus && minus.in(1)._type instanceof TypeInteger )
return minus.in(1);
return null;
diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/MulNode.java
index debc2c56..6e1dc053 100644
--- a/chapter24/src/main/java/com/seaofnodes/simple/node/MulNode.java
+++ b/chapter24/src/main/java/com/seaofnodes/simple/node/MulNode.java
@@ -32,7 +32,7 @@ public class MulNode extends ArithNode {
// Mul of 1. We do not check for (1*x) because this will already
// canonicalize to (x*1)
long c = i.value();
- if( c==1 ) return lhs;
+ if( c==1 ) return lhs._type instanceof TypeInteger ? lhs : null;
if( c==0 ) return Parser.ZERO;
// Mul by a power of 2, +/-1. Bit patterns more complex than this
// are unlikely to win on an X86 vs the normal "imul", and so
diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/OrNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/OrNode.java
index ac0d8454..96e5bc2e 100644
--- a/chapter24/src/main/java/com/seaofnodes/simple/node/OrNode.java
+++ b/chapter24/src/main/java/com/seaofnodes/simple/node/OrNode.java
@@ -26,7 +26,7 @@ public class OrNode extends ArithNode {
// Or of 0. We do not check for (0|x) because this will already
// canonicalize to (x|0)
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && lhs._type instanceof TypeInteger )
return lhs;
// Move constants to RHS: con*arg becomes arg*con
diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/SarNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/SarNode.java
index ee15fa65..b46b8f76 100644
--- a/chapter24/src/main/java/com/seaofnodes/simple/node/SarNode.java
+++ b/chapter24/src/main/java/com/seaofnodes/simple/node/SarNode.java
@@ -27,7 +27,7 @@ public class SarNode extends ArithNode {
Type t2 = rhs._type;
// Sar of 0.
- if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && lhs._type instanceof TypeInteger )
return lhs;
// TODO: x >> 3 >> (y ? 1 : 2) ==> x >> (y ? 4 : 5)
diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/ShlNode.java
index 2557bf6b..fbeabd5f 100644
--- a/chapter24/src/main/java/com/seaofnodes/simple/node/ShlNode.java
+++ b/chapter24/src/main/java/com/seaofnodes/simple/node/ShlNode.java
@@ -32,7 +32,7 @@ public class ShlNode extends ArithNode {
if( rhs._type instanceof TypeInteger shl && shl.isConstant() ) {
// Shl of 0.
- if( (shl.value()&63)==0 )
+ if( (shl.value()&63)==0 && lhs._type instanceof TypeInteger )
return lhs;
// (x + c) << i => (x << i) + (c << i)
if( lhs instanceof AddNode add && addDep(add).in(2)._type instanceof TypeInteger c && c.isConstant() ) {
diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/ShrNode.java
index 269f6b7f..ee6f6821 100644
--- a/chapter24/src/main/java/com/seaofnodes/simple/node/ShrNode.java
+++ b/chapter24/src/main/java/com/seaofnodes/simple/node/ShrNode.java
@@ -23,7 +23,7 @@ public class ShrNode extends ArithNode {
Type t2 = rhs._type;
// Shr of 0.
- if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && lhs._type instanceof TypeInteger )
return lhs;
// TODO: x >>> 3 >>> (y ? 1 : 2) ==> x >>> (y ? 4 : 5)
diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/XorNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/XorNode.java
index fdb4863e..38986c85 100644
--- a/chapter24/src/main/java/com/seaofnodes/simple/node/XorNode.java
+++ b/chapter24/src/main/java/com/seaofnodes/simple/node/XorNode.java
@@ -25,7 +25,7 @@ public class XorNode extends ArithNode {
// Xor of 0. We do not check for (0^x) because this will already
// canonicalize to (x^0)
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && lhs._type instanceof TypeInteger )
return lhs;
// Move constants to RHS: con*arg becomes arg*con
diff --git a/chapter24/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java b/chapter24/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java
new file mode 100644
index 00000000..b4893ec2
--- /dev/null
+++ b/chapter24/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java
@@ -0,0 +1,93 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class IntegerIdentityTest {
+ private static final String[] OPS = {"And","Or","Xor","Shl","Sar","Shr","Add","Mul","Div"};
+
+ @Test public void testIdentityWaitsForIntegerInput() {
+ new CodeGen("return 0;").parse();
+ for( String name : OPS ) {
+ Node lhs = input(Type.BOTTOM);
+ Node rhs = input(TypeInteger.constant(identity(name)));
+ Node op = op(name,lhs,rhs);
+ assertFalse(name,lhs._type.isa(op.compute()));
+ assertNull(name,op.idealize());
+ // Model a provisional input lifting to int. The same fold can now fire.
+ lhs._type = TypeInteger.BOT;
+ assertSame(name,lhs,op.idealize());
+ assertTrue(name,lhs._type.isa(op.compute()));
+ }
+ }
+
+ @Test public void testNonIntegerIdentityCannotEraseTheOperator() {
+ new CodeGen("return 0;").parse();
+ for( Type type : new Type[]{TypeFloat.constant(3.5)} )
+ for( String name : OPS ) {
+ Node lhs=input(type);
+ Node op=op(name,lhs,input(TypeInteger.constant(identity(name))));
+ assertNull(name,op.idealize());
+ }
+ }
+
+ @Test public void testIntegerIdentitiesAndMaskedShiftsStillFold() {
+ new CodeGen("return 0;").parse();
+ for( String name : OPS )
+ for( Type type : new Type[]{TypeInteger.BOT,TypeInteger.make(2,7),TypeInteger.constant(5)} ) {
+ Node lhs=input(type);
+ long[] identities = name.equals("And") ? new long[]{-1} :
+ name.equals("Shl") || name.equals("Sar") || name.equals("Shr") ? new long[]{0,64,-64} : new long[]{identity(name)};
+ for( long identity : identities ) {
+ Node op=op(name,lhs,input(TypeInteger.constant(identity)));
+ assertSame(name,lhs,op.idealize());
+ assertTrue(name,lhs._type.isa(op.compute()));
+ }
+ }
+ }
+
+ @Test public void testDoubleNegationWaitsForIntegerInput() {
+ new CodeGen("return 0;").parse();
+ for( Type type : new Type[]{Type.BOTTOM,TypeFloat.constant(3.5)} ) {
+ Node lhs=input(type);
+ Node inner=new MinusNode(lhs);
+ inner._type=inner.compute();
+ Node outer=new MinusNode(inner);
+ assertFalse(lhs._type.isa(outer.compute()));
+ assertNull(outer.idealize());
+ lhs._type=TypeInteger.make(2,7);
+ inner._type=inner.compute();
+ assertSame(lhs,outer.idealize());
+ assertTrue(lhs._type.isa(outer.compute()));
+ }
+ }
+
+ // These tests exercise idealize directly, before GVN or constant folding.
+ private static Node input(Type type) {
+ Node n = new ConstantNode(type);
+ n._type=type;
+ return n;
+ }
+
+ private static long identity(String name) {
+ return name.equals("And") ? -1 : name.equals("Mul") || name.equals("Div") ? 1 : 0;
+ }
+
+ private static Node op(String name, Node lhs, Node rhs) {
+ return switch(name) {
+ case "And" -> new AndNode(null,lhs,rhs);
+ case "Or" -> new OrNode(null,lhs,rhs);
+ case "Xor" -> new XorNode(null,lhs,rhs);
+ case "Shl" -> new ShlNode(null,lhs,rhs);
+ case "Sar" -> new SarNode(null,lhs,rhs);
+ case "Shr" -> new ShrNode(null,lhs,rhs);
+ case "Add" -> new AddNode(lhs,rhs);
+ case "Mul" -> new MulNode(lhs,rhs);
+ case "Div" -> new DivNode(lhs,rhs);
+ default -> throw new AssertionError(name);
+ };
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
index ca2cc955..64892f3f 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
@@ -49,7 +49,7 @@ public class CodeGen {
// -Dsimple.assert.expensive=0 disables these checks
// -Dsimple.assert.expensive=1 checks every step
// -Dsimple.assert.expensive=N checks once every 1<<N steps
- private static final int EXPENSIVE_ASSERT_LOG = Integer.getInteger("simple.assert.expensive",8);
+ private static final int EXPENSIVE_ASSERT_LOG = Integer.getInteger("simple.assert.expensive",1);
public static boolean expensiveAssert() { return EXPENSIVE_ASSERT_LOG > 0; }
public static boolean expensiveAssert( int trip ) {
if( EXPENSIVE_ASSERT_LOG <= 0 ) return false;
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/AddNode.java
index 46607c23..a29ba73b 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/AddNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/AddNode.java
@@ -37,7 +37,7 @@ public class AddNode extends ArithNode {
// Add of 0. We do not check for (0+x) because this will already
// canonicalize to (x+0)
- if( t2 == TypeInteger.ZERO )
+ if( t2 == TypeInteger.ZERO && lhs._type.isa(compute()) )
return lhs;
// Add of same to a multiply by 2
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/DivNode.java
index dcc9a27a..f173f0ef 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/DivNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/DivNode.java
@@ -22,12 +22,12 @@ public class DivNode extends ArithNode {
@Override
public Node idealize() {
// Div of 1.
- if( in(2)._type == TypeInteger.TRUE )
+ if( in(2)._type == TypeInteger.TRUE && in(1)._type.isa(compute()) )
return in(1);
// Div of constant
if( _mode==2 ) {
- if( in(2)._type == TypeFloat.constant(1.) )
+ if( in(2)._type == TypeFloat.constant(1.) && in(1)._type.isa(compute()) )
return in(1);
if( in(2)._type instanceof TypeFloat f && f.isConstant() )
return new MulNode(in(1),ConstantNode.make(TypeFloat.constant(1.0/f.value())).peephole(),_mode);
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MinusNode.java
index 2261adab..34e5667e 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/MinusNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/MinusNode.java
@@ -65,7 +65,7 @@ public class MinusNode extends Node implements ModeNode {
@Override
public Node idealize() {
// -(-x) is x
- if( in(1) instanceof MinusNode minus )
+ if( in(1) instanceof MinusNode minus && minus.in(1)._type.isa(compute()) )
return minus.in(1);
// Can we decide int vs flt?
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MulNode.java
index e773ed57..bb99cad5 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/MulNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/MulNode.java
@@ -35,7 +35,7 @@ public class MulNode extends ArithNode {
// Mul of 1. We do not check for (1*x) because this will already
// canonicalize to (x*1)
long c = i.value();
- if( c==1 ) return lhs;
+ if( c==1 ) return lhs._type.isa(compute()) ? lhs : super.idealize();
if( c==0 ) return CodeGen.CODE.ZERO;
// Mul by a power of 2, +/-1. Bit patterns more complex than this
// are unlikely to win on an X86 vs the normal "imul", and so
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/SarNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/SarNode.java
index e3f99ed4..de21fead 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/SarNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/SarNode.java
@@ -28,7 +28,7 @@ public class SarNode extends ArithNode {
Type t2 = rhs._type;
// Sar of 0.
- if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && forceInt(lhs) )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && forceInt(lhs) && lhs._type instanceof TypeInteger )
return lhs;
// TODO: x >> 3 >> (y ? 1 : 2) ==> x >> (y ? 4 : 5)
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ShlNode.java
index 24d9bdb4..8f7b7b7e 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/ShlNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ShlNode.java
@@ -33,7 +33,7 @@ public class ShlNode extends ArithNode {
if( rhs._type instanceof TypeInteger shl && shl.isConstant() ) {
// Shl of 0.
- if( (shl.value()&63)==0 && forceInt(lhs) )
+ if( (shl.value()&63)==0 && forceInt(lhs) && lhs._type instanceof TypeInteger )
return lhs;
// (x + c) << i => (x << i) + (c << i)
if( lhs instanceof AddNode add && addDep(add).in(2)._type instanceof TypeInteger c && c.isConstant() ) {
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ShrNode.java
index 1bf7d118..2b268b42 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/ShrNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ShrNode.java
@@ -23,7 +23,7 @@ public class ShrNode extends ArithNode {
Type t2 = rhs._type;
// Shr of 0.
- if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && forceInt(lhs) )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && forceInt(lhs) && lhs._type instanceof TypeInteger )
return lhs;
// TODO: x >>> 3 >>> (y ? 1 : 2) ==> x >>> (y ? 4 : 5)
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/XorNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/XorNode.java
index 27ebc3dc..a979bad8 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/XorNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/XorNode.java
@@ -26,7 +26,7 @@ public class XorNode extends ArithNode {
// Xor of 0. We do not check for (0^x) because this will already
// canonicalize to (x^0)
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && forceInt(lhs) )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && forceInt(lhs) && lhs._type instanceof TypeInteger )
return lhs;
// Move constants to RHS: con*arg becomes arg*con
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/FuzzerWrap.java b/chapter25/src/test/java/com/seaofnodes/simple/FuzzerWrap.java
index 66bc4717..912c78ae 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/FuzzerWrap.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/FuzzerWrap.java
@@ -34,6 +34,7 @@ public class FuzzerWrap {
};
private static final long[] OPEN_FAILING_SEEDS = {
+ 5948158604996761547L,
};
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java b/chapter25/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java
new file mode 100644
index 00000000..90864731
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/IntegerIdentityTest.java
@@ -0,0 +1,113 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class IntegerIdentityTest {
+ private static final String[] OPS = {"And","Or","Xor","Shl","Sar","Shr","Add","Mul","Div"};
+
+ @Test public void testIdentityWaitsForIntegerInput() {
+ new CodeGen("return 0;").parse();
+ for( String name : OPS ) {
+ Node lhs = input(Type.BOTTOM);
+ Node rhs = input(TypeInteger.constant(identity(name)));
+ Node op = op(name,lhs,rhs);
+ assertFalse(name,lhs._type.isa(op.compute()));
+ assertNull(name,op.idealize());
+ // Model a provisional input lifting to int. The same fold can now fire.
+ lhs._type = TypeInteger.BOT;
+ assertSame(name,lhs,op.idealize());
+ assertTrue(name,lhs._type.isa(op.compute()));
+ }
+ }
+
+ @Test public void testNonIntegerIdentityCannotEraseTheOperator() {
+ new CodeGen("return 0;").parse();
+ for( Type type : new Type[]{TypeFloat.constant(3.5),TypeScalar.BOT} )
+ for( String name : OPS ) {
+ Node lhs=input(type);
+ Node op=op(name,lhs,input(TypeInteger.constant(identity(name))));
+ assertNull(name,op.idealize());
+ }
+ }
+
+ @Test public void testIntegerIdentitiesAndMaskedShiftsStillFold() {
+ new CodeGen("return 0;").parse();
+ for( String name : OPS )
+ for( Type type : new Type[]{TypeInteger.BOT,TypeInteger.make(2,7),TypeInteger.constant(5)} ) {
+ Node lhs=input(type);
+ long[] identities = name.equals("And") ? new long[]{-1} :
+ name.equals("Shl") || name.equals("Sar") || name.equals("Shr") ? new long[]{0,64,-64} : new long[]{identity(name)};
+ for( long identity : identities ) {
+ Node op=op(name,lhs,input(TypeInteger.constant(identity)));
+ assertSame(name,lhs,op.idealize());
+ assertTrue(name,lhs._type.isa(op.compute()));
+ }
+ }
+ }
+
+ @Test public void testFloatingIdentitiesKeepTheirResultType() {
+ new CodeGen("return 0;").parse();
+ for( Type type : new Type[]{Type.BOTTOM,TypeInteger.BOT,TypeFloat.F64} ) {
+ Node lhs=input(type);
+ Node zero=input(TypeInteger.ZERO), one=input(TypeInteger.TRUE);
+ Node[] ops = {new AddNode(lhs,zero,(byte)2), new MulNode(lhs,one,(byte)2),
+ new DivNode(lhs,one,(byte)2),
+ new DivNode(lhs,input(TypeFloat.constant(1.)),(byte)2)};
+ for( Node op : ops ) {
+ Node replacement=op.idealize();
+ if( type==TypeFloat.F64 )
+ assertSame(lhs,replacement);
+ else
+ assertNotSame(lhs,replacement);
+ if( replacement!=null )
+ assertTrue(replacement.compute().isa(op.compute()));
+ }
+ }
+ }
+
+ @Test public void testDoubleNegationWaitsForIntegerInput() {
+ new CodeGen("return 0;").parse();
+ for( Type type : new Type[]{Type.BOTTOM,TypeFloat.constant(3.5),TypeScalar.BOT} ) {
+ Node lhs=input(type);
+ Node inner=new MinusNode(lhs,(byte)1);
+ inner._type=inner.compute();
+ Node outer=new MinusNode(inner,(byte)1);
+ assertFalse(lhs._type.isa(outer.compute()));
+ assertNull(outer.idealize());
+ lhs._type=TypeInteger.make(2,7);
+ inner._type=inner.compute();
+ assertSame(lhs,outer.idealize());
+ assertTrue(lhs._type.isa(outer.compute()));
+ }
+ }
+
+ // These tests exercise idealize directly, before GVN or constant folding.
+ private static Node input(Type type) {
+ Node n = new ConstantNode(type);
+ n._type=type;
+ return n;
+ }
+
+ private static long identity(String name) {
+ return name.equals("And") ? -1 : name.equals("Mul") || name.equals("Div") ? 1 : 0;
+ }
+
+ private static Node op(String name, Node lhs, Node rhs) {
+ return switch(name) {
+ case "And" -> new AndNode(null,lhs,rhs);
+ case "Or" -> new OrNode(null,lhs,rhs);
+ case "Xor" -> new XorNode(null,lhs,rhs);
+ case "Shl" -> new ShlNode(null,lhs,rhs);
+ case "Sar" -> new SarNode(null,lhs,rhs);
+ case "Shr" -> new ShrNode(null,lhs,rhs);
+ case "Add" -> new AddNode(lhs,rhs,(byte)1);
+ case "Mul" -> new MulNode(lhs,rhs,(byte)1);
+ case "Div" -> new DivNode(lhs,rhs,(byte)1);
+ default -> throw new AssertionError(name);
+ };
+ }
+}