diff options
Diffstat (limited to 'chapter28b')
4 files changed, 188 insertions, 18 deletions
diff --git a/chapter28b/src/main/java/com/seaofnodes/simple/node/ArithNode.java b/chapter28b/src/main/java/com/seaofnodes/simple/node/ArithNode.java index 92ebd587..e2a88944 100644 --- a/chapter28b/src/main/java/com/seaofnodes/simple/node/ArithNode.java +++ b/chapter28b/src/main/java/com/seaofnodes/simple/node/ArithNode.java @@ -111,12 +111,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 ) @@ -126,12 +130,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/chapter28b/src/main/java/com/seaofnodes/simple/node/ConvertNode.java b/chapter28b/src/main/java/com/seaofnodes/simple/node/ConvertNode.java index cdc890a7..7f5398ef 100644 --- a/chapter28b/src/main/java/com/seaofnodes/simple/node/ConvertNode.java +++ b/chapter28b/src/main/java/com/seaofnodes/simple/node/ConvertNode.java @@ -3,13 +3,17 @@ 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; -// Convert a value to an authoritative destination type. The destination is -// known structurally from a declaration; the source family might sharpen later. +// Convert the incoming value; actual conversion depends on the _dst type. +// Convert ints to floats, ptrs to ints, nullable to not-null, etc. Once the +// input type is known precisely enough, the idealize() call triggers and +// replaces the Convert with a concrete conversion (e.g. toFloatNode). +// +// Convert has no runtime dynamic behavior; it must *always* get replaced with +// some concrete behavior (or its an error). public class ConvertNode extends Node { private Type _dst; @@ -29,14 +33,13 @@ 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. + + // Not much to do here, if the input type is not sharp enough to fold into + // the proper conversion, its not sharp enough to do much else. @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/chapter28b/src/test/java/com/seaofnodes/simple/ArithNodeTest.java b/chapter28b/src/test/java/com/seaofnodes/simple/ArithNodeTest.java new file mode 100644 index 00000000..e912f224 --- /dev/null +++ b/chapter28b/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 = ConstantNode.seed(TypeInteger.TRUE); + Node rhs = ConstantNode.seed(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 = ConstantNode.seed(Type.BOTTOM); + Node rhs = ConstantNode.seed(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/chapter28b/src/test/java/com/seaofnodes/simple/ConvertNodeTest.java b/chapter28b/src/test/java/com/seaofnodes/simple/ConvertNodeTest.java new file mode 100644 index 00000000..96d73f26 --- /dev/null +++ b/chapter28b/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 = ConstantNode.seed(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]); + } + } + } +} |
