changeset 52926:b1d6664ce434 datum

Add bootstraps for switch, including pattern switch
author briangoetz
date Mon, 29 Oct 2018 17:34:03 -0400
parents 89969d71c998
children b49697bf259d ee2b4eb8a6e7
files src/java.base/share/classes/java/lang/compiler/Extractor.java src/java.base/share/classes/java/lang/compiler/SwitchBootstraps.java test/jdk/java/lang/compiler/ExtractorTest.java test/jdk/java/lang/compiler/RecordTest.java test/jdk/java/lang/compiler/SwitchBootstrapsTest.java
diffstat 5 files changed, 1503 insertions(+), 27 deletions(-) [+]
line wrap: on
line diff
--- a/src/java.base/share/classes/java/lang/compiler/Extractor.java	Mon Oct 29 16:25:13 2018 -0400
+++ b/src/java.base/share/classes/java/lang/compiler/Extractor.java	Mon Oct 29 17:34:03 2018 -0400
@@ -51,6 +51,11 @@
  * argument types of the descriptor indicate the types of the output binding
  * variables.
  *
+ * Notes:
+ *  - totality is erased;
+ *  - compilers expected to optimize away total type patterns;
+ *  - adaptation done in nest() and switch combinators
+ *
  * @author Brian Goetz
  */
 public interface Extractor {
@@ -309,7 +314,7 @@
      *                   for this outer binding is desired
      * @return the nested extractor
      */
-    public static Extractor nested(Extractor outer, Extractor... extractors) {
+    public static Extractor ofNested(Extractor outer, Extractor... extractors) {
         int outerCount = outer.descriptor().parameterCount();
         Class<?> outerCarrierType = outer.tryMatch().type().returnType();
 
@@ -415,6 +420,49 @@
     }
 
     /**
+     * Condy bootstrap for creating nested extractors
+     *
+     * @param lookup ignored
+     * @param invocationName ignored
+     * @param invocationType ignored
+     * @param outer the outer extractor
+     * @param inners the inner extractors, null if no nesting is needed for this binding
+     * @return the nested extractor
+     */
+    public static Extractor ofNested(MethodHandles.Lookup lookup, String invocationName, MethodType invocationType,
+                                     Extractor outer, Extractor... inners) {
+        return ofNested(outer, inners);
+    }
+
+    /**
+     * Condy bootstrap for creating non-nullable type extractor
+     *
+     * @param lookup ignored
+     * @param invocationName ignored
+     * @param invocationType ignored
+     * @param type the type
+     * @return the extractor
+     */
+    public static Extractor ofType(MethodHandles.Lookup lookup, String invocationName, MethodType invocationType,
+                                   Class<?> type) {
+        return ofType(type);
+    }
+
+    /**
+     * Condy bootstrap for creating nullable type extractor
+     *
+     * @param lookup ignored
+     * @param invocationName ignored
+     * @param invocationType ignored
+     * @param type the type
+     * @return the extractor
+     */
+    public static Extractor ofTypeNullable(MethodHandles.Lookup lookup, String invocationName, MethodType invocationType,
+                                           Class<?> type) {
+        return ofTypeNullable(type);
+    }
+
+    /**
      * Condy bootstrap for finding extractors
      *
      * @param lookup the lookup context
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/java/lang/compiler/SwitchBootstraps.java	Mon Oct 29 17:34:03 2018 -0400
@@ -0,0 +1,882 @@
+/*
+ * Copyright (c) 2017, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package java.lang.compiler;
+
+import java.lang.invoke.CallSite;
+import java.lang.invoke.ConstantCallSite;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+import java.util.stream.IntStream;
+import java.util.stream.LongStream;
+import java.util.stream.Stream;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Bootstrap methods for linking {@code invokedynamic} call sites that implement
+ * the selection functionality of the {@code switch} statement.  The bootstraps
+ * take additional static arguments corresponding to the {@code case} labels
+ * of the {@code switch}, implicitly numbered sequentially from {@code [0..N)}.
+ *
+ * <p>The bootstrap call site accepts a single parameter of the type of the
+ * operand of the {@code switch}, and return an {@code int} that is the index of
+ * the matched {@code case} label, {@code -1} if the target is {@code null},
+ * or {@code N} if the target is not null but matches no {@code case} label.
+ */
+public class SwitchBootstraps {
+
+    // Shared INIT_HOOK for all switch call sites; looks the target method up in a map
+    private static final MethodHandle CONSTANT_INIT_HOOK;
+    private static final MethodHandle PATTERN_INIT_HOOK;
+    private static final MethodHandle TYPE_INIT_HOOK;
+    private static final MethodHandle PATTERN_SWITCH_METHOD;
+    private static final MethodHandle TYPE_SWITCH_METHOD;
+    private static final Map<Class<?>, MethodHandle> switchMethods = new ConcurrentHashMap<>();
+
+    private static final Set<Class<?>> BOOLEAN_TYPES
+            = Set.of(boolean.class, Boolean.class);
+    // Types that can be handled as int switches
+    private static final Set<Class<?>> INT_TYPES
+            = Set.of(int.class, short.class, byte.class, char.class,
+                     Integer.class, Short.class, Byte.class, Character.class);
+    private static final Set<Class<?>> FLOAT_TYPES
+            = Set.of(float.class, Float.class);
+    private static final Set<Class<?>> LONG_TYPES
+            = Set.of(long.class, Long.class);
+    private static final Set<Class<?>> DOUBLE_TYPES
+            = Set.of(double.class, Double.class);
+
+    private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
+    private static final Function<Class<?>, MethodHandle> lookupSwitchMethod =
+            new Function<>() {
+                @Override
+                public MethodHandle apply(Class<?> c) {
+                    try {
+                        Class<?> switchClass;
+                        if (c == Enum.class)
+                            switchClass = EnumSwitchCallSite.class;
+                        else if (c == String.class)
+                            switchClass = StringSwitchCallSite.class;
+                        else if (BOOLEAN_TYPES.contains(c) || INT_TYPES.contains(c) ||
+                                 FLOAT_TYPES.contains(c))
+                            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);
+
+                        return LOOKUP.findVirtual(switchClass, "doSwitch",
+                                                  MethodType.methodType(int.class, c));
+                    }
+                    catch (ReflectiveOperationException e) {
+                        throw new BootstrapMethodError("Invalid switch type: " + c);
+                    }
+                }
+            };
+
+    static {
+        try {
+            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));
+            TYPE_INIT_HOOK = LOOKUP.findStatic(SwitchBootstraps.class, "typeInitHook",
+                                                  MethodType.methodType(MethodHandle.class, CallSite.class));
+            PATTERN_SWITCH_METHOD = LOOKUP.findVirtual(PatternSwitchCallSite.class, "doSwitch",
+                                                       MethodType.methodType(PatternSwitchResult.class, Object.class));
+            TYPE_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 constantInitHook(T receiver) {
+        return switchMethods.computeIfAbsent(receiver.type().parameterType(0), lookupSwitchMethod)
+                            .bindTo(receiver);
+    }
+
+    private static<T extends CallSite> MethodHandle typeInitHook(T receiver) {
+        return TYPE_SWITCH_METHOD.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}.
+     * The static arguments are a varargs array of {@code boolean} labels,
+     *
+     * <p>The results are undefined if the labels array contains duplicates.
+     *
+     * @implNote
+     *
+     * The implementation only enforces the requirement that the labels array
+     * be duplicate-free if system assertions are enabled.
+     *
+     * @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 which is
+     *                       {@code boolean} or {@code Boolean},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 booleanLabels boolean values corresponding to the case labels of the
+     *                  {@code switch} statement.
+     * @return the index into {@code booleanLabels} of the target value, if the target
+     *         matches any of the labels, {@literal -1} if the target value is
+     *         {@code null}, or {@code booleanLabels.length} if the target value does
+     *         not match any of the labels.
+     * @throws NullPointerException if any required argument is null
+     * @throws IllegalArgumentException if the invocation type is not
+     * {@code (boolean)int} or {@code (Boolean)int}
+     * @throws Throwable if there is any error linking the call site
+     */
+    public static CallSite booleanSwitch(MethodHandles.Lookup lookup,
+                                         String invocationName,
+                                         MethodType invocationType,
+                                         boolean... booleanLabels) throws Throwable {
+        if (invocationType.parameterCount() != 1
+            || (!invocationType.returnType().equals(int.class))
+            || (!BOOLEAN_TYPES.contains(invocationType.parameterType(0))))
+            throw new IllegalArgumentException("Illegal invocation type " + invocationType);
+        requireNonNull(booleanLabels);
+
+        int[] intLabels = IntStream.range(0, booleanLabels.length)
+                                   .map(i -> booleanLabels[i] ? 1 : 0)
+                                   .toArray();
+
+        assert IntStream.of(intLabels).distinct().count() == intLabels.length
+                : "switch labels are not distinct: " + Arrays.toString(booleanLabels);
+
+        return new IntSwitchCallSite(invocationType, intLabels);
+    }
+
+    /**
+     * Bootstrap method for linking an {@code invokedynamic} call site that
+     * implements a {@code switch} on an {@code int}, {@code short}, {@code byte},
+     * {@code char}, or one of their box types.  The static arguments are a
+     * varargs array of {@code int} labels.
+     *
+     * <p>The results are undefined if the labels array contains duplicates.
+     *
+     * @implNote
+     *
+     * The implementation only enforces the requirement that the labels array
+     * be duplicate-free if system assertions are enabled.
+     *
+     * @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 which is
+     *                       one of the 32-bit or shorter primitive types, or
+     *                       one of their box types, 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 intLabels integral values corresponding to the case labels of the
+     *                  {@code switch} statement.
+     * @return the index into {@code intLabels} of the target value, if the target
+     *         matches any of the labels, {@literal -1} if the target value is
+     *         {@code null}, or {@code intLabels.length} if the target value does
+     *         not match any of the labels.
+     * @throws NullPointerException if any required argument is null
+     * @throws IllegalArgumentException if the invocation type is not
+     * {@code (T)int}, where {@code T} is one of the 32-bit or smaller integral
+     * primitive types, or one of their box types
+     * @throws Throwable if there is any error linking the call site
+     */
+    public static CallSite intSwitch(MethodHandles.Lookup lookup,
+                                     String invocationName,
+                                     MethodType invocationType,
+                                     int... intLabels) throws Throwable {
+        if (invocationType.parameterCount() != 1
+            || (!invocationType.returnType().equals(int.class))
+            || (!INT_TYPES.contains(invocationType.parameterType(0))))
+            throw new IllegalArgumentException("Illegal invocation type " + invocationType);
+        requireNonNull(intLabels);
+
+        assert IntStream.of(intLabels).distinct().count() == intLabels.length
+                : "switch labels are not distinct: " + Arrays.toString(intLabels);
+
+        return new IntSwitchCallSite(invocationType, intLabels);
+    }
+
+    /**
+     * Bootstrap method for linking an {@code invokedynamic} call site that
+     * implements a {@code switch} on an {@code float} or {@code Float}.
+     * The static arguments are a varargs array of {@code float} labels.
+     *
+     * <p>The results are undefined if the labels array contains duplicates
+     * according to {@link Float#floatToIntBits(float)}.
+     *
+     * @implNote
+     *
+     * The implementation only enforces the requirement that the labels array
+     * be duplicate-free if system assertions are enabled.
+     *
+     * @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 which is
+     *                       one of the 32-bit or shorter primitive types, or
+     *                       one of their box types, 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 floatLabels float values corresponding to the case labels of the
+     *                    {@code switch} statement.
+     * @return the index into {@code floatLabels} of the target value, if the target
+     *         matches any of the labels, {@literal -1} if the target value is
+     *         {@code null}, or {@code floatLabels.length} if the target value does
+     *         not match any of the labels.
+     * @throws NullPointerException if any required argument is null
+     * @throws IllegalArgumentException if the invocation type is not
+     * {@code (float)int} or {@code (Float)int}
+     * @throws Throwable if there is any error linking the call site
+     */
+    public static CallSite floatSwitch(MethodHandles.Lookup lookup,
+                                       String invocationName,
+                                       MethodType invocationType,
+                                       float... floatLabels) throws Throwable {
+        if (invocationType.parameterCount() != 1
+            || (!invocationType.returnType().equals(int.class))
+            || (!FLOAT_TYPES.contains(invocationType.parameterType(0))))
+            throw new IllegalArgumentException("Illegal invocation type " + invocationType);
+        requireNonNull(floatLabels);
+
+        int[] intLabels = new int[floatLabels.length];
+        for (int i=0; i<floatLabels.length; i++)
+            intLabels[i] = Float.floatToIntBits(floatLabels[i]);
+
+        assert IntStream.of(intLabels).distinct().count() == intLabels.length
+                : "switch labels are not distinct: " + Arrays.toString(floatLabels);
+
+        return new IntSwitchCallSite(invocationType, intLabels);
+    }
+
+    static class IntSwitchCallSite extends ConstantCallSite {
+        private final int[] labels;
+        private final int[] indexes;
+
+        IntSwitchCallSite(MethodType targetType,
+                          int[] intLabels) throws Throwable {
+            super(targetType, CONSTANT_INIT_HOOK);
+
+            // expensive way to index an array
+            indexes = IntStream.range(0, intLabels.length)
+                               .boxed()
+                               .sorted(Comparator.comparingInt(a -> intLabels[a]))
+                               .mapToInt(Integer::intValue)
+                               .toArray();
+            labels = new int[indexes.length];
+            for (int i=0; i<indexes.length; i++)
+                labels[i] = intLabels[indexes[i]];
+        }
+
+        int doSwitch(int target) {
+            int index = Arrays.binarySearch(labels, target);
+            return (index >= 0) ? indexes[index] : indexes.length;
+        }
+
+        int doSwitch(boolean target) {
+            return doSwitch(target ? 1 : 0);
+        }
+
+        int doSwitch(float target) {
+            return doSwitch(Float.floatToIntBits(target));
+        }
+
+        int doSwitch(short target) {
+            return doSwitch((int) target);
+        }
+
+        int doSwitch(byte target) {
+            return doSwitch((int) target);
+        }
+
+        int doSwitch(char target) {
+            return doSwitch((int) target);
+        }
+
+        int doSwitch(Boolean target) {
+            return (target == null) ? -1 : doSwitch((boolean) target);
+        }
+
+        int doSwitch(Integer target) {
+            return (target == null) ? -1 : doSwitch((int) target);
+        }
+
+        int doSwitch(Float target) {
+            return (target == null) ? -1 : doSwitch((float) target);
+        }
+
+        int doSwitch(Short target) {
+            return (target == null) ? -1 : doSwitch((int) target);
+        }
+
+        int doSwitch(Character target) {
+            return (target == null) ? -1 : doSwitch((int) target);
+        }
+
+        int doSwitch(Byte target) {
+            return (target == null) ? -1 : doSwitch((int) target);
+        }
+    }
+
+    /**
+     * Bootstrap method for linking an {@code invokedynamic} call site that
+     * implements a {@code switch} on a {@code long} or {@code Long}.
+     * The static arguments are a varargs array of {@code long} labels.
+     *
+     * <p>The results are undefined if the labels array contains duplicates.
+     *
+     * @implNote
+     *
+     * The implementation only enforces the requirement that the labels array
+     * be duplicate-free if system assertions are enabled.
+     *
+     * @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 which is
+     *                       one of the 32-bit or shorter primitive types, or
+     *                       one of their box types, 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 longLabels long values corresponding to the case labels of the
+     *                  {@code switch} statement.
+     * @return the index into {@code longLabels} of the target value, if the target
+     *         matches any of the labels, {@literal -1} if the target value is
+     *         {@code null}, or {@code longLabels.length} if the target value does
+     *         not match any of the labels.
+     * @throws NullPointerException if any required argument is null
+     * @throws IllegalArgumentException if the invocation type is not
+     * {@code (long)int} or {@code (Long)int}
+     * @throws Throwable if there is any error linking the call site
+     */
+    public static CallSite longSwitch(MethodHandles.Lookup lookup,
+                                      String invocationName,
+                                      MethodType invocationType,
+                                      long... longLabels) throws Throwable {
+        if (invocationType.parameterCount() != 1
+            || (!invocationType.returnType().equals(int.class))
+            || (!LONG_TYPES.contains(invocationType.parameterType(0))))
+            throw new IllegalArgumentException("Illegal invocation type " + invocationType);
+        requireNonNull(longLabels);
+
+        assert LongStream.of(longLabels).distinct().count() == longLabels.length
+                : "switch labels are not distinct: " + Arrays.toString(longLabels);
+
+        return new LongSwitchCallSite(invocationType, longLabels);
+    }
+
+    /**
+     * Bootstrap method for linking an {@code invokedynamic} call site that
+     * implements a {@code switch} on a {@code double} or {@code Double}.
+     * The static arguments are a varargs array of {@code double} labels.
+     *
+     * <p>The results are undefined if the labels array contains duplicates
+     * according to {@link Double#doubleToLongBits(double)}.
+     *
+     * @implNote
+     *
+     * The implementation only enforces the requirement that the labels array
+     * be duplicate-free if system assertions are enabled.
+     *
+     * @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 which is
+     *                       one of the 32-bit or shorter primitive types, or
+     *                       one of their box types, 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 doubleLabels long values corresponding to the case labels of the
+     *                  {@code switch} statement.
+     * @return the index into {@code doubleLabels} of the target value, if the target
+     *         matches any of the labels, {@literal -1} if the target value is
+     *         {@code null}, or {@code doubleLabels.length} if the target value does
+     *         not match any of the labels.
+     * @throws NullPointerException if any required argument is null
+     * @throws IllegalArgumentException if the invocation type is not
+     * {@code (double)int} or {@code (Double)int}
+     * @throws Throwable if there is any error linking the call site
+     */
+    public static CallSite doubleSwitch(MethodHandles.Lookup lookup,
+                                        String invocationName,
+                                        MethodType invocationType,
+                                        double... doubleLabels) throws Throwable {
+        if (invocationType.parameterCount() != 1
+            || (!invocationType.returnType().equals(int.class))
+            || (!DOUBLE_TYPES.contains(invocationType.parameterType(0))))
+            throw new IllegalArgumentException("Illegal invocation type " + invocationType);
+        requireNonNull(doubleLabels);
+
+        long[] longLabels = new long[doubleLabels.length];
+        for (int i=0; i<doubleLabels.length; i++)
+            longLabels[i] = Double.doubleToLongBits(doubleLabels[i]);
+
+        assert LongStream.of(longLabels).distinct().count() == longLabels.length
+                : "switch labels are not distinct: " + Arrays.toString(doubleLabels);
+
+        return new LongSwitchCallSite(invocationType, longLabels);
+    }
+
+    static class LongSwitchCallSite extends ConstantCallSite {
+        private final long[] labels;
+        private final int[] indexes;
+
+        LongSwitchCallSite(MethodType targetType,
+                           long[] longLabels) throws Throwable {
+            super(targetType, CONSTANT_INIT_HOOK);
+
+            // expensive way to index an array
+            indexes = IntStream.range(0, longLabels.length)
+                               .boxed()
+                               .sorted(Comparator.comparingLong(a -> longLabels[a]))
+                               .mapToInt(Integer::intValue)
+                               .toArray();
+            labels = new long[indexes.length];
+            for (int i=0; i<indexes.length; i++)
+                labels[i] = longLabels[indexes[i]];
+        }
+
+        int doSwitch(long target) {
+            int index = Arrays.binarySearch(labels, target);
+            return (index >= 0) ? indexes[index] : indexes.length;
+        }
+
+        int doSwitch(double target) {
+            return doSwitch(Double.doubleToLongBits(target));
+        }
+
+        int doSwitch(Long target) {
+            return (target == null) ? -1 : doSwitch((long) target);
+        }
+
+        int doSwitch(Double target) {
+            return (target == null) ? -1 : doSwitch((double) target);
+        }
+    }
+
+    /**
+     * Bootstrap method for linking an {@code invokedynamic} call site that
+     * implements a {@code switch} on a {@code String} target.  The static
+     * arguments are a varargs array of {@code String} labels.
+     *
+     * <p>The results are undefined if the labels array contains duplicates
+     * according to {@link String#equals(Object)}.
+     *
+     * @implNote
+     *
+     * The implementation only enforces the requirement that the labels array
+     * be duplicate-free if system assertions are enabled.
+     *
+     * @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
+     *                       {@code String}, 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 stringLabels non-null string values corresponding to the case
+     *                     labels of the {@code switch} statement.
+     * @return the index into {@code labels} of the target value, if the target
+     *         matches any of the labels, {@literal -1} if the target value is
+     *         {@code null}, or {@code stringLabels.length} if the target value
+     *         does not match any of the labels.
+     * @throws NullPointerException if any required argument is null
+     * @throws IllegalArgumentException if any labels are null, or if the
+     * invocation type is not {@code (String)int}
+     * @throws Throwable if there is any error linking the call site
+     */
+    public static CallSite stringSwitch(MethodHandles.Lookup lookup,
+                                        String invocationName,
+                                        MethodType invocationType,
+                                        String... stringLabels) throws Throwable {
+        if (invocationType.parameterCount() != 1
+            || (!invocationType.returnType().equals(int.class))
+            || (!invocationType.parameterType(0).equals(String.class)))
+            throw new IllegalArgumentException("Illegal invocation type " + invocationType);
+        requireNonNull(stringLabels);
+        if (Stream.of(stringLabels).anyMatch(Objects::isNull))
+            throw new IllegalArgumentException("null label found");
+
+        assert Stream.of(stringLabels).distinct().count() == stringLabels.length
+                : "switch labels are not distinct: " + Arrays.toString(stringLabels);
+
+        return new StringSwitchCallSite(invocationType, stringLabels);
+    }
+
+    static class StringSwitchCallSite extends ConstantCallSite {
+        private static final Comparator<String> STRING_BY_HASH
+                = Comparator.comparingInt(Objects::hashCode);
+
+        private final String[] sortedByHash;
+        private final int[] indexes;
+        private final boolean collisions;
+
+        StringSwitchCallSite(MethodType targetType,
+                             String[] stringLabels) throws Throwable {
+            super(targetType, CONSTANT_INIT_HOOK);
+
+            // expensive way to index an array
+            indexes = IntStream.range(0, stringLabels.length)
+                               .boxed()
+                               .sorted(Comparator.comparingInt(i -> stringLabels[i].hashCode()))
+                               .mapToInt(Integer::intValue)
+                               .toArray();
+            sortedByHash = new String[indexes.length];
+            for (int i=0; i<indexes.length; i++)
+                sortedByHash[i] = stringLabels[indexes[i]];
+
+            collisions = IntStream.range(0, sortedByHash.length-1)
+                                  .anyMatch(i -> sortedByHash[i].hashCode() == sortedByHash[i + 1].hashCode());
+        }
+
+        int doSwitch(String target) {
+            if (target == null)
+                return -1;
+
+            int index = Arrays.binarySearch(sortedByHash, target, STRING_BY_HASH);
+            if (index < 0)
+                return indexes.length;
+            else if (target.equals(sortedByHash[index])) {
+                return indexes[index];
+            }
+            else if (collisions) {
+                int hash = target.hashCode();
+                while (index > 0 && sortedByHash[index-1].hashCode() == hash)
+                    --index;
+                for (; index < sortedByHash.length && sortedByHash[index].hashCode() == hash; index++)
+                    if (target.equals(sortedByHash[index]))
+                        return indexes[index];
+            }
+
+            return indexes.length;
+        }
+    }
+
+    /**
+     * Bootstrap method for linking an {@code invokedynamic} call site that
+     * implements a {@code switch} on an {@code Enum} target.  The static
+     * arguments are the enum class, and a varargs arrays of {@code String}
+     * that are the names of the enum constants corresponding to the
+     * {@code case} labels.
+     *
+     * <p>The results are undefined if the names array contains duplicates.
+     *
+     * @implNote
+     *
+     * The implementation only enforces the requirement that the labels array
+     * be duplicate-free if system assertions are enabled.
+     *
+     * @param <E> the enum type
+     * @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
+     *                       {@code Enum}, 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 enumClass the enum class
+     * @param enumNames names of the enum constants against which the target
+     *                  should be matched
+     * @return the index into {@code labels} of the target value, if the target
+     *         matches any of the labels, {@literal -1} if the target value is
+     *         {@code null}, or {@code stringLabels.length} if the target value
+     *         does not match any of the labels.
+     * @throws IllegalArgumentException if the specified class is not an
+     *                                  enum class, or any label name is null,
+     *                                  or if the invocation type is not
+     *                                  {@code (Enum)int}
+     * @throws NullPointerException if any required argument is null
+     * @throws Throwable if there is any error linking the call site
+     */
+    public static<E extends Enum<E>> CallSite enumSwitch(MethodHandles.Lookup lookup,
+                                                         String invocationName,
+                                                         MethodType invocationType,
+                                                         Class<E> enumClass,
+                                                         String... enumNames) throws Throwable {
+        if (invocationType.parameterCount() != 1
+            || (!invocationType.returnType().equals(int.class))
+            || (!invocationType.parameterType(0).equals(Enum.class)))
+            throw new IllegalArgumentException("Illegal invocation type " + invocationType);
+        requireNonNull(enumClass);
+        requireNonNull(enumNames);
+        if (!enumClass.isEnum())
+            throw new IllegalArgumentException("not an enum class");
+        if (Stream.of(enumNames).anyMatch(Objects::isNull))
+            throw new IllegalArgumentException("null label found");
+
+        assert Stream.of(enumNames).distinct().count() == enumNames.length
+                : "switch labels are not distinct: " + Arrays.toString(enumNames);
+
+        return new EnumSwitchCallSite<>(invocationType, enumClass, enumNames);
+    }
+
+    static class EnumSwitchCallSite<E extends Enum<E>> extends ConstantCallSite {
+        private final int[] ordinalMap;
+
+        EnumSwitchCallSite(MethodType targetType,
+                           Class<E> enumClass,
+                           String... enumNames) throws Throwable {
+            super(targetType, CONSTANT_INIT_HOOK);
+
+            ordinalMap = new int[enumClass.getEnumConstants().length];
+            Arrays.fill(ordinalMap, enumNames.length);
+
+            for (int i=0; i<enumNames.length; i++) {
+                try {
+                    ordinalMap[E.valueOf(enumClass, enumNames[i]).ordinal()] = i;
+                }
+                catch (Exception e) {
+                    // allow non-existent labels, but never match them
+                    continue;
+                }
+            }
+        }
+
+        @SuppressWarnings("rawtypes")
+        int doSwitch(Enum target) {
+            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, TYPE_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;
+        }
+    }
+
+    /**
+     * Result type for pattern switches
+     */
+    public static class PatternSwitchResult {
+        /**
+         * The selected index, -1 if input was null, or length if not matched
+         */
+        public final int index;
+
+        /**
+         * The carrier
+         */
+        public final Object carrier;
+
+        /**
+         * Construct a PatternSwitchResult
+         *
+         * @param index the index
+         * @param carrier the carrier
+         */
+        public PatternSwitchResult(int index, Object carrier) {
+            this.index = index;
+            this.carrier = carrier;
+        }
+    }
+
+    /**
+     * Bootstrap for pattern switches
+     *
+     * @param lookup the lookup (ignored)
+     * @param invocationName the invocation name (ignored)
+     * @param invocationType the invocation type (must return PatternSwitchResult)
+     * @param patterns the patterns
+     * @return the result
+     * @throws Throwable if something went wrong
+     */
+    public static CallSite patternSwitch(MethodHandles.Lookup lookup,
+                                         String invocationName,
+                                         MethodType invocationType,
+                                         Extractor... patterns) throws Throwable {
+        if (invocationType.parameterCount() != 1
+            || (!invocationType.returnType().equals(PatternSwitchResult.class))
+            || invocationType.parameterType(0).isPrimitive())
+            throw new IllegalArgumentException("Illegal invocation type " + invocationType);
+        requireNonNull(patterns);
+
+        patterns = patterns.clone();
+        Class<?> targetType = invocationType.parameterType(0);
+
+        for (int i = 0; i < patterns.length; i++) {
+            Extractor pattern = patterns[i];
+            if (pattern.descriptor().returnType() != targetType)
+                patterns[i] = Extractor.adapt(pattern, targetType);
+        }
+
+        if (Stream.of(patterns).anyMatch(Objects::isNull))
+            throw new IllegalArgumentException("null pattern found");
+
+        return new PatternSwitchCallSite(invocationType, patterns);
+    }
+
+    static class PatternSwitchCallSite extends ConstantCallSite {
+        private final Extractor[] patterns;
+
+        PatternSwitchCallSite(MethodType targetType,
+                              Extractor[] patterns) throws Throwable {
+            super(targetType, PATTERN_INIT_HOOK);
+            this.patterns = patterns;
+        }
+
+        PatternSwitchResult doSwitch(Object target) throws Throwable {
+            if (target == null)
+                return new PatternSwitchResult(-1, null);
+
+            // Dumbest possible strategy
+            for (int i = 0; i < patterns.length; i++) {
+                Extractor e = patterns[i];
+                Object o = e.tryMatch().invoke(target);
+                if (o != null)
+                    return new PatternSwitchResult(i, o);
+            }
+
+            return new PatternSwitchResult(patterns.length, null);
+
+        }
+    }
+}
--- a/test/jdk/java/lang/compiler/ExtractorTest.java	Mon Oct 29 16:25:13 2018 -0400
+++ b/test/jdk/java/lang/compiler/ExtractorTest.java	Mon Oct 29 17:34:03 2018 -0400
@@ -254,14 +254,14 @@
         Extractor STRING = Extractor.ofType(String.class);
         Extractor OBJECT  = Extractor.ofType(Object.class);
 
-        assertMatch(MatchKind.CARRIER, Extractor.dropBindings(Extractor.nested(TC2, STRING), 0), new TestClass2("foo"),
+        assertMatch(MatchKind.CARRIER, Extractor.dropBindings(Extractor.ofNested(TC2, STRING), 0), new TestClass2("foo"),
                     "foo");
-        assertMatch(MatchKind.CARRIER, Extractor.dropBindings(Extractor.nested(TC2, OBJECT), 0), new TestClass2("foo"),
+        assertMatch(MatchKind.CARRIER, Extractor.dropBindings(Extractor.ofNested(TC2, OBJECT), 0), new TestClass2("foo"),
                     "foo");
-        assertMatch(MatchKind.FAIL, Extractor.dropBindings(Extractor.nested(TC2, STRING), 0), new TestClass2(List.of(3)),
+        assertMatch(MatchKind.FAIL, Extractor.dropBindings(Extractor.ofNested(TC2, STRING), 0), new TestClass2(List.of(3)),
                     "foo");
 
-        assertMatch(MatchKind.CARRIER, Extractor.dropBindings(Extractor.nested(TC2, Extractor.nested(TC2, STRING)), 0, 1), new TestClass2(new TestClass2("foo")),
+        assertMatch(MatchKind.CARRIER, Extractor.dropBindings(Extractor.ofNested(TC2, Extractor.ofNested(TC2, STRING)), 0, 1), new TestClass2(new TestClass2("foo")),
                     "foo");
 
     }
--- a/test/jdk/java/lang/compiler/RecordTest.java	Mon Oct 29 16:25:13 2018 -0400
+++ b/test/jdk/java/lang/compiler/RecordTest.java	Mon Oct 29 17:34:03 2018 -0400
@@ -24,19 +24,15 @@
  */
 
 import java.lang.compiler.Extractor;
-import java.lang.compiler.ExtractorCarriers;
+import java.lang.compiler.SwitchBootstraps;
+import java.lang.invoke.CallSite;
 import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.MethodType;
-import java.lang.reflect.Method;
-import java.util.Objects;
-import java.util.stream.IntStream;
-import java.util.stream.Stream;
 
 import org.testng.annotations.Test;
 
 import static java.lang.invoke.MethodHandleInfo.REF_newInvokeSpecial;
-import static java.util.Collections.nCopies;
 import static org.testng.Assert.assertEquals;
 
 @Test
@@ -49,15 +45,20 @@
     record R(int a, String b, double c);
     record RR(R r1, R R2);
 
+    private Extractor recordExtractor(Class<?> recordClass,
+                                      Class<?>... paramTypes) throws Throwable {
+        return Extractor.findExtractor(MethodHandles.lookup(), "_", Extractor.class,
+                                       recordClass, MethodType.methodType(void.class, paramTypes), recordClass.getName(), REF_newInvokeSpecial);
+    }
+
     public void testRecord() throws Throwable {
         R r = new R(1, "two", 3.14d);
-        Extractor ex = Extractor.findExtractor(MethodHandles.lookup(), "_", Extractor.class,
-                                               R.class, MethodType.methodType(void.class, int.class, String.class, double.class), "Foo", REF_newInvokeSpecial);
+        Extractor rExtract = recordExtractor(R.class, int.class, String.class, double.class);
 
-        MethodHandle tryExtract = Extractor.extractorTryMatch(MethodHandles.lookup(), "_", MethodHandle.class, ex);
-        MethodHandle a = Extractor.extractorComponent(MethodHandles.lookup(), "_", MethodHandle.class, ex, 0);
-        MethodHandle b = Extractor.extractorComponent(MethodHandles.lookup(), "_", MethodHandle.class, ex, 1);
-        MethodHandle c = Extractor.extractorComponent(MethodHandles.lookup(), "_", MethodHandle.class, ex, 2);
+        MethodHandle tryExtract = Extractor.extractorTryMatch(MethodHandles.lookup(), "_", MethodHandle.class, rExtract);
+        MethodHandle a = Extractor.extractorComponent(MethodHandles.lookup(), "_", MethodHandle.class, rExtract, 0);
+        MethodHandle b = Extractor.extractorComponent(MethodHandles.lookup(), "_", MethodHandle.class, rExtract, 1);
+        MethodHandle c = Extractor.extractorComponent(MethodHandles.lookup(), "_", MethodHandle.class, rExtract, 2);
 
         Object o = tryExtract.invoke(r);
         assertEquals(1, a.invoke(o));
@@ -70,10 +71,8 @@
         R r2 = new R(2, "four", 6.0d);
         RR rr = new RR(r1, r2);
 
-        Extractor rExtract = Extractor.findExtractor(MethodHandles.lookup(), "_", Extractor.class,
-                                                     R.class, MethodType.methodType(void.class, int.class, String.class, double.class), "Foo", REF_newInvokeSpecial);
-        Extractor rrExtract = Extractor.findExtractor(MethodHandles.lookup(), "_", Extractor.class,
-                                                      RR.class, MethodType.methodType(void.class, R.class, R.class), "Foo", REF_newInvokeSpecial);
+        Extractor rExtract = recordExtractor(R.class, int.class, String.class, double.class);
+        Extractor rrExtract = recordExtractor(RR.class, R.class, R.class);
 
         MethodHandle tryExtractR = Extractor.extractorTryMatch(MethodHandles.lookup(), "_", MethodHandle.class, rExtract);
         MethodHandle ra = Extractor.extractorComponent(MethodHandles.lookup(), "_", MethodHandle.class, rExtract, 0);
@@ -98,12 +97,10 @@
     }
 
     public void testNested() throws Throwable {
-        Extractor rExtract = Extractor.findExtractor(MethodHandles.lookup(), "_", Extractor.class,
-                                                     R.class, MethodType.methodType(void.class, int.class, String.class, double.class), "Foo", REF_newInvokeSpecial);
-        Extractor rrExtract = Extractor.findExtractor(MethodHandles.lookup(), "_", Extractor.class,
-                                                      RR.class, MethodType.methodType(void.class, R.class, R.class), "Foo", REF_newInvokeSpecial);
+        Extractor rExtract = recordExtractor(R.class, int.class, String.class, double.class);
+        Extractor rrExtract = recordExtractor(RR.class, R.class, R.class);
 
-        Extractor e = Extractor.nested(rrExtract, rExtract);
+        Extractor e = Extractor.ofNested(rrExtract, rExtract);
 
         R r1 = new R(1, "two", 3.14d);
         R r2 = new R(2, "four", 6.0d);
@@ -117,7 +114,7 @@
         assertEquals(e.component(3).invoke(o), "two");
         assertEquals(e.component(4).invoke(o), 3.14d);
 
-        Extractor ee = Extractor.nested(rrExtract, rExtract, rExtract);
+        Extractor ee = Extractor.ofNested(rrExtract, rExtract, rExtract);
         o = ee.tryMatch().invoke(rr);
 
         assertEquals(ee.component(0).invoke(o), new R(1, "two", 3.14d));
@@ -129,4 +126,87 @@
         assertEquals(ee.component(6).invoke(o), "four");
         assertEquals(ee.component(7).invoke(o), 6.0d);
     }
+
+    record A(int a);
+    record B(int a, int b);
+    record S(String s);
+    record T(String s, String t);
+    record U();
+
+    private Object component(Extractor e, int num, Object carrier) throws Throwable {
+        return Extractor.extractorComponent(MethodHandles.lookup(), "_", MethodHandle.class,
+                                            e, num).invoke(carrier);
+    }
+
+    public void testRecordSwitch() throws Throwable {
+        Extractor[] extractors = {
+                recordExtractor(A.class, int.class),
+                recordExtractor(B.class, int.class, int.class),
+                recordExtractor(S.class, String.class),
+                recordExtractor(T.class, String.class, String.class),
+                recordExtractor(U.class)
+        };
+
+        Object[] exemplars = {
+                new A(1),
+                new B(2, 3),
+                new S("four"),
+                new T("five", "six"),
+                new U()
+        };
+
+        CallSite cs = SwitchBootstraps.patternSwitch(MethodHandles.lookup(), "_",
+                                                     MethodType.methodType(SwitchBootstraps.PatternSwitchResult.class, Object.class),
+                                                     extractors);
+        MethodHandle mh = cs.dynamicInvoker();
+        for (int i = 0; i < exemplars.length; i++) {
+            Object exemplar = exemplars[i];
+            SwitchBootstraps.PatternSwitchResult result = (SwitchBootstraps.PatternSwitchResult) mh.invoke(exemplar);
+            assertEquals(result.index, i);
+            switch (result.index) {
+                case 0:
+                    assertEquals(component(extractors[i], 0, result.carrier), 1);
+                    break;
+                case 1:
+                    assertEquals(component(extractors[i], 0, result.carrier), 2);
+                    assertEquals(component(extractors[i], 1, result.carrier), 3);
+                    break;
+                case 2:
+                    assertEquals(component(extractors[i], 0, result.carrier), "four");
+                    break;
+                case 3:
+                    assertEquals(component(extractors[i], 0, result.carrier), "five");
+                    assertEquals(component(extractors[i], 1, result.carrier), "six");
+                    break;
+            };
+
+            result = (SwitchBootstraps.PatternSwitchResult) mh.invoke(null);
+            assertEquals(result.index, -1);
+
+            result = (SwitchBootstraps.PatternSwitchResult) mh.invoke("foo");
+            assertEquals(result.index, 5);
+        }
+    }
+
+    record Box(Object o1);
+
+    public void testNestedRecord() throws Throwable {
+        Extractor boxA = Extractor.ofNested(recordExtractor(Box.class, Object.class),
+                                            recordExtractor(A.class, int.class));
+        Extractor boxB = Extractor.ofNested(recordExtractor(Box.class, Object.class),
+                                            recordExtractor(B.class, int.class, int.class));
+
+        CallSite cs = SwitchBootstraps.patternSwitch(MethodHandles.lookup(), "_",
+                                                     MethodType.methodType(SwitchBootstraps.PatternSwitchResult.class, Object.class),
+                                                     boxA, boxB);
+        MethodHandle mh = cs.dynamicInvoker();
+
+        assertEquals(((SwitchBootstraps.PatternSwitchResult) mh.invoke(new Box(new A(1)))).index, 0);
+        assertEquals(((SwitchBootstraps.PatternSwitchResult) mh.invoke(new Box(new B(2, 3)))).index, 1);
+        assertEquals(((SwitchBootstraps.PatternSwitchResult) mh.invoke(new Box("foo"))).index, 2);
+        assertEquals(((SwitchBootstraps.PatternSwitchResult) mh.invoke(new Box(null))).index, 2);
+        assertEquals(((SwitchBootstraps.PatternSwitchResult) mh.invoke("foo")).index, 2);
+        assertEquals(((SwitchBootstraps.PatternSwitchResult) mh.invoke(null)).index, -1);
+
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/compiler/SwitchBootstrapsTest.java	Mon Oct 29 17:34:03 2018 -0400
@@ -0,0 +1,466 @@
+/*
+ * Copyright (c) 2012, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+import java.io.Serializable;
+import java.lang.invoke.CallSite;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.compiler.SwitchBootstraps;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import jdk.test.lib.RandomFactory;
+
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.fail;
+
+/**
+ * @test
+ * @key randomness
+ * @library /test/lib
+ * @build jdk.test.lib.RandomFactory
+ * @run testng SwitchBootstrapsTest
+ */
+@Test
+public class SwitchBootstrapsTest {
+    private final static Set<Class<?>> BOOLEAN_TYPES = Set.of(boolean.class, Boolean.class);
+    private final static Set<Class<?>> ALL_INT_TYPES = Set.of(int.class, short.class, byte.class, char.class,
+                                                              Integer.class, Short.class, Byte.class, Character.class);
+    private final static Set<Class<?>> SIGNED_NON_BYTE_TYPES = Set.of(int.class, Integer.class, short.class, Short.class);
+    private final static Set<Class<?>> CHAR_TYPES = Set.of(char.class, Character.class);
+    private final static Set<Class<?>> BYTE_TYPES = Set.of(byte.class, Byte.class);
+    private final static Set<Class<?>> SIGNED_TYPES
+            = Set.of(int.class, short.class, byte.class,
+                     Integer.class, Short.class, Byte.class);
+
+    public static final MethodHandle BSM_BOOLEAN_SWITCH;
+    public static final MethodHandle BSM_INT_SWITCH;
+    public static final MethodHandle BSM_LONG_SWITCH;
+    public static final MethodHandle BSM_FLOAT_SWITCH;
+    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();
+
+    static {
+        try {
+            BSM_BOOLEAN_SWITCH = MethodHandles.lookup().findStatic(SwitchBootstraps.class, "booleanSwitch",
+                                                                   MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, boolean[].class));
+            BSM_INT_SWITCH = MethodHandles.lookup().findStatic(SwitchBootstraps.class, "intSwitch",
+                                                               MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, int[].class));
+            BSM_LONG_SWITCH = MethodHandles.lookup().findStatic(SwitchBootstraps.class, "longSwitch",
+                                                                MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, long[].class));
+            BSM_FLOAT_SWITCH = MethodHandles.lookup().findStatic(SwitchBootstraps.class, "floatSwitch",
+                                                                 MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, float[].class));
+            BSM_DOUBLE_SWITCH = MethodHandles.lookup().findStatic(SwitchBootstraps.class, "doubleSwitch",
+                                                                  MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, double[].class));
+            BSM_STRING_SWITCH = MethodHandles.lookup().findStatic(SwitchBootstraps.class, "stringSwitch",
+                                                                  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);
+        }
+    }
+
+    private MethodType switchType(Class<?> target) {
+        return MethodType.methodType(int.class, target);
+    }
+
+    private Object box(Class<?> clazz, int i) {
+        if (clazz == Integer.class)
+            return i;
+        else if (clazz == Short.class)
+            return (short) i;
+        else if (clazz == Character.class)
+            return (char) i;
+        else if (clazz == Byte.class)
+            return (byte) i;
+        else
+            throw new IllegalArgumentException(clazz.toString());
+    }
+
+    private void testBoolean(boolean... labels) throws Throwable {
+        Map<Class<?>, MethodHandle> mhs
+                = Map.of(boolean.class, ((CallSite) BSM_BOOLEAN_SWITCH.invoke(MethodHandles.lookup(), "", switchType(boolean.class), labels)).dynamicInvoker(),
+                         Boolean.class, ((CallSite) BSM_BOOLEAN_SWITCH.invoke(MethodHandles.lookup(), "", switchType(Boolean.class), labels)).dynamicInvoker());
+
+        List<Boolean> labelList = new ArrayList<>();
+        for (boolean label : labels)
+            labelList.add(label);
+
+        for (int i=0; i<labels.length; i++) {
+            assertEquals(i, (int) mhs.get(boolean.class).invokeExact((boolean) labels[i]));
+            assertEquals(i, (int) mhs.get(Boolean.class).invokeExact((Boolean) labels[i]));
+        }
+
+        boolean[] booleans = { false, true };
+        for (boolean b : booleans) {
+            if (!labelList.contains(b)) {
+                assertEquals(labels.length, mhs.get(boolean.class).invoke((boolean) b));
+                assertEquals(labels.length, mhs.get(Boolean.class).invoke((boolean) b));
+            }
+        }
+
+        assertEquals(-1, (int) mhs.get(Boolean.class).invoke(null));
+    }
+
+    private void testInt(Set<Class<?>> targetTypes, int... labels) throws Throwable {
+        Map<Class<?>, MethodHandle> mhs
+                = Map.of(char.class, ((CallSite) BSM_INT_SWITCH.invoke(MethodHandles.lookup(), "", switchType(char.class), labels)).dynamicInvoker(),
+                         byte.class, ((CallSite) BSM_INT_SWITCH.invoke(MethodHandles.lookup(), "", switchType(byte.class), labels)).dynamicInvoker(),
+                         short.class, ((CallSite) BSM_INT_SWITCH.invoke(MethodHandles.lookup(), "", switchType(short.class), labels)).dynamicInvoker(),
+                         int.class, ((CallSite) BSM_INT_SWITCH.invoke(MethodHandles.lookup(), "", switchType(int.class), labels)).dynamicInvoker(),
+                         Character.class, ((CallSite) BSM_INT_SWITCH.invoke(MethodHandles.lookup(), "", switchType(Character.class), labels)).dynamicInvoker(),
+                         Byte.class, ((CallSite) BSM_INT_SWITCH.invoke(MethodHandles.lookup(), "", switchType(Byte.class), labels)).dynamicInvoker(),
+                         Short.class, ((CallSite) BSM_INT_SWITCH.invoke(MethodHandles.lookup(), "", switchType(Short.class), labels)).dynamicInvoker(),
+                         Integer.class, ((CallSite) BSM_INT_SWITCH.invoke(MethodHandles.lookup(), "", switchType(Integer.class), labels)).dynamicInvoker());
+
+        List<Integer> labelList = IntStream.of(labels)
+                                           .boxed()
+                                           .collect(Collectors.toList());
+
+        for (int i=0; i<labels.length; i++) {
+            // test with invokeExact
+            if (targetTypes.contains(char.class))
+                assertEquals(i, (int) mhs.get(char.class).invokeExact((char) labels[i]));
+            if (targetTypes.contains(byte.class))
+                assertEquals(i, (int) mhs.get(byte.class).invokeExact((byte) labels[i]));
+            if (targetTypes.contains(short.class))
+                assertEquals(i, (int) mhs.get(short.class).invokeExact((short) labels[i]));
+            if (targetTypes.contains(int.class))
+                assertEquals(i, (int) mhs.get(int.class).invokeExact(labels[i]));
+            if (targetTypes.contains(Integer.class))
+                assertEquals(i, (int) mhs.get(Integer.class).invokeExact((Integer) labels[i]));
+            if (targetTypes.contains(Short.class))
+                assertEquals(i, (int) mhs.get(Short.class).invokeExact((Short) (short) labels[i]));
+            if (targetTypes.contains(Byte.class))
+                assertEquals(i, (int) mhs.get(Byte.class).invokeExact((Byte) (byte) labels[i]));
+            if (targetTypes.contains(Character.class))
+                assertEquals(i, (int) mhs.get(Character.class).invokeExact((Character) (char) labels[i]));
+
+            // and with invoke
+            assertEquals(i, (int) mhs.get(int.class).invoke(labels[i]));
+            assertEquals(i, (int) mhs.get(Integer.class).invoke(labels[i]));
+        }
+
+        for (int i=-1000; i<1000; i++) {
+            if (!labelList.contains(i)) {
+                assertEquals(labels.length, mhs.get(short.class).invoke((short) i));
+                assertEquals(labels.length, mhs.get(Short.class).invoke((short) i));
+                assertEquals(labels.length, mhs.get(int.class).invoke(i));
+                assertEquals(labels.length, mhs.get(Integer.class).invoke(i));
+                if (i >= 0) {
+                    assertEquals(labels.length, mhs.get(char.class).invoke((char)i));
+                    assertEquals(labels.length, mhs.get(Character.class).invoke((char)i));
+                }
+                if (i >= -128 && i <= 127) {
+                    assertEquals(labels.length, mhs.get(byte.class).invoke((byte)i));
+                    assertEquals(labels.length, mhs.get(Byte.class).invoke((byte)i));
+                }
+            }
+        }
+
+        assertEquals(-1, (int) mhs.get(Integer.class).invoke(null));
+        assertEquals(-1, (int) mhs.get(Short.class).invoke(null));
+        assertEquals(-1, (int) mhs.get(Byte.class).invoke(null));
+        assertEquals(-1, (int) mhs.get(Character.class).invoke(null));
+    }
+
+    private void testFloat(float... labels) throws Throwable {
+        Map<Class<?>, MethodHandle> mhs
+                = Map.of(float.class, ((CallSite) BSM_FLOAT_SWITCH.invoke(MethodHandles.lookup(), "", switchType(float.class), labels)).dynamicInvoker(),
+                         Float.class, ((CallSite) BSM_FLOAT_SWITCH.invoke(MethodHandles.lookup(), "", switchType(Float.class), labels)).dynamicInvoker());
+
+        List<Float> labelList = new ArrayList<>();
+        for (float label : labels)
+            labelList.add(label);
+
+        for (int i=0; i<labels.length; i++) {
+            assertEquals(i, (int) mhs.get(float.class).invokeExact((float) labels[i]));
+            assertEquals(i, (int) mhs.get(Float.class).invokeExact((Float) labels[i]));
+        }
+
+        float[] someFloats = { 1.0f, Float.MIN_VALUE, 3.14f };
+        for (float f : someFloats) {
+            if (!labelList.contains(f)) {
+                assertEquals(labels.length, mhs.get(float.class).invoke((float) f));
+                assertEquals(labels.length, mhs.get(Float.class).invoke((float) f));
+            }
+        }
+
+        assertEquals(-1, (int) mhs.get(Float.class).invoke(null));
+    }
+
+    private void testDouble(double... labels) throws Throwable {
+        Map<Class<?>, MethodHandle> mhs
+                = Map.of(double.class, ((CallSite) BSM_DOUBLE_SWITCH.invoke(MethodHandles.lookup(), "", switchType(double.class), labels)).dynamicInvoker(),
+                         Double.class, ((CallSite) BSM_DOUBLE_SWITCH.invoke(MethodHandles.lookup(), "", switchType(Double.class), labels)).dynamicInvoker());
+
+        var labelList = new ArrayList<Double>();
+        for (double label : labels)
+            labelList.add(label);
+
+        for (int i=0; i<labels.length; i++) {
+            assertEquals(i, (int) mhs.get(double.class).invokeExact((double) labels[i]));
+            assertEquals(i, (int) mhs.get(Double.class).invokeExact((Double) labels[i]));
+        }
+
+        double[] someDoubles = { 1.0, Double.MIN_VALUE, 3.14 };
+        for (double f : someDoubles) {
+            if (!labelList.contains(f)) {
+                assertEquals(labels.length, mhs.get(double.class).invoke((double) f));
+                assertEquals(labels.length, mhs.get(Double.class).invoke((double) f));
+            }
+        }
+
+        assertEquals(-1, (int) mhs.get(Double.class).invoke(null));
+    }
+
+    private void testLong(long... labels) throws Throwable {
+        Map<Class<?>, MethodHandle> mhs
+                = Map.of(long.class, ((CallSite) BSM_LONG_SWITCH.invoke(MethodHandles.lookup(), "", switchType(long.class), labels)).dynamicInvoker(),
+                         Long.class, ((CallSite) BSM_LONG_SWITCH.invoke(MethodHandles.lookup(), "", switchType(Long.class), labels)).dynamicInvoker());
+
+        List<Long> labelList = new ArrayList<>();
+        for (long label : labels)
+            labelList.add(label);
+
+        for (int i=0; i<labels.length; i++) {
+            assertEquals(i, (int) mhs.get(long.class).invokeExact((long) labels[i]));
+            assertEquals(i, (int) mhs.get(Long.class).invokeExact((Long) labels[i]));
+        }
+
+        long[] someLongs = { 1L, Long.MIN_VALUE, Long.MAX_VALUE };
+        for (long l : someLongs) {
+            if (!labelList.contains(l)) {
+                assertEquals(labels.length, mhs.get(long.class).invoke((long) l));
+                assertEquals(labels.length, mhs.get(Long.class).invoke((long) l));
+            }
+        }
+
+        assertEquals(-1, (int) mhs.get(Long.class).invoke(null));
+    }
+
+    private void testString(String... targets) throws Throwable {
+        MethodHandle indy = ((CallSite) BSM_STRING_SWITCH.invoke(MethodHandles.lookup(), "", switchType(String.class), targets)).dynamicInvoker();
+        List<String> targetList = Stream.of(targets)
+                                        .collect(Collectors.toList());
+
+        for (int i=0; i<targets.length; i++) {
+            String s = targets[i];
+            int result = (int) indy.invoke(s);
+            assertEquals((s == null) ? -1 : i, result);
+        }
+
+        for (String s : List.of("", "A", "AA", "AAA", "AAAA")) {
+            if (!targetList.contains(s)) {
+                assertEquals(targets.length, indy.invoke(s));
+            }
+        }
+        assertEquals(-1, (int) indy.invoke(null));
+    }
+
+    private<E extends Enum<E>> void testEnum(Class<E> enumClass, String... targets) throws Throwable {
+        MethodHandle indy = ((CallSite) BSM_ENUM_SWITCH.invoke(MethodHandles.lookup(), "", switchType(Enum.class), enumClass, targets)).dynamicInvoker();
+        List<E> targetList = Stream.of(targets)
+                                   .map(s -> Enum.valueOf(enumClass, s))
+                                   .collect(Collectors.toList());
+
+        for (int i=0; i<targets.length; i++) {
+            String s = targets[i];
+            E e = Enum.valueOf(enumClass, s);
+            int result = (int) indy.invoke(e);
+            assertEquals((s == null) ? -1 : i, result);
+        }
+
+        for (E e : enumClass.getEnumConstants()) {
+            int index = (int) indy.invoke(e);
+            if (targetList.contains(e))
+                assertEquals(e.name(), targets[index]);
+            else
+                assertEquals(targets.length, index);
+        }
+
+        assertEquals(-1, (int) indy.invoke(null));
+    }
+
+    public void testBoolean() throws Throwable {
+        testBoolean(new boolean[0]);
+        testBoolean(false);
+        testBoolean(true);
+        testBoolean(false, true);
+    }
+
+    public void testInt() throws Throwable {
+        testInt(ALL_INT_TYPES, 8, 6, 7, 5, 3, 0, 9);
+        testInt(ALL_INT_TYPES, 1, 2, 4, 8, 16);
+        testInt(ALL_INT_TYPES, 5, 4, 3, 2, 1, 0);
+        testInt(SIGNED_TYPES, 5, 4, 3, 2, 1, 0, -1);
+        testInt(SIGNED_TYPES, -1);
+        testInt(ALL_INT_TYPES, new int[] { });
+
+        for (int i=0; i<5; i++) {
+            int len = 50 + random.nextInt(800);
+            int[] arr = IntStream.generate(() -> random.nextInt(10000) - 5000)
+                                 .distinct()
+                                 .limit(len)
+                                 .toArray();
+            testInt(SIGNED_NON_BYTE_TYPES, arr);
+
+            arr = IntStream.generate(() -> random.nextInt(10000))
+                    .distinct()
+                    .limit(len)
+                    .toArray();
+            testInt(CHAR_TYPES, arr);
+
+            arr = IntStream.generate(() -> random.nextInt(127) - 64)
+                           .distinct()
+                           .limit(120)
+                           .toArray();
+            testInt(BYTE_TYPES, arr);
+        }
+    }
+
+    public void testLong() throws Throwable {
+        testLong(1L, Long.MIN_VALUE, Long.MAX_VALUE);
+        testLong(8L, 2L, 5L, 4L, 3L, 9L, 1L);
+        testLong(new long[] { });
+
+        // @@@ Random tests
+        // @@@ More tests for weird values
+    }
+
+    public void testFloat() throws Throwable {
+        testFloat(0.0f, -0.0f, -1.0f, 1.0f, 3.14f, Float.MIN_VALUE, Float.MAX_VALUE, Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY);
+        testFloat(new float[] { });
+        testFloat(0.0f, 1.0f, 3.14f, Float.NaN);
+
+        // @@@ Random tests
+        // @@@ More tests for weird values
+    }
+
+    public void testDouble() throws Throwable {
+        testDouble(0.0, -0.0, -1.0, 1.0, 3.14, Double.MIN_VALUE, Double.MAX_VALUE,
+                   Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY);
+        testDouble(new double[] { });
+        testDouble(0.0f, 1.0f, 3.14f, Double.NaN);
+
+        // @@@ Random tests
+        // @@@ More tests for weird values
+    }
+
+    public void testString() throws Throwable {
+        testString("a", "b", "c");
+        testString("c", "b", "a");
+        testString("cow", "pig", "horse", "orangutan", "elephant", "dog", "frog", "ant");
+        testString("a", "b", "c", "A", "B", "C");
+        testString("C", "B", "A", "c", "b", "a");
+
+        // Tests with hash collisions; Ba/CB, Ca/DB
+        testString("Ba", "CB");
+        testString("Ba", "CB", "Ca", "DB");
+
+        // Test with null
+        try {
+            testString("a", null, "c");
+            fail("expected failure");
+        }
+        catch (IllegalArgumentException t) {
+            // success
+        }
+    }
+
+    enum E1 { A, B }
+    enum E2 { C, D, E, F, G, H }
+
+    public void testEnum() throws Throwable {
+        testEnum(E1.class);
+        testEnum(E1.class, "A");
+        testEnum(E1.class, "A", "B");
+        testEnum(E1.class, "B", "A");
+        testEnum(E2.class, "C");
+        testEnum(E2.class, "C", "D", "E", "F", "H");
+        testEnum(E2.class, "H", "C", "G", "D", "F", "E");
+
+        // Bad enum class
+        try {
+            testEnum((Class) String.class, "A");
+            fail("expected failure");
+        }
+        catch (IllegalArgumentException t) {
+            // success
+        }
+
+        // Bad enum constants
+        try {
+            testEnum(E1.class, "B", "A", "FILE_NOT_FOUND");
+            fail("expected failure");
+        }
+        catch (IllegalArgumentException t) {
+            // success
+        }
+
+        // Null enum constant
+        try {
+            testEnum(E1.class, "A", null, "B");
+            fail("expected failure");
+        }
+        catch (IllegalArgumentException t) {
+            // 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
+    }
+}