1 /*
   2  * Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 package org.graalvm.compiler.nodes.test;
  24 
  25 import java.util.EnumSet;
  26 import java.util.HashSet;
  27 
  28 import org.graalvm.compiler.core.common.calc.FloatConvert;
  29 import org.graalvm.compiler.core.common.calc.FloatConvertCategory;
  30 import org.graalvm.compiler.core.common.type.ArithmeticOpTable;
  31 import org.graalvm.compiler.core.common.type.ArithmeticOpTable.BinaryOp;
  32 import org.graalvm.compiler.core.common.type.ArithmeticOpTable.IntegerConvertOp;
  33 import org.graalvm.compiler.core.common.type.ArithmeticOpTable.ShiftOp;
  34 import org.graalvm.compiler.core.common.type.FloatStamp;
  35 import org.graalvm.compiler.core.common.type.IntegerStamp;
  36 import org.graalvm.compiler.core.common.type.PrimitiveStamp;
  37 import org.graalvm.compiler.core.common.type.Stamp;
  38 import org.graalvm.compiler.test.GraalTest;
  39 import org.junit.Test;
  40 
  41 import jdk.vm.ci.meta.Constant;
  42 import jdk.vm.ci.meta.JavaConstant;
  43 import jdk.vm.ci.meta.JavaKind;
  44 
  45 /**
  46  * Exercise the various stamp folding operations by generating ranges from a set of boundary values
  47  * and then ensuring that the values that produced those ranges are in the resulting stamp.
  48  */
  49 public class PrimitiveStampBoundaryTest extends GraalTest {
  50 
  51     static long[] longBoundaryValues = {Long.MIN_VALUE, Long.MIN_VALUE + 1, Integer.MIN_VALUE, Integer.MIN_VALUE + 1, -1, 0, 1, Integer.MAX_VALUE - 1, Integer.MAX_VALUE, Long.MAX_VALUE - 1,
  52                     Long.MAX_VALUE};
  53 
  54     static int[] shiftBoundaryValues = {-128, -1, 0, 1, 4, 8, 16, 31, 63, 128};
  55 
  56     static HashSet<IntegerStamp> shiftStamps;
  57     static HashSet<PrimitiveStamp> integerTestStamps;
  58     static HashSet<PrimitiveStamp> floatTestStamps;
  59 
  60     static {
  61         shiftStamps = new HashSet<>();
  62         for (long v1 : shiftBoundaryValues) {
  63             for (long v2 : shiftBoundaryValues) {
  64                 shiftStamps.add(IntegerStamp.create(32, Math.min(v1, v2), Math.max(v1, v2)));
  65             }
  66         }
  67 
  68         integerTestStamps = new HashSet<>();
  69         for (long v1 : longBoundaryValues) {
  70             for (long v2 : longBoundaryValues) {
  71                 if (v2 == (int) v2 && v1 == (int) v1) {
  72                     integerTestStamps.add(IntegerStamp.create(32, Math.min(v1, v2), Math.max(v1, v2)));
  73                 }
  74                 integerTestStamps.add(IntegerStamp.create(64, Math.min(v1, v2), Math.max(v1, v2)));
  75             }
  76         }
  77     }
  78 
  79     static double[] doubleBoundaryValues = {Double.NEGATIVE_INFINITY, Double.MIN_VALUE, Float.NEGATIVE_INFINITY, Float.MIN_VALUE,
  80                     Long.MIN_VALUE, Long.MIN_VALUE + 1, Integer.MIN_VALUE, Integer.MIN_VALUE + 1, -1, 0, 1,
  81                     Integer.MAX_VALUE - 1, Integer.MAX_VALUE, Long.MAX_VALUE - 1, Long.MAX_VALUE,
  82                     Float.MAX_VALUE, Float.POSITIVE_INFINITY, Double.MAX_VALUE, Double.POSITIVE_INFINITY};
  83 
  84     static double[] doubleSpecialValues = {Double.NaN, -0.0, -0.0F, Float.NaN};
  85 
  86     static {
  87         floatTestStamps = new HashSet<>();
  88 
  89         for (double d1 : doubleBoundaryValues) {
  90             for (double d2 : doubleBoundaryValues) {
  91                 float f1 = (float) d2;
  92                 float f2 = (float) d1;
  93                 if (d2 == f1 && d1 == f2) {
  94                     generateFloatingStamps(new FloatStamp(32, Math.min(f2, f1), Math.max(f2, f1), true));
  95                     generateFloatingStamps(new FloatStamp(32, Math.min(f2, f1), Math.max(f2, f1), false));
  96                 }
  97                 generateFloatingStamps(new FloatStamp(64, Math.min(d1, d2), Math.max(d1, d2), true));
  98                 generateFloatingStamps(new FloatStamp(64, Math.min(d1, d2), Math.max(d1, d2), false));
  99             }
 100         }
 101     }
 102 
 103     private static void generateFloatingStamps(FloatStamp floatStamp) {
 104         floatTestStamps.add(floatStamp);
 105         for (double d : doubleSpecialValues) {
 106             FloatStamp newStamp = (FloatStamp) floatStamp.meet(floatStampForConstant(d, floatStamp.getBits()));
 107             if (!newStamp.isUnrestricted()) {
 108                 floatTestStamps.add(newStamp);
 109             }
 110         }
 111     }
 112 
 113     @Test
 114     public void testConvertBoundaryValues() {
 115         testConvertBoundaryValues(IntegerStamp.OPS.getSignExtend(), 32, 64, integerTestStamps);
 116         testConvertBoundaryValues(IntegerStamp.OPS.getZeroExtend(), 32, 64, integerTestStamps);
 117         testConvertBoundaryValues(IntegerStamp.OPS.getNarrow(), 64, 32, integerTestStamps);
 118     }
 119 
 120     private static void testConvertBoundaryValues(IntegerConvertOp<?> op, int inputBits, int resultBits, HashSet<PrimitiveStamp> stamps) {
 121         for (PrimitiveStamp stamp : stamps) {
 122             if (inputBits == stamp.getBits()) {
 123                 Stamp lower = boundaryStamp(stamp, false);
 124                 Stamp upper = boundaryStamp(stamp, true);
 125                 checkConvertOperation(op, inputBits, resultBits, op.foldStamp(inputBits, resultBits, stamp), lower);
 126                 checkConvertOperation(op, inputBits, resultBits, op.foldStamp(inputBits, resultBits, stamp), upper);
 127             }
 128         }
 129     }
 130 
 131     private static void checkConvertOperation(IntegerConvertOp<?> op, int inputBits, int resultBits, Stamp result, Stamp v1stamp) {
 132         Stamp folded = op.foldStamp(inputBits, resultBits, v1stamp);
 133         assertTrue(folded.asConstant() != null, "should constant fold %s %s %s", op, v1stamp, folded);
 134         assertTrue(result.meet(folded).equals(result), "result out of range %s %s %s %s %s", op, v1stamp, folded, result, result.meet(folded));
 135     }
 136 
 137     @Test
 138     public void testFloatConvertBoundaryValues() {
 139         for (FloatConvert op : EnumSet.allOf(FloatConvert.class)) {
 140             ArithmeticOpTable.FloatConvertOp floatConvert = IntegerStamp.OPS.getFloatConvert(op);
 141             if (floatConvert == null) {
 142                 continue;
 143             }
 144             assert op.getCategory() == FloatConvertCategory.IntegerToFloatingPoint : op;
 145             testConvertBoundaryValues(floatConvert, op.getInputBits(), integerTestStamps);
 146         }
 147         for (FloatConvert op : EnumSet.allOf(FloatConvert.class)) {
 148             ArithmeticOpTable.FloatConvertOp floatConvert = FloatStamp.OPS.getFloatConvert(op);
 149             if (floatConvert == null) {
 150                 continue;
 151             }
 152             assert op.getCategory() == FloatConvertCategory.FloatingPointToInteger || op.getCategory() == FloatConvertCategory.FloatingPointToFloatingPoint : op;
 153             testConvertBoundaryValues(floatConvert, op.getInputBits(), floatTestStamps);
 154         }
 155     }
 156 
 157     private static void testConvertBoundaryValues(ArithmeticOpTable.FloatConvertOp op, int bits, HashSet<PrimitiveStamp> stamps) {
 158         for (PrimitiveStamp stamp : stamps) {
 159             if (bits == stamp.getBits()) {
 160                 Stamp lower = boundaryStamp(stamp, false);
 161                 Stamp upper = boundaryStamp(stamp, true);
 162                 checkConvertOperation(op, op.foldStamp(stamp), lower);
 163                 checkConvertOperation(op, op.foldStamp(stamp), upper);
 164             }
 165         }
 166     }
 167 
 168     private static void checkConvertOperation(ArithmeticOpTable.FloatConvertOp op, Stamp result, Stamp v1stamp) {
 169         Stamp folded = op.foldStamp(v1stamp);
 170         assertTrue(folded.asConstant() != null, "should constant fold %s %s %s", op, v1stamp, folded);
 171         assertTrue(result.meet(folded).equals(result), "result out of range %s %s %s %s %s", op, v1stamp, folded, result, result.meet(folded));
 172     }
 173 
 174     @Test
 175     public void testShiftBoundaryValues() {
 176         for (ShiftOp<?> op : IntegerStamp.OPS.getShiftOps()) {
 177             testShiftBoundaryValues(op, integerTestStamps, shiftStamps);
 178         }
 179     }
 180 
 181     private static void testShiftBoundaryValues(ShiftOp<?> shiftOp, HashSet<PrimitiveStamp> stamps, HashSet<IntegerStamp> shifts) {
 182         for (PrimitiveStamp testStamp : stamps) {
 183             if (testStamp instanceof IntegerStamp) {
 184                 IntegerStamp stamp = (IntegerStamp) testStamp;
 185                 for (IntegerStamp shiftStamp : shifts) {
 186                     IntegerStamp foldedStamp = (IntegerStamp) shiftOp.foldStamp(stamp, shiftStamp);
 187                     checkShiftOperation(stamp.getBits(), shiftOp, foldedStamp, stamp.lowerBound(), shiftStamp.lowerBound());
 188                     checkShiftOperation(stamp.getBits(), shiftOp, foldedStamp, stamp.lowerBound(), shiftStamp.upperBound());
 189                     checkShiftOperation(stamp.getBits(), shiftOp, foldedStamp, stamp.upperBound(), shiftStamp.lowerBound());
 190                     checkShiftOperation(stamp.getBits(), shiftOp, foldedStamp, stamp.upperBound(), shiftStamp.upperBound());
 191                 }
 192             }
 193         }
 194     }
 195 
 196     private static void checkShiftOperation(int bits, ShiftOp<?> op, IntegerStamp result, long v1, long v2) {
 197         IntegerStamp v1stamp = IntegerStamp.create(bits, v1, v1);
 198         IntegerStamp v2stamp = IntegerStamp.create(32, v2, v2);
 199         IntegerStamp folded = (IntegerStamp) op.foldStamp(v1stamp, v2stamp);
 200         Constant constant = op.foldConstant(JavaConstant.forPrimitiveInt(bits, v1), (int) v2);
 201         assertTrue(constant != null);
 202         assertTrue(folded.asConstant() != null, "should constant fold %s %s %s %s", op, v1stamp, v2stamp, folded);
 203         assertTrue(result.meet(folded).equals(result), "result out of range %s %s %s %s %s %s", op, v1stamp, v2stamp, folded, result, result.meet(folded));
 204     }
 205 
 206     private static void checkBinaryOperation(ArithmeticOpTable.BinaryOp<?> op, Stamp result, Stamp v1stamp, Stamp v2stamp) {
 207         Stamp folded = op.foldStamp(v1stamp, v2stamp);
 208         Constant constant = op.foldConstant(v1stamp.asConstant(), v2stamp.asConstant());
 209         if (constant != null) {
 210             Constant constant2 = folded.asConstant();
 211             if (constant2 == null && v1stamp instanceof FloatStamp) {
 212                 JavaConstant c = (JavaConstant) constant;
 213                 assertTrue((c.getJavaKind() == JavaKind.Double && Double.isNaN(c.asDouble())) ||
 214                                 (c.getJavaKind() == JavaKind.Float && Float.isNaN(c.asFloat())));
 215             } else {
 216                 assertTrue(constant2 != null, "should constant fold %s %s %s %s", op, v1stamp, v2stamp, folded);
 217                 if (!constant.equals(constant2)) {
 218                     op.foldConstant(v1stamp.asConstant(), v2stamp.asConstant());
 219                     op.foldStamp(v1stamp, v2stamp);
 220                 }
 221                 assertTrue(constant.equals(constant2), "should produce same constant %s %s %s %s %s", op, v1stamp, v2stamp, constant, constant2);
 222             }
 223             assertTrue(result.meet(folded).equals(result), "result out of range %s %s %s %s %s %s", op, v1stamp, v2stamp, folded, result, result.meet(folded));
 224         }
 225     }
 226 
 227     @Test
 228     public void testBinaryBoundaryValues() {
 229         for (BinaryOp<?> op : IntegerStamp.OPS.getBinaryOps()) {
 230             if (op != null) {
 231                 testBinaryBoundaryValues(op, integerTestStamps);
 232             }
 233         }
 234         for (BinaryOp<?> op : FloatStamp.OPS.getBinaryOps()) {
 235             if (op != null) {
 236                 testBinaryBoundaryValues(op, floatTestStamps);
 237             }
 238         }
 239     }
 240 
 241     private static Stamp boundaryStamp(Stamp v1, boolean upper) {
 242         if (v1 instanceof IntegerStamp) {
 243             IntegerStamp istamp = (IntegerStamp) v1;
 244             long bound = upper ? istamp.upperBound() : istamp.lowerBound();
 245             return IntegerStamp.create(istamp.getBits(), bound, bound);
 246         } else if (v1 instanceof FloatStamp) {
 247             FloatStamp floatStamp = (FloatStamp) v1;
 248             double bound = upper ? floatStamp.upperBound() : floatStamp.lowerBound();
 249             int bits = floatStamp.getBits();
 250             return floatStampForConstant(bound, bits);
 251         } else {
 252             throw new InternalError("unexpected stamp type " + v1);
 253         }
 254     }
 255 
 256     private static FloatStamp floatStampForConstant(double bound, int bits) {
 257         if (bits == 32) {
 258             float fbound = (float) bound;
 259             return new FloatStamp(bits, fbound, fbound, !Float.isNaN(fbound));
 260         } else {
 261             return new FloatStamp(bits, bound, bound, !Double.isNaN(bound));
 262         }
 263     }
 264 
 265     private static void testBinaryBoundaryValues(ArithmeticOpTable.BinaryOp<?> op, HashSet<PrimitiveStamp> stamps) {
 266         for (PrimitiveStamp v1 : stamps) {
 267             for (PrimitiveStamp v2 : stamps) {
 268                 if (v1.getBits() == v2.getBits() && v1.getClass() == v2.getClass()) {
 269                     Stamp result = op.foldStamp(v1, v2);
 270                     Stamp v1lower = boundaryStamp(v1, false);
 271                     Stamp v1upper = boundaryStamp(v1, true);
 272                     Stamp v2lower = boundaryStamp(v2, false);
 273                     Stamp v2upper = boundaryStamp(v2, true);
 274                     checkBinaryOperation(op, result, v1lower, v2lower);
 275                     checkBinaryOperation(op, result, v1lower, v2upper);
 276                     checkBinaryOperation(op, result, v1upper, v2lower);
 277                     checkBinaryOperation(op, result, v1upper, v2upper);
 278                 }
 279             }
 280         }
 281     }
 282 
 283     @Test
 284     public void testUnaryBoundaryValues() {
 285         for (ArithmeticOpTable.UnaryOp<?> op : IntegerStamp.OPS.getUnaryOps()) {
 286             if (op != null) {
 287                 testUnaryBoundaryValues(op, integerTestStamps);
 288             }
 289         }
 290         for (ArithmeticOpTable.UnaryOp<?> op : FloatStamp.OPS.getUnaryOps()) {
 291             if (op != null) {
 292                 testUnaryBoundaryValues(op, floatTestStamps);
 293             }
 294         }
 295     }
 296 
 297     private static void testUnaryBoundaryValues(ArithmeticOpTable.UnaryOp<?> op, HashSet<PrimitiveStamp> stamps) {
 298         for (PrimitiveStamp v1 : stamps) {
 299             Stamp result = op.foldStamp(v1);
 300             checkUnaryOperation(op, result, boundaryStamp(v1, false));
 301             checkUnaryOperation(op, result, boundaryStamp(v1, true));
 302         }
 303     }
 304 
 305     private static void checkUnaryOperation(ArithmeticOpTable.UnaryOp<?> op, Stamp result, Stamp v1stamp) {
 306         Stamp folded = op.foldStamp(v1stamp);
 307         Constant v1constant = v1stamp.asConstant();
 308         if (v1constant != null) {
 309             Constant constant = op.foldConstant(v1constant);
 310             if (constant != null) {
 311                 Constant constant2 = folded.asConstant();
 312                 if (constant2 == null && v1stamp instanceof FloatStamp) {
 313                     JavaConstant c = (JavaConstant) constant;
 314                     assertTrue((c.getJavaKind() == JavaKind.Double && Double.isNaN(c.asDouble())) ||
 315                                     (c.getJavaKind() == JavaKind.Float && Float.isNaN(c.asFloat())));
 316                 } else {
 317                     assertTrue(constant2 != null, "should constant fold %s %s %s", op, v1stamp, folded);
 318                     assertTrue(constant.equals(constant2), "should produce same constant %s %s %s %s", op, v1stamp, constant, constant2);
 319                 }
 320             }
 321         } else {
 322             assert v1stamp instanceof FloatStamp;
 323         }
 324         assertTrue(result.meet(folded).equals(result), "result out of range %s %s %s %s %s", op, v1stamp, folded, result, result.meet(folded));
 325     }
 326 }