summaryrefslogtreecommitdiff
path: root/chapter26/src
diff options
context:
space:
mode:
Diffstat (limited to 'chapter26/src')
-rw-r--r--chapter26/src/main/java/com/seaofnodes/simple/node/ArithNode.java20
-rw-r--r--chapter26/src/main/java/com/seaofnodes/simple/node/ConvertNode.java14
-rw-r--r--chapter26/src/test/java/com/seaofnodes/simple/ArithNodeTest.java96
-rw-r--r--chapter26/src/test/java/com/seaofnodes/simple/ConvertNodeTest.java69
4 files changed, 183 insertions, 16 deletions
diff --git a/chapter26/src/main/java/com/seaofnodes/simple/node/ArithNode.java b/chapter26/src/main/java/com/seaofnodes/simple/node/ArithNode.java
index 5f0862b2..bdd918fd 100644
--- a/chapter26/src/main/java/com/seaofnodes/simple/node/ArithNode.java
+++ b/chapter26/src/main/java/com/seaofnodes/simple/node/ArithNode.java
@@ -106,12 +106,16 @@ public abstract class ArithNode extends Node implements ModeNode {
@Override
public final Type compute() {
Type t1 = in(1)._type, t2 = in(2)._type;
+ // Constant array payloads lie below their element ranges in the lattice.
+ if( t1 instanceof TypeConAry ary ) t1 = ary.elem();
+ if( t2 instanceof TypeConAry ary ) t2 = ary.elem();
if( t1==null || t2==null ) return Type.TOP;
if( t1.isHigh() || t2.isHigh() ) return Type.TOP;
- if( t1==Type.BOTTOM || t2==Type.BOTTOM ) return Type.BOTTOM;
+ // Without numeric inputs, stay below both possible result families.
+ // Pointer modes belong to comparisons, not arithmetic.
+ if( !(t1 instanceof TypeInteger || t1 instanceof TypeFloat) ||
+ !(t2 instanceof TypeInteger || t2 instanceof TypeFloat) ) return Type.BOTTOM;
byte mode = _mode==0 ? mode(t1,t2) : _mode;
- if( mode==0 )
- return CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal() ? Type.BOTTOM : Type.TOP;
if( mode==1 ) {
if( t1 instanceof TypeInteger x &&
t2 instanceof TypeInteger y )
@@ -121,12 +125,10 @@ public abstract class ArithNode extends Node implements ModeNode {
return TypeInteger.BOT;
}
// FP handling
- if( t1 instanceof TypeFloat f1 &&
- t2 instanceof TypeFloat f2 ) {
- if( f1.isConstant() && f2.isConstant() )
- return TypeFloat.constant(doOp(f1.value(),f2.value()));
- return t1.meet(t2);
- }
+ if( t1 instanceof TypeFloat f1 && t2 instanceof TypeFloat f2 &&
+ f1.isConstant() && f2.isConstant() )
+ return TypeFloat.constant(doOp(f1.value(),f2.value()));
+ // Two f32 inputs can still produce a result needing f64 precision.
return TypeFloat.F64;
}
diff --git a/chapter26/src/main/java/com/seaofnodes/simple/node/ConvertNode.java b/chapter26/src/main/java/com/seaofnodes/simple/node/ConvertNode.java
index c5be0123..7e527de2 100644
--- a/chapter26/src/main/java/com/seaofnodes/simple/node/ConvertNode.java
+++ b/chapter26/src/main/java/com/seaofnodes/simple/node/ConvertNode.java
@@ -3,7 +3,6 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.print.ExprPrinter;
import com.seaofnodes.simple.Parser;
-import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.type.*;
import java.util.HashMap;
@@ -29,14 +28,15 @@ public class ConvertNode extends Node {
return p.p(label()).open().n(val()).close();
}
- // The declaration fixes the result family during pessimistic parsing.
- // From SCCP onward, preserve dead inputs and stronger facts in that family.
+ // The declaration bounds the result; preserve dead inputs and stronger
+ // scalar facts so SCCP can propagate constants through loop conversions.
+ // Managed pointers keep the declared type until idealize checks access:
+ // array element access must agree in both directions, so it cannot gate
+ // precision in this monotone transfer function.
@Override public Type compute() {
Type src = val()._type;
- if( CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Opto.ordinal() ) {
- if( src.isHigh() ) return Type.TOP;
- if( (src.isa(_dst) && src.accessISA(_dst)) ) return src;
- }
+ if( src.isHigh() ) return Type.TOP;
+ if( !(_dst instanceof TypeMemPtr) && src.isa(_dst) ) return src;
return _dst;
}
diff --git a/chapter26/src/test/java/com/seaofnodes/simple/ArithNodeTest.java b/chapter26/src/test/java/com/seaofnodes/simple/ArithNodeTest.java
new file mode 100644
index 00000000..708c32a2
--- /dev/null
+++ b/chapter26/src/test/java/com/seaofnodes/simple/ArithNodeTest.java
@@ -0,0 +1,96 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class ArithNodeTest {
+ @Test public void testUnresolvedInput() {
+ new CodeGen("return 0;").driver(CodeGen.Phase.Opto);
+ Node lhs = new ConstantNode(TypeInteger.TRUE);
+ Node rhs = new ConstantNode(TypeInteger.TRUE);
+ ArithNode add = new AddNode(lhs,rhs);
+ assertSame(TypeInteger.constant(2),add.compute());
+ lhs._type = TypeScalar.BOT;
+ assertSame(Type.BOTTOM,add.compute());
+ add.setMode((byte)1);
+ assertSame(Type.BOTTOM,add.compute());
+ }
+
+ @Test public void testFloatPrecision() {
+ CodeGen code = new CodeGen("f32 x = arg; return x+1.0;").driver(CodeGen.Phase.TypeCheck);
+ assertEquals("1.6777217E7",Eval2.eval(code,16777216));
+ code = new CodeGen("f32 x = arg; f32 y = x+1.0; return y;").driver(CodeGen.Phase.TypeCheck);
+ assertEquals("1.6777216E7",Eval2.eval(code,16777216));
+ }
+
+ @Test public void testMonotoneCompute() {
+ CodeGen code = new CodeGen("return 0;");
+ ArrayList<Type> inputs = new ArrayList<>(Arrays.asList(
+ Type.BOTTOM,Type.CONTROL,Type.NIL,TypeScalar.BOT,TypeMem.BOT));
+ TypeInteger.gather(inputs);
+ TypeFloat.gather(inputs);
+ TypePtr.gather(inputs);
+ TypeMemPtr.gather(inputs);
+ TypeFunPtr.gather(inputs);
+ inputs.add(TypeConAryB.make(new byte[]{1,2,3}));
+ for( long value : new long[]{Long.MIN_VALUE,Long.MIN_VALUE+1,-1,1,Long.MAX_VALUE} )
+ inputs.add(TypeInteger.constant(value));
+ for( double value : new double[]{-0.0,0.0,-1.0,1.0,16777216.0,16777217.0,Double.NaN,Double.NEGATIVE_INFINITY,Double.POSITIVE_INFINITY} )
+ inputs.add(TypeFloat.constant(value));
+ int size = inputs.size();
+ for( int i = 0; i<size; i++ ) inputs.add(inputs.get(i).dual());
+ size = inputs.size();
+
+ // Isolated inputs let us vary types without peepholes settling modes.
+ Node lhs = new ConstantNode(Type.BOTTOM);
+ Node rhs = new ConstantNode(Type.BOTTOM);
+ lhs.setDef(0,null);
+ rhs.setDef(0,null);
+ ArrayList<ArithNode> ops = new ArrayList<>();
+ for( byte mode = 0; mode<3; mode++ ) {
+ ops.add(new AddNode(lhs,rhs,mode));
+ ops.add(new SubNode(lhs,rhs,mode));
+ ops.add(new MulNode(lhs,rhs,mode));
+ ops.add(new DivNode(lhs,rhs,mode));
+ }
+ ops.add(new AndNode(null,lhs,rhs));
+ ops.add(new OrNode(null,lhs,rhs));
+ ops.add(new XorNode(null,lhs,rhs));
+ ops.add(new ShlNode(null,lhs,rhs));
+ ops.add(new SarNode(null,lhs,rhs));
+ ops.add(new ShrNode(null,lhs,rhs));
+ Type[][][] expected = new Type[ops.size()][size][size];
+ for( CodeGen.Phase phase : new CodeGen.Phase[]{CodeGen.Phase.Parse,CodeGen.Phase.Iter,CodeGen.Phase.Opto,CodeGen.Phase.IterPostOpto} ) {
+ code.driver(phase);
+ for( int op = 0; op<ops.size(); op++ ) {
+ ArithNode arith = ops.get(op);
+ Type[][] results = new Type[size][size];
+ for( int i = 0; i<size; i++ )
+ for( int j = 0; j<size; j++ ) {
+ lhs._type = inputs.get(i);
+ rhs._type = inputs.get(j);
+ results[i][j] = arith.compute();
+ if( phase==CodeGen.Phase.Parse ) expected[op][i][j] = results[i][j];
+ else assertSame("Phase changed compute",expected[op][i][j],results[i][j]);
+ }
+ // Check the order relation in each argument independently.
+ // This covers both falling SCCP types and rising Iter types.
+ for( int i = 0; i<size; i++ )
+ for( int j = 0; j<size; j++ )
+ if( inputs.get(i).isa(inputs.get(j)) )
+ for( int k = 0; k<size; k++ ) {
+ if( !results[i][k].isa(results[j][k]) || !results[k][i].isa(results[k][j]) )
+ fail(arith.getClass().getSimpleName()+" mode="+arith.mode()+": "+
+ inputs.get(i)+" -> "+inputs.get(j)+", other="+inputs.get(k)+
+ "; lhs results "+results[i][k]+" -> "+results[j][k]+
+ "; rhs results "+results[k][i]+" -> "+results[k][j]);
+ }
+ }
+ }
+ }
+}
diff --git a/chapter26/src/test/java/com/seaofnodes/simple/ConvertNodeTest.java b/chapter26/src/test/java/com/seaofnodes/simple/ConvertNodeTest.java
new file mode 100644
index 00000000..2342b33a
--- /dev/null
+++ b/chapter26/src/test/java/com/seaofnodes/simple/ConvertNodeTest.java
@@ -0,0 +1,69 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.node.ConstantNode;
+import com.seaofnodes.simple.node.ConvertNode;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.type.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class ConvertNodeTest {
+ @Test public void testMonotoneCompute() {
+ CodeGen code = new CodeGen("return 0;");
+ ArrayList<Type> inputs = new ArrayList<>(Arrays.asList(
+ Type.BOTTOM,Type.CONTROL,Type.NIL,TypeScalar.BOT,TypeMem.BOT));
+ TypeInteger.gather(inputs);
+ TypeFloat.gather(inputs);
+ TypePtr.gather(inputs);
+ TypeMemPtr.gather(inputs);
+ TypeFunPtr.gather(inputs);
+ // Writable arrays require matching element access in both directions.
+ // Widening the element's access can make that check pass while keeping
+ // a known length; compute must not suddenly gain that length fact.
+ for( TypeInteger len : new TypeInteger[]{TypeInteger.U32,TypeInteger.TRUE} )
+ for( boolean elementRO : new boolean[]{false,true} )
+ for( boolean arrayRO : new boolean[]{false,true} )
+ inputs.add(TypeMemPtr.make(TypeStruct.makeAry("[]test",len,-1,
+ TypeMemPtr.TEST.withAccess(elementRO),-2,false)).withAccess(arrayRO));
+ inputs.add(TypeConAryB.make(new byte[]{1,2,3}));
+ for( long value : new long[]{Long.MIN_VALUE,-129,-128,-1,0,1,127,128,255,256,Long.MAX_VALUE} )
+ inputs.add(TypeInteger.constant(value));
+ for( double value : new double[]{-0.0,0.0,1.0,16777217.0,Double.NaN,Double.NEGATIVE_INFINITY,Double.POSITIVE_INFINITY} )
+ inputs.add(TypeFloat.constant(value));
+ int size = inputs.size();
+ for( int i = 0; i<size; i++ ) inputs.add(inputs.get(i).dual());
+
+ ArrayList<Type> destinations = new ArrayList<>();
+ for( Type t : inputs )
+ if( !t.isHigh() && (t instanceof TypeInteger || t instanceof TypeFloat || t instanceof TypeNil) )
+ destinations.add(t);
+
+ // Vary one isolated input without folding or lowering the conversion.
+ Node input = new ConstantNode(Type.BOTTOM);
+ input.setDef(0,null);
+ Type[][] expected = new Type[destinations.size()][inputs.size()];
+ for( CodeGen.Phase phase : new CodeGen.Phase[]{CodeGen.Phase.Parse,CodeGen.Phase.Iter,CodeGen.Phase.Opto,CodeGen.Phase.IterPostOpto} ) {
+ code.driver(phase);
+ for( int d = 0; d<destinations.size(); d++ ) {
+ Type dst = destinations.get(d);
+ ConvertNode convert = new ConvertNode(dst,input);
+ Type[] results = new Type[inputs.size()];
+ for( int i = 0; i<inputs.size(); i++ ) {
+ input._type = inputs.get(i);
+ results[i] = convert.compute();
+ if( phase==CodeGen.Phase.Parse ) expected[d][i] = results[i];
+ else assertSame("Phase changed compute for "+inputs.get(i)+" to "+dst,expected[d][i],results[i]);
+ }
+ // Check both rising Iter and falling SCCP with the same order.
+ for( int i = 0; i<inputs.size(); i++ )
+ for( int j = 0; j<inputs.size(); j++ )
+ if( inputs.get(i).isa(inputs.get(j)) && !results[i].isa(results[j]) )
+ fail("Convert to "+dst+": "+inputs.get(i)+" -> "+inputs.get(j)+
+ " gave "+results[i]+" -> "+results[j]);
+ }
+ }
+ }
+}