changeset 53183:a0ee83b59f56 lworld

Add utility methods for substitutability tests for VM to experiment acmp for values.
author mchung
date Fri, 14 Dec 2018 11:50:21 -0800
parents a86921211088
children 2843104787a7
files src/java.base/share/classes/java/lang/invoke/ValueBootstrapMethods.java test/jdk/valhalla/valuetypes/SubstitutabilityTest.java test/jdk/valhalla/valuetypes/Value.java
diffstat 3 files changed, 561 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/src/java.base/share/classes/java/lang/invoke/ValueBootstrapMethods.java	Fri Dec 14 13:15:10 2018 -0500
+++ b/src/java.base/share/classes/java/lang/invoke/ValueBootstrapMethods.java	Fri Dec 14 11:50:21 2018 -0800
@@ -25,6 +25,7 @@
 
 package java.lang.invoke;
 
+import sun.invoke.util.Wrapper;
 import sun.security.action.GetPropertyAction;
 
 import java.lang.reflect.Modifier;
@@ -236,6 +237,7 @@
         return o1.equals(o2);
     }
 
+
     private static boolean isValue(Object obj) {
         return obj.getClass().isValue();
     }
@@ -251,4 +253,270 @@
             throw new InternalError(e);
         }
     }
+
+    static class MethodHandleBuilder {
+        static MethodHandle[] getters(Lookup lookup) {
+            Class<?> type = lookup.lookupClass().asValueType();
+            return Arrays.stream(type.getDeclaredFields())
+                         .filter(f -> !Modifier.isStatic(f.getModifiers()))
+                         .map(f -> {
+                             try {
+                                 return lookup.unreflectGetter(f);
+                             } catch (IllegalAccessException e) {
+                                 throw newLinkageError(e);
+                             }
+                         }).sorted((mh1, mh2) -> {
+                             // sort the getters with the return type
+                             Class<?> t1 = mh1.type().returnType();
+                             Class<?> t2 = mh2.type().returnType();
+                             if (t1.isPrimitive()) {
+                                 if (!t2.isPrimitive()) {
+                                     return 1;
+                                 }
+                             } else {
+                                 if (t2.isPrimitive()) {
+                                     return -1;
+                                 }
+                             }
+                             return -1;
+                         }).toArray(MethodHandle[]::new);
+        }
+
+        static MethodHandle primitiveEquals(Class<?> primitiveType) {
+            int index = Wrapper.forPrimitiveType(primitiveType).ordinal();
+            return EQUALS[index];
+        }
+
+        static MethodHandle referenceEquals(Class<?> type) {
+            return EQUALS[Wrapper.OBJECT.ordinal()].asType(methodType(boolean.class, type, type));
+        }
+
+        /*
+         * Produces a MethodHandle that returns boolean if two instances
+         * of the given interface class are substitutable.
+         *
+         * Two interface values are i== iff
+         * 1. if o1 and o2 are both reference objects then o1 r== o2; or
+         * 2. if o1 and o2 are both values then o1 v== o2
+         */
+        static MethodHandle interfaceEquals(Class<?> type) {
+            assert type.isInterface() || type == Object.class;
+            MethodType mt = methodType(boolean.class, type, type);
+            return guardWithTest(IS_SAME_VALUE_CLASS.asType(mt), VALUE_EQUALS.asType(mt), referenceEquals(type));
+            // return INTERFACE_EQUALS.asType(methodType(boolean.class, type, type));
+        }
+
+        /*
+         * Produces a MethodHandle that returns boolean if two value instances
+         * of the given value class are substitutable.
+         */
+        static MethodHandle valueEquals(Class<?> c) {
+            assert c.isValue();
+            Class<?> type = c.asValueType();
+            MethodHandles.Lookup lookup = new MethodHandles.Lookup(type);
+            MethodHandle[] getters = getters(lookup);
+
+            MethodHandle instanceFalse = dropArguments(FALSE, 0, type, Object.class)
+                .asType(methodType(boolean.class, type, type));
+            MethodHandle accumulator = dropArguments(TRUE, 0, type, type);
+            for (MethodHandle getter : getters) {
+                MethodHandle eq = substitutableInvoker(getter.type().returnType());
+                MethodHandle thisFieldEqual = filterArguments(eq, 0, getter, getter);
+                accumulator = guardWithTest(thisFieldEqual, accumulator, instanceFalse);
+            }
+            return accumulator;
+        }
+
+        private static boolean eq(byte a, byte b)       { return a == b; }
+        private static boolean eq(short a, short b)     { return a == b; }
+        private static boolean eq(char a, char b)       { return a == b; }
+        private static boolean eq(int a, int b)         { return a == b; }
+        private static boolean eq(long a, long b)       { return a == b; }
+        private static boolean eq(float a, float b)     { return Float.compare(a, b) == 0; }
+        private static boolean eq(double a, double b)   { return Double.compare(a, b) == 0; }
+        private static boolean eq(boolean a, boolean b) { return a == b; }
+        private static boolean eq(Object a, Object b)   { return a == b; }
+
+        private static boolean isSameValueClass(Object a, Object b) {
+            return (a != null && b != null
+                    && a.getClass().asBoxType() == b.getClass().asBoxType()
+                    && a.getClass().isValue());
+        }
+
+        private static boolean valueEq(Object a, Object b) {
+            assert isSameValueClass(a, b);
+            try {
+                Class<?> type = a.getClass().asValueType();
+                return (boolean) valueEquals(type).invoke(type.cast(a), type.cast(b));
+            } catch (Throwable e) {
+                throw new InternalError(e);
+            }
+        }
+
+        static final MethodHandle[] EQUALS = initEquals();
+        static final MethodHandle IS_SAME_VALUE_CLASS =
+            findStatic("isSameValueClass", methodType(boolean.class, Object.class, Object.class));
+        static final MethodHandle VALUE_EQUALS =
+            findStatic("valueEq", methodType(boolean.class, Object.class, Object.class));
+        static final MethodHandle FALSE = constant(boolean.class, false);
+        static final MethodHandle TRUE = constant(boolean.class, true);
+        static MethodHandle[] initEquals() {
+            MethodHandle[] mhs = new MethodHandle[Wrapper.COUNT];
+            for (Wrapper wrapper : Wrapper.values()) {
+                if (wrapper == Wrapper.VOID) continue;
+
+                Class<?> type = wrapper.primitiveType();
+                mhs[wrapper.ordinal()] = findStatic("eq", methodType(boolean.class, type, type));
+            }
+            return mhs;
+        }
+
+        private static MethodHandle findStatic(String name, MethodType methodType) {
+            try {
+                return IMPL_LOOKUP.findStatic(MethodHandleBuilder.class, name, methodType);
+            } catch (NoSuchMethodException|IllegalAccessException e) {
+                throw newLinkageError(e);
+            }
+        }
+    }
+
+    private static LinkageError newLinkageError(Throwable e) {
+        return (LinkageError) new LinkageError().initCause(e);
+    }
+
+    /**
+     * Returns {@code true} if the arguments are <em>substitutable</em> to each
+     * other and {@code false} otherwise.
+     * <em>Substitutability</em> means that they cannot be distinguished from
+     * each other in any data-dependent way, meaning that it is safe to substitute
+     * one for the other.
+     *
+     * <ul>
+     * <li>If {@code a} and {@code b} are both {@code null}, this method returns
+     *     {@code true}.
+     * <li>If {@code a} and {@code b} are both value instances of the same class
+     *     {@code V}, this method returns {@code true} if, for all fields {@code f}
+     *      declared in {@code V}, {@code a.f} and {@code b.f} are substitutable.
+     * <li>If {@code a} and {@code b} are both primitives of the same type,
+     *     this method returns {@code a == b} with the following exception:
+     *     <ul>
+     *     <li> If {@code a} and {@code b} both represent {@code NaN},
+     *          this method returns {@code true}, even though {@code NaN == NaN}
+     *          has the value {@code false}.
+     *     <li> If {@code a} is floating point positive zero while {@code b} is
+     *          floating point negative zero, or vice versa, this method
+     *          returns {@code false}, even though {@code +0.0 == -0.0} has
+     *          the value {@code true}.
+     *     </ul>
+     * <li>If {@code a} and {@code b} are both instances of the same reference type,
+     *     this method returns {@code a == b}.
+     * <li>Otherwise this method returns {@code false}.
+     * </ul>
+     *
+     * <p>For example,
+     * <pre>{@code interface Number { ... }
+     * // ordinary reference class
+     * class IntNumber implements Number { ... }
+     * // value class
+     * value class IntValue implements Number {
+     *     int i;
+     *     :
+     *     public static IntValue of(int i) {...}     // IntValue::of creates a new value instance
+     * }
+     * // value class with an Object field
+     * value class RefValue {
+     *     Object o;
+     *     :
+     * }
+     *
+     * var val1 = IntValue.of(10);
+     * var val2 = IntValue.of(10);                    // val1 and val2 have the same value
+     * var ref1 = new IntNumber(10);                  // ref1 and ref2 are two reference instances
+     * var ref2 = new IntNumber(10);
+     * assertTrue(isSubstitutable(val1, val2));       // val1 and val2 are both value instances of IntValue
+     * assertFalse(isSubstitutable(ref1, ref2));      // ref1 and ref2 are two reference instances that are not substitutable
+     * assertTrue(isSubstitutable(ref1, ref1));       // a reference instance is substitutable with itself
+     *
+     * var rval1 = RefValue.of(List.of("list"));      // rval1.o and rval2.o both contain a list of one-single element "list"
+     * var rval2 = RefValue.of(List.of("list");
+     * var rval3 = RefValue.of(rval1.o);
+     *
+     * assertFalse(isSubstitutable(rval1, rval2));    // rval1.o and rval2.o are two different List instances and hence not substitutable
+     * assertTrue(isSubstitutable(rval1, rval3 ));    // rval1.o and rval3.o are the same reference instance
+     * }</pre>
+     *
+     * @apiNote
+     * This API is intended for performance evaluation of this idiom for
+     * {@code acmp}.  Hence it is not in the {@link System} class.
+     *
+     * @param a an object
+     * @param b an object to be compared with {@code a} for substitutability
+     * @return {@code true} if the arguments are substitutable to each other;
+     *         {@code false} otherwise.
+     * @param <T> type
+     * @see Float#equals(Object)
+     * @see Double#equals(Object)
+     */
+    public static <T> boolean isSubstitutable(T a, T b) {
+        if (a == b) return true;
+        if (a == null || b == null) return false;
+
+        try {
+            Class<?> type = a.getClass().isValue() ? a.getClass().asValueType() : a.getClass();
+            return (boolean) substitutableInvoker(type).invoke(a, b);
+        } catch (Throwable e) {
+            if (VERBOSE) e.printStackTrace();
+            throw new InternalError(e);
+        }
+    }
+
+    /**
+     * Produces a method handle which tests if two arguments are
+     * {@linkplain #isSubstitutable(Object, Object) substitutable}.
+     *
+     * <ul>
+     * <li>If {@code T} is a non-floating point primitive type, this method
+     *     returns a method handle testing the two arguments are the same value,
+     *     i.e. {@code a == b}.
+     * <li>If {@code T} is {@code float} or {@code double}, this method
+     *     returns a method handle representing {@link Float#equals(Object)} or
+     *     {@link Double#equals(Object)} respectively.
+     * <li>If {@code T} is a reference type that is not {@code Object} and not an
+     *     interface, this method returns a method handle testing
+     *     the two arguments are the same reference, i.e. {@code a == b}.
+     * <li>If {@code T} is a value type, this method returns
+     *     a method handle that returns {@code true} if
+     *     for all fields {@code f} declared in {@code T}, where {@code U} is
+     *     the type of {@code f}, if {@code a.f} and {@code b.f} are substitutable
+     *     with respect to {@code U}.
+     * <li>If {@code T} is an interface or {@code Object}, and
+     *     {@code a} and {@code b} are of the same value class {@code V},
+     *     this method returns a method handle that returns {@code true} if
+     *     {@code a} and {@code b} are substitutable with respect to {@code V}.
+     * </ul>
+     *
+     * @param type class type
+     * @param <T> type
+     * @return a method handle for substitutability test
+     */
+    static <T> MethodHandle substitutableInvoker(Class<T> type) {
+        if (type.isPrimitive())
+            return MethodHandleBuilder.primitiveEquals(type);
+
+        if (type.isInterface() || type == Object.class)
+            return MethodHandleBuilder.interfaceEquals(type);
+
+        if (type.isValue())
+            return SUBST_TEST_METHOD_HANDLES.get(type.asValueType());
+
+        return MethodHandleBuilder.referenceEquals(type);
+    }
+
+    // store the method handle for value types in ClassValue
+    private static ClassValue<MethodHandle> SUBST_TEST_METHOD_HANDLES = new ClassValue<>() {
+        @Override protected MethodHandle computeValue(Class<?> c) {
+            return MethodHandleBuilder.valueEquals(c);
+        }
+    };
+
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/valhalla/valuetypes/SubstitutabilityTest.java	Fri Dec 14 11:50:21 2018 -0800
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @summary test MethodHandle/VarHandle on value types
+ * @compile -XDallowWithFieldOperator Point.java Line.java Value.java MutablePath.java MixedValues.java
+ * @run testng/othervm -XX:+EnableValhalla SubstitutabilityTest
+ */
+
+import java.lang.invoke.ValueBootstrapMethods;
+import java.util.List;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+import static org.testng.Assert.*;
+
+public class SubstitutabilityTest {
+    @DataProvider(name="substitutable")
+    Object[][] substitutableCases() {
+        Point p1 = Point.makePoint(10, 10);
+        Point p2 = Point.makePoint(20, 20);
+        Point.box box1 = p1;
+        Point.box box2 = p2;
+        Line l1 = Line.makeLine(p1, p2);
+        var mpath = MutablePath.makePath(10, 20, 30, 40);
+        var mixedValues = new MixedValues(p1, l1, mpath, "value");
+        var number = Value.Number.intValue(99);
+        var list = List.of("list");
+        return new Object[][] {
+            new Object[] { p1, Point.makePoint(10, 10) },
+            new Object[] { l1, Line.makeLine(10,10, 20,20) },
+            new Object[] { box1, Point.makePoint(10, 10) },
+            new Object[] { mpath, mpath},
+            new Object[] { mixedValues, mixedValues},
+            new Object[] { valueBuilder().setPoint(p1).build(),
+                           valueBuilder().setPoint(Point.makePoint(10, 10)).build() },
+            new Object[] { valueBuilder().setFloat(Float.NaN).setDouble(Double.NaN).setPoint(p1).build(),
+                           valueBuilder().setFloat(Float.NaN).setDouble(Double.NaN).setPoint(l1.p1).build() },
+            new Object[] { valueBuilder().setFloat(Float.NaN).setDouble(Double.NaN).setNumber(number).build(),
+                           valueBuilder().setFloat(Float.NaN).setDouble(Double.NaN).setNumber(Value.Number.intValue(99)).build() },
+            new Object[] { valueBuilder().setFloat(+0.0f).setDouble(+0.0).setReference(list).build(),
+                           valueBuilder().setFloat(+0.0f).setDouble(+0.0).setReference(list).build() },
+            new Object[] { valueBuilder().setNumber(Value.Number.intValue(100)).build(),
+                           valueBuilder().setNumber(Value.Number.intValue(100)).build() },
+            new Object[] { valueBuilder().setReference(list).build(),
+                           valueBuilder().setReference(list).build() },
+        };
+    }
+
+    @Test(dataProvider="substitutable")
+    public void substitutableTest(Object a, Object b) {
+        assertTrue(ValueBootstrapMethods.isSubstitutable(a, b));
+    }
+
+    @DataProvider(name="notSubstitutable")
+    Object[][] notSubstitutableCases() {
+        var point = Point.makePoint(10, 10);
+        var mpath = MutablePath.makePath(10, 20, 30, 40);
+        var number = Value.Number.intValue(99);
+        return new Object[][] {
+            new Object[] { Point.makePoint(10, 10), Point.makePoint(10, 20)},
+            new Object[] { mpath, MutablePath.makePath(10, 20, 30, 40)},
+            new Object[] { valueBuilder().setFloat(+0.0f).setDouble(+0.0).build(),
+                           valueBuilder().setFloat(-0.0f).setDouble(+0.0).build() },
+            new Object[] { valueBuilder().setFloat(+0.0f).setDouble(+0.0).build(),
+                           valueBuilder().setFloat(+0.0f).setDouble(-0.0).build() },
+            new Object[] { valueBuilder().setPoint(point).build(),
+                           valueBuilder().setPoint(Point.makePoint(20, 20)).build() },
+            new Object[] { valueBuilder().setNumber(number).build(),
+                           valueBuilder().setNumber(new Value.IntNumber(99)).build() },
+            new Object[] { valueBuilder().setNumber(Value.Number.intValue(1)).build(),
+                           valueBuilder().setNumber(Value.Number.shortValue((short)1)).build() },
+            new Object[] { valueBuilder().setNumber(new Value.IntNumber(99)).build(),
+                           valueBuilder().setNumber(new Value.IntNumber(99)).build() },
+            new Object[] { valueBuilder().setReference(List.of("list")).build(),
+                           valueBuilder().setReference(List.of("list")).build() },
+        };
+    }
+    @Test(dataProvider="notSubstitutable")
+    public void notSubstitutableTest(Object a, Object b) {
+        assertFalse(ValueBootstrapMethods.isSubstitutable(a, b));
+    }
+    private static Value.Builder valueBuilder() {
+        Value.Builder builder = new Value.Builder();
+        return builder.setChar('a')
+                       .setBoolean(true)
+                       .setByte((byte)0x1)
+                       .setShort((short)3)
+                       .setLong(4L);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/valhalla/valuetypes/Value.java	Fri Dec 14 11:50:21 2018 -0800
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+public value class Value {
+    char char_v;
+    byte byte_v;
+    boolean boolean_v;
+    int int_v;
+    short short_v;
+    long long_v;
+    double double_v;
+    float float_v;
+    Number number_v;
+    Point point_v;
+    Object ref_v;
+
+    Value() {
+        char_v = 'z';
+        boolean_v = true;
+        byte_v = 0;
+        int_v = 1;
+        short_v = 2;
+        long_v = 3;
+        float_v = 0.1f;
+        double_v = 0.2d;
+        point_v = Point.makePoint(0,0);
+        number_v = null;
+        ref_v = null;
+    }
+    static Value makeValue(char c, boolean z, byte b, int x, short y, long l, float f, double d, Number number, Point p, Object o) {
+        Value v = Value.default;
+        v = __WithField(v.char_v, c);
+        v = __WithField(v.byte_v, b);
+        v = __WithField(v.boolean_v, z);
+        v = __WithField(v.int_v, x);
+        v = __WithField(v.short_v, y);
+        v = __WithField(v.long_v, l);
+        v = __WithField(v.float_v, f);
+        v = __WithField(v.double_v, d);
+        v = __WithField(v.number_v, number);
+        v = __WithField(v.point_v, p);
+        v = __WithField(v.ref_v, o);
+        return v;
+    }
+
+    static class Builder {
+        private static final Object REF = new Object();
+        private char c;
+        private byte b;
+        private boolean z;
+        private int i;
+        private short s;
+        private long l;
+        private double d;
+        private float f;
+        private Number n = Number.intValue(0);
+        private Point p = Point.makePoint(0,0);
+        private Object ref = REF;
+
+        public Builder() {}
+        Builder setChar(char c) {
+            this.c = c;
+            return this;
+        }
+        Builder setByte(byte b) {
+            this.b = b;
+            return this;
+        }
+        Builder setBoolean(boolean z) {
+            this.z = z;
+            return this;
+        }
+        Builder setInt(int i) {
+            this.i = i;
+            return this;
+        }
+        Builder setShort(short s) {
+            this.s = s;
+            return this;
+        }
+        Builder setLong(long l) {
+            this.l = l;
+            return this;
+        }
+        Builder setDouble(double d) {
+            this.d = d;
+            return this;
+        }
+        Builder setFloat(float f) {
+            this.f = f;
+            return this;
+        }
+        Builder setNumber(Number n) {
+            this.n = n;
+            return this;
+        }
+        Builder setPoint(Point p) {
+            this.p = p;
+            return this;
+        }
+        Builder setReference(Object o) {
+            this.ref = o;
+            return this;
+        }
+        Value build() {
+            return Value.makeValue(c, z, b, i, s, l, f, d, n, p, ref);
+        }
+    }
+
+    interface Number {
+        default int intValue() {
+            throw new UnsupportedOperationException();
+        }
+        default short shortValue() {
+            throw new UnsupportedOperationException();
+        }
+
+        static IntValue intValue(int i) {
+            IntValue v = IntValue.default;
+            v = __WithField(v.i, i);
+            return v;
+        }
+
+        static ShortValue shortValue(short s) {
+            ShortValue v = ShortValue.default;
+            v = __WithField(v.s, s);
+            return v;
+        }
+    }
+
+    static value class IntValue implements Number {
+        int i;
+        IntValue() {
+            i = 0;
+        }
+        public int intValue() {
+            return i;
+        }
+    }
+
+    static value class ShortValue implements Number {
+        short s;
+        ShortValue() {
+            s = 0;
+        }
+        public short shortValue() {
+            return s;
+        }
+    }
+
+    static class IntNumber implements Number {
+        final int i;
+        public IntNumber(int i) {
+            this.i = i;
+        }
+
+        public int intValue() {
+            return i;
+        }
+    }
+}