changeset 49714:f7a8524a7e9b switch

Add support for (really dumb) type switches
author briangoetz
date Fri, 06 Apr 2018 18:13:21 -0400
parents d6cbfe9e3030
children 0b4d0779f772
files src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java test/jdk/java/lang/runtime/TestSwitchBootstrap.java
diffstat 2 files changed, 115 insertions(+), 8 deletions(-) [+]
line wrap: on
line diff
--- a/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java	Fri Mar 23 14:04:22 2018 +0100
+++ b/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java	Fri Apr 06 18:13:21 2018 -0400
@@ -57,7 +57,9 @@
 public class SwitchBootstraps {
 
     // Shared INIT_HOOK for all switch call sites; looks the target method up in a map
-    private static final MethodHandle INIT_HOOK;
+    private static final MethodHandle CONSTANT_INIT_HOOK;
+    private static final MethodHandle PATTERN_INIT_HOOK;
+    private static final MethodHandle PATTERN_SWITCH_METHOD;
     private static final Map<Class<?>, MethodHandle> switchMethods = new ConcurrentHashMap<>();
 
     private static final Set<Class<?>> BOOLEAN_TYPES
@@ -89,6 +91,8 @@
                             switchClass = IntSwitchCallSite.class;
                         else if (LONG_TYPES.contains(c) || DOUBLE_TYPES.contains(c))
                             switchClass = LongSwitchCallSite.class;
+                        else if (c == Object.class)
+                            switchClass = TypeSwitchCallSite.class;
                         else
                             throw new BootstrapMethodError("Invalid switch type: " + c);
 
@@ -103,19 +107,27 @@
 
     static {
         try {
-            INIT_HOOK = LOOKUP.findStatic(SwitchBootstraps.class, "initHook",
-                                          MethodType.methodType(MethodHandle.class, CallSite.class));
+            CONSTANT_INIT_HOOK = LOOKUP.findStatic(SwitchBootstraps.class, "constantInitHook",
+                                                   MethodType.methodType(MethodHandle.class, CallSite.class));
+            PATTERN_INIT_HOOK = LOOKUP.findStatic(SwitchBootstraps.class, "patternInitHook",
+                                                  MethodType.methodType(MethodHandle.class, CallSite.class));
+            PATTERN_SWITCH_METHOD = LOOKUP.findVirtual(TypeSwitchCallSite.class, "doSwitch",
+                                                       MethodType.methodType(int.class, Object.class));
         }
         catch (ReflectiveOperationException e) {
             throw new ExceptionInInitializerError(e);
         }
     }
 
-    private static<T extends CallSite> MethodHandle initHook(T receiver) {
+    private static<T extends CallSite> MethodHandle constantInitHook(T receiver) {
         return switchMethods.computeIfAbsent(receiver.type().parameterType(0), lookupSwitchMethod)
                             .bindTo(receiver);
     }
 
+    private static<T extends CallSite> MethodHandle patternInitHook(T receiver) {
+        return PATTERN_SWITCH_METHOD.bindTo(receiver);
+    }
+
     /**
      * Bootstrap method for linking an {@code invokedynamic} call site that
      * implements a {@code switch} on a {@code boolean} or {@code Boolean}.
@@ -291,7 +303,7 @@
 
         IntSwitchCallSite(MethodType targetType,
                           int[] intLabels) throws Throwable {
-            super(targetType, INIT_HOOK);
+            super(targetType, CONSTANT_INIT_HOOK);
 
             // expensive way to index an array
             indexes = IntStream.range(0, intLabels.length)
@@ -472,7 +484,7 @@
 
         LongSwitchCallSite(MethodType targetType,
                            long[] longLabels) throws Throwable {
-            super(targetType, INIT_HOOK);
+            super(targetType, CONSTANT_INIT_HOOK);
 
             // expensive way to index an array
             indexes = IntStream.range(0, longLabels.length)
@@ -568,7 +580,7 @@
 
         StringSwitchCallSite(MethodType targetType,
                              String[] stringLabels) throws Throwable {
-            super(targetType, INIT_HOOK);
+            super(targetType, CONSTANT_INIT_HOOK);
 
             // expensive way to index an array
             indexes = IntStream.range(0, stringLabels.length)
@@ -677,7 +689,7 @@
         EnumSwitchCallSite(MethodType targetType,
                            Class<E> enumClass,
                            String... enumNames) throws Throwable {
-            super(targetType, INIT_HOOK);
+            super(targetType, CONSTANT_INIT_HOOK);
 
             ordinalMap = new int[enumClass.getEnumConstants().length];
             Arrays.fill(ordinalMap, enumNames.length);
@@ -698,4 +710,77 @@
             return (target == null) ? -1 : ordinalMap[target.ordinal()];
         }
     }
+
+    /**
+     * Bootstrap method for linking an {@code invokedynamic} call site that
+     * implements a {@code switch} on a reference-typed target.  The static
+     * arguments are a varargs array of {@code Class} labels.
+     *
+     * @param lookup Represents a lookup context with the accessibility
+     *               privileges of the caller.  When used with {@code invokedynamic},
+     *               this is stacked automatically by the VM.
+     * @param invocationName The invocation name, which is ignored.  When used with
+     *                       {@code invokedynamic}, this is provided by the
+     *                       {@code NameAndType} of the {@code InvokeDynamic}
+     *                       structure and is stacked automatically by the VM.
+     * @param invocationType The invocation type of the {@code CallSite}.  This
+     *                       method type should have a single parameter of
+     *                       a reference type, and return {@code int}.  When
+     *                       used with {@code invokedynamic}, this is provided by
+     *                       the {@code NameAndType} of the {@code InvokeDynamic}
+     *                       structure and is stacked automatically by the VM.
+     * @param types non-null {@link Class} values
+     * @return the index into {@code labels} of the target value, if the target
+     *         is an instance of any of the types, {@literal -1} if the target
+     *         value is {@code null}, or {@code types.length} if the target value
+     *         is not an instance of any of the types
+     * @throws NullPointerException if any required argument is null
+     * @throws IllegalArgumentException if any labels are null, or if the
+     * invocation type is not {@code (T)int for some reference type {@code T}}
+     * @throws Throwable if there is any error linking the call site
+     */
+    public static CallSite typeSwitch(MethodHandles.Lookup lookup,
+                                      String invocationName,
+                                      MethodType invocationType,
+                                      Class<?>... types) throws Throwable {
+        if (invocationType.parameterCount() != 1
+            || (!invocationType.returnType().equals(int.class))
+            || invocationType.parameterType(0).isPrimitive())
+            throw new IllegalArgumentException("Illegal invocation type " + invocationType);
+        requireNonNull(types);
+
+        types = types.clone();
+        if (Stream.of(types).anyMatch(Objects::isNull))
+            throw new IllegalArgumentException("null label found");
+
+        assert Stream.of(types).distinct().count() == types.length
+                : "switch labels are not distinct: " + Arrays.toString(types);
+
+        return new TypeSwitchCallSite(invocationType, types);
+    }
+
+    static class TypeSwitchCallSite extends ConstantCallSite {
+        private final Class<?>[] types;
+
+        TypeSwitchCallSite(MethodType targetType,
+                           Class<?>[] types) throws Throwable {
+            super(targetType, PATTERN_INIT_HOOK);
+            this.types = types;
+        }
+
+        int doSwitch(Object target) {
+            if (target == null)
+                return -1;
+
+            // Dumbest possible strategy
+            Class<?> targetClass = target.getClass();
+            for (int i = 0; i < types.length; i++) {
+                Class<?> c = types[i];
+                if (c.isAssignableFrom(targetClass))
+                    return i;
+            }
+
+            return types.length;
+        }
+    }
 }
--- a/test/jdk/java/lang/runtime/TestSwitchBootstrap.java	Fri Mar 23 14:04:22 2018 +0100
+++ b/test/jdk/java/lang/runtime/TestSwitchBootstrap.java	Fri Apr 06 18:13:21 2018 -0400
@@ -23,6 +23,7 @@
  * questions.
  */
 
+import java.io.Serializable;
 import java.lang.invoke.CallSite;
 import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
@@ -69,6 +70,7 @@
     public static final MethodHandle BSM_DOUBLE_SWITCH;
     public static final MethodHandle BSM_STRING_SWITCH;
     public static final MethodHandle BSM_ENUM_SWITCH;
+    public static final MethodHandle BSM_TYPE_SWITCH;
 
     private final static Random random = RandomFactory.getRandom();
 
@@ -88,6 +90,8 @@
                                                                   MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, String[].class));
             BSM_ENUM_SWITCH = MethodHandles.lookup().findStatic(SwitchBootstraps.class, "enumSwitch",
                                                                 MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, Class.class, String[].class));
+            BSM_TYPE_SWITCH = MethodHandles.lookup().findStatic(SwitchBootstraps.class, "typeSwitch",
+                                                                MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, Class[].class));
         }
         catch (NoSuchMethodException | IllegalAccessException e) {
             throw new RuntimeException(e);
@@ -441,4 +445,22 @@
             // success
         }
     }
+
+    private void testType(Object target, int result, Class... labels) throws Throwable {
+        MethodHandle indy = ((CallSite) BSM_TYPE_SWITCH.invoke(MethodHandles.lookup(), "", switchType(Object.class), labels)).dynamicInvoker();
+        assertEquals((int) indy.invoke(target), result);
+        assertEquals(-1, (int) indy.invoke(null));
+    }
+
+    public void testTypes() throws Throwable {
+        testType("", 0, String.class, Object.class);
+        testType("", 0, Object.class);
+        testType("", 1, Integer.class);
+        testType("", 1, Integer.class, Serializable.class);
+        testType(E1.A, 0, E1.class, Object.class);
+        testType(E2.C, 1, E1.class, Object.class);
+        testType(new Serializable() { }, 1, Comparable.class, Serializable.class);
+
+        // test failures: duplicates, nulls, dominance inversion
+    }
 }