changeset 56879:755f726a61a7 pattern-runtime

Initial implementation of PatternHandle and friends
author briangoetz
date Thu, 20 Jun 2019 16:05:18 -0400
parents 41a7c27cbbef
children d85aaf1ffd89
files src/java.base/share/classes/java/lang/runtime/PatternCarriers.java src/java.base/share/classes/java/lang/runtime/PatternHandle.java src/java.base/share/classes/java/lang/runtime/PatternHandles.java src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java src/java.base/share/classes/module-info.java test/jdk/java/lang/lang-runtime/PatternHandleTest.java test/jdk/java/lang/lang-runtime/SwitchBootstrapsTest.java test/jdk/java/lang/lang-runtime/boottest/TEST.properties test/jdk/java/lang/lang-runtime/boottest/java.base/java/lang/runtime/CarrierTest.java
diffstat 9 files changed, 2893 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/java/lang/runtime/PatternCarriers.java	Thu Jun 20 16:05:18 2019 -0400
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2019, 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.runtime;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * PatternCarriers
+ */
+class PatternCarriers {
+
+    private static final CarrierFactory factory = CarrierFactories.DUMB;
+
+    interface CarrierFactory {
+        MethodHandle constructor(MethodType methodType);
+        MethodHandle component(MethodType methodType, int component);
+    }
+
+    static class DumbCarrier {
+        private final Object[] args;
+
+        DumbCarrier(Object... args) {
+            this.args = args.clone();
+        }
+
+        Object get(int i) {
+            return args[i];
+        }
+    }
+
+    enum CarrierFactories implements CarrierFactory {
+        DUMB {
+            private final MethodHandle CARRIER_CTOR;
+            private final MethodHandle CARRIER_GET;
+
+            {
+                try {
+                    CARRIER_CTOR = MethodHandles.lookup().findConstructor(DumbCarrier.class, MethodType.methodType(void.class, Object[].class));
+                    CARRIER_GET = MethodHandles.lookup().findVirtual(DumbCarrier.class, "get", MethodType.methodType(Object.class, int.class));
+                }
+                catch (ReflectiveOperationException e) {
+                    throw new ExceptionInInitializerError(e);
+                }
+            }
+
+            @Override
+            public MethodHandle constructor(MethodType methodType) {
+                return CARRIER_CTOR.asType(methodType.changeReturnType(Object.class));
+            }
+
+            @Override
+            public MethodHandle component(MethodType methodType, int component) {
+                return MethodHandles.insertArguments(CARRIER_GET, 1, component)
+                                    .asType(MethodType.methodType(methodType.parameterType(component), Object.class));
+            }
+        },
+        DUMB_SINGLE {
+            // An optimization of DUMB, where we use the value itself as carrier when there is only one value
+
+            @Override
+            public MethodHandle constructor(MethodType methodType) {
+                return methodType.parameterCount() == 1 ? MethodHandles.identity(methodType.parameterType(0)) : DUMB.constructor(methodType);
+            }
+
+            @Override
+            public MethodHandle component(MethodType methodType, int component) {
+                return methodType.parameterCount() == 1 ? MethodHandles.identity(methodType.parameterType(0)) : DUMB.component(methodType, component);
+            }
+        }
+    }
+
+    /**
+     * Returns a method handle with the given method type that instantiates
+     * a new carrier object.
+     *
+     * @param methodType the types of the carrier elements
+     * @return the carrier factory
+     */
+    public static MethodHandle carrierFactory(MethodType methodType) {
+        return factory.constructor(methodType);
+    }
+
+    /**
+     * Returns a method handle that accepts a carrier and returns the i'th component
+     *
+     * @param methodType the type of the carrier elements
+     * @param i the index of the component
+     * @return the component method handle
+     */
+    public static MethodHandle carrierComponent(MethodType methodType, int i) {
+        return factory.component(methodType, i);
+    }
+
+    /**
+     * Return all the components method handles for a carrier
+     * @param methodType the type of the carrier elements
+     * @return the component method handles
+     */
+    public static List<MethodHandle> carrierComponents(MethodType methodType) {
+        MethodHandle[] components = new MethodHandle[methodType.parameterCount()];
+        Arrays.setAll(components, i -> factory.component(methodType, i));
+        return List.of(components);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/java/lang/runtime/PatternHandle.java	Thu Jun 20 16:05:18 2019 -0400
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2019, 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.runtime;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodType;
+import java.util.List;
+
+/**
+ * Runtime object for low-level implementation of <em>pattern matching</em>. A
+ * {@linkplain PatternHandle} exposes functionality for determining if a target
+ * matches the pattern, and if so, for conditionally extracting the resulting
+ * bindings.
+ *
+ * <p>A {@linkplain PatternHandle} is parameterized by a <em>target type</em>
+ * and zero or more <em>binding variable types</em>. The target type (denoted
+ * {@code T}) is the type against which the pattern can be applied (often a
+ * broad type such as {@link Object}, but need not be), and the binding variable
+ * types (denoted {@code B*}) are the types of the binding variables that are
+ * produced by a successful match.  These types are combined into a type
+ * <em>descriptor</em>, accessed via {@link #descriptor()}, where the return
+ * type of the descriptor is the target type, and the parameter types of the
+ * descriptor are the binding variable types.
+ *
+ * <p>The behavior of a {@linkplain PatternHandle} is exposed via method
+ * handles.  The method handle returned by {@link #tryMatch()} is applied to the
+ * target to be tested, and returns an opaque result of type {@code Object}.  If
+ * the result is {@code null}, the match has failed; if is non-null, it has
+ * succeeded, and the result can be used as input to the method handles returned
+ * by {@link #components()} or {@link #component(int)} to retrieve specific
+ * binding variables.
+ *
+ * <p>The class {@link PatternHandles} contains numerous factories and
+ * combinators for {@linkplain PatternHandle}s, including {@link
+ * PatternHandles#adaptTarget(PatternHandle, Class)} which can be used to adapt
+ * a pattern handle from one target type to another (such as widening the set of
+ * types against which it can be applied.)
+ *
+ * <p>{@linkplain PatternHandle} implementations must be <a
+ * href="{@docRoot}/java.base/java/lang/doc-files/ValueBased.html">value-based</a>
+ * classes.
+ */
+public interface PatternHandle {
+
+    /**
+     * Returns a method handle that attempts to perform the pattern match
+     * described by this pattern handle.  It will have type {@code (T)Object},
+     * where {@code T} is the target type of the extractor. It accepts the
+     * target to be matched, and returns a non-null opaque carrier of type
+     * {@link Object} if the match succeeds, or {@code null} if it fails.
+     *
+     * @return the {@code tryMatch} method handle
+     */
+    MethodHandle tryMatch();
+
+    /**
+     * Returns a method handle that extracts a component from a successful
+     * match.  It will have type {@code (Object)Bi}, where {@code Bi} is the
+     * type of the corresponding binding variable, and will take the match
+     * carrier and return the corresponding binding variable.
+     *
+     * @param i the index of the component
+     * @return the component method handle
+     * @throws IndexOutOfBoundsException if {@code i} does not correspond to the
+     *                                   index of a binding variable of this
+     *                                   pattern
+     */
+    MethodHandle component(int i);
+
+    /**
+     * Returns all the component method handles for this pattern as a {@link
+     * List}.
+     *
+     * @return the component method handles
+     */
+    List<MethodHandle> components();
+
+    /**
+     * Returns the descriptor for this pattern.  The parameter types of the
+     * descriptor are the types of the binding variables, and the return type is
+     * the target type.
+     *
+     * @return the pattern type descriptor
+     */
+    MethodType descriptor();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/java/lang/runtime/PatternHandles.java	Thu Jun 20 16:05:18 2019 -0400
@@ -0,0 +1,740 @@
+/*
+ * Copyright (c) 2019, 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.runtime;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+import sun.invoke.util.BytecodeName;
+import sun.invoke.util.Wrapper;
+
+import static java.lang.invoke.MethodHandleInfo.REF_invokeInterface;
+import static java.lang.invoke.MethodHandleInfo.REF_invokeStatic;
+import static java.lang.invoke.MethodHandleInfo.REF_invokeVirtual;
+import static java.lang.invoke.MethodHandleInfo.REF_newInvokeSpecial;
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Factories and combinators for {@link PatternHandle}s.
+ */
+public final class PatternHandles {
+    private static final MethodHandle[] EMPTY_MH_ARRAY = new MethodHandle[0];
+    private static final Object NULL_SENTINEL = new Object();
+
+    private PatternHandles() {
+    }
+
+    // Factories
+
+    /**
+     * Returns a {@linkplain PatternHandle} for a <em>type pattern</em>, which
+     * matches all non-null instances of the match type, with a single binding
+     * variable which is the target cast to the match type.  The target type of
+     * the resulting pattern is the match type; if a broader target type is
+     * desired, use {@link #ofType(Class, Class)} or adapt the resulting pattern
+     * handle with {@link #adaptTarget(PatternHandle, Class)}.
+     *
+     * @param matchType the type to match against
+     * @return a pattern handle for a type pattern
+     */
+    public static PatternHandle ofType(Class<?> matchType) {
+        requireNonNull(matchType);
+        MethodType descriptor = MethodType.methodType(matchType, matchType);
+        MethodHandle component = MethodHandles.identity(matchType);
+        MethodHandle tryMatch
+                = matchType.isPrimitive()
+                  ? MethodHandles.identity(matchType)
+                  : MH_OF_TYPE_TRY_MATCH.bindTo(matchType).asType(descriptor);
+
+        return new PatternHandleImpl(descriptor, tryMatch, List.of(component));
+    }
+
+    /**
+     * Returns a {@linkplain PatternHandle} for a <em>type pattern</em>, which
+     * matches all non-null instances of the match type, with a single binding
+     * variable which is the target cast to the match type.  The target type of
+     * the resulting pattern is the {@code targetType}.
+     *
+     * @param matchType  the type to match against
+     * @param targetType the desired target type for the resulting pattern
+     *                   handle
+     * @return a pattern handle for a type pattern
+     * @throws IllegalArgumentException if the provided match type and target
+     *                                  type are not compatible
+     */
+    public static PatternHandle ofType(Class<?> matchType, Class<?> targetType) {
+        return adaptTarget(ofType(matchType), targetType);
+    }
+
+    /**
+     * Returns a {@linkplain PatternHandle} for a <em>nullable type
+     * pattern</em>, which matches all instances of the match type, plus {@code
+     * null}, with a single binding variable which is the target cast to the
+     * match type.  The target type of the resulting pattern is the match type;
+     * if a broader target type is desired, use {@link #ofType(Class, Class)} or
+     * adapt the resulting pattern handle with {@link #adaptTarget(PatternHandle,
+     * Class)}.
+     *
+     * @param matchType the type to match against
+     * @return a pattern handle for a nullable type pattern
+     */
+    public static PatternHandle ofTypeNullable(Class<?> matchType) {
+        requireNonNull(matchType);
+        MethodType descriptor = MethodType.methodType(matchType, matchType);
+        MethodHandle component = MH_OF_TYPE_NULLABLE_COMPONENT
+                .asType(MethodType.methodType(matchType, Object.class));
+        MethodHandle tryMatch
+                = matchType.isPrimitive()
+                  ? MethodHandles.identity(matchType)
+                  : MH_OF_TYPE_NULLABLE_TRY_MATCH.bindTo(matchType)
+                                                 .asType(MethodType.methodType(Object.class, matchType));
+
+        return new PatternHandleImpl(descriptor, tryMatch, List.of(component));
+    }
+
+    /**
+     * Returns a {@linkplain PatternHandle} for a <em>nullable type
+     * pattern</em>, which matches all instances of the match type, plus {@code
+     * null}, with a single binding variable which is the target cast to the
+     * match type.  The target type of the resulting pattern is the {@code
+     * targetType}.
+     *
+     * @param matchType  the type to match against
+     * @param targetType the desired target type for the resulting pattern
+     *                   handle
+     * @return a pattern handle for a nullable type pattern
+     * @throws IllegalArgumentException if the provided match type and target
+     *                                  type are not compatible
+     */
+    public static PatternHandle ofTypeNullable(Class<?> matchType, Class<?> targetType) {
+        return adaptTarget(ofTypeNullable(matchType), targetType);
+    }
+
+    /**
+     * Returns a {@linkplain PatternHandle} for a <em>constant pattern</em>,
+     * which matches all instances that are {@link Object#equals(Object)} to
+     * the specified constant.  The resulting pattern has no binding variables.
+     * If the constant is {@code null}, the target type of the pattern is
+     * {@link Object}, otherwise it is the result of {@code Object::getClass}
+     * on the constant.
+     *
+     * <p>TODO: restrict type of constant to String, boxes, and enums?
+     *
+     * @param o the constant
+     * @return a pattern handle for a constant pattern
+     */
+    public static PatternHandle ofConstant(Object o) {
+        Class<?> type = o == null ? Object.class : o.getClass();
+        MethodHandle match = partialize(MethodHandles.dropArguments(MethodHandles.constant(Object.class, Boolean.TRUE), 0, type),
+                                        MethodHandles.insertArguments(MH_OBJECTS_EQUAL, 0, o)
+                                                     .asType(MethodType.methodType(boolean.class, type)));
+        return new PatternHandleImpl(MethodType.methodType(type), match, List.of());
+    }
+
+    /**
+     * Returns a {@linkplain PatternHandle} for a <em>constant pattern</em>,
+     * which matches all instances that are {@link Object#equals(Object)} to
+     * the specified constant.  The resulting pattern has no binding variables.
+     * The target type of the pattern is {@code targetType}.
+     *
+     * @param o the constant
+     * @param targetType the target type for the pattern
+     * @return a pattern handle for a constant pattern
+     * @throws IllegalArgumentException if the type of the constant and the
+     * target type are not compatible
+     */
+    public static PatternHandle ofConstant(Object o, Class<?> targetType) {
+        return adaptTarget(ofConstant(0), targetType);
+    }
+
+    // @@@ Primitive constant patterns
+
+    /**
+     * Returns a {@linkplain PatternHandle} for decomposing a target into its
+     * components.  It matches all non-null instances of the specified target
+     * type, and extracts one binding variable for each component specified in
+     * the {@code components} argument.  The method handles in {@code components}
+     * must be of type {@code (T)Bi} where T is the target type of the pattern
+     * and Bi is the i'th binding variable.  The components are extracted
+     * <em>lazily</em> -- when the component method handle is invoked by the
+     * client -- rather than when the {@code tryMatch} method handle is invoked.
+     *
+     * @param targetType The type of the match target
+     * @param components The component method handles
+     * @return a pattern handle for a decomposition pattern
+     */
+    public static PatternHandle ofLazyProjection(Class<?> targetType,
+                                                 MethodHandle... components) {
+        requireNonNull(targetType);
+        requireNonNull(components);
+        return new PatternHandleImpl(descriptor(targetType, components),
+                                     MethodHandles.identity(targetType),
+                                     List.of(components));
+    }
+
+    /**
+     * Returns a {@linkplain PatternHandle} for decomposing a target into its
+     * components.  It matches all non-null instances of the specified target
+     * type, and extracts one binding variable for each component specified in
+     * the {@code components} argument.  The method handles in {@code components}
+     * must be of type {@code (T)Bi} where T is the target type of the pattern
+     * and Bi is the i'th binding variable.  The components are extracted
+     * <em>eagerly</em> -- at the time the {@code tryMatch} method handle is
+     * invoked.
+     *
+     * @param targetType The type of the match target
+     * @param components The component method handles
+     * @return a pattern handle for a decomposition pattern
+     */
+    public static PatternHandle ofEagerProjection(Class<?> targetType,
+                                                  MethodHandle... components) {
+        requireNonNull(targetType);
+        requireNonNull(components);
+        MethodType descriptor = descriptor(targetType, components);
+        return new PatternHandleImpl(descriptor,
+                                     carrierTryExtract(descriptor, components),
+                                     PatternCarriers.carrierComponents(descriptor));
+    }
+
+    /**
+     * Returns a {@linkplain PatternHandle} that delegates matching and
+     * extraction to another method handle.  The target type of the pattern is
+     * the return type of the {@code descriptor}, and the binding variable types
+     * are the parameter types of the {@code descriptor}.  The {@code tryMatch}
+     * method handle will invoke the specified {@code digester} method handle
+     * with the target, as well as a method handle whose parameter types are
+     * the binding variable types and whose return type is some type {@code C}.
+     * For a successful match, the digester method should invoke this method
+     * handle with the extracted bindings, and return the result; for an
+     * unsuccessful match, it should return {@code null}.
+     *
+     * @param descriptor the type descriptor of the pattern
+     * @param digester   the digester method handle
+     * @return a pattern handle implementing the pattern
+     */
+    public static PatternHandle ofImperative(MethodType descriptor,
+                                             MethodHandle digester) {
+        Class<?> targetType = descriptor.returnType();
+        return new PatternHandleImpl(descriptor,
+                                     partialize(MethodHandles.insertArguments(digester,
+                                                                              1, PatternCarriers.carrierFactory(descriptor)),
+                                                MH_OBJECTS_NONNULL.asType(MH_OBJECTS_NONNULL.type().changeParameterType(0, targetType))),
+                                     PatternCarriers.carrierComponents(descriptor));
+    }
+
+    /**
+     * Compose a pattern handle with a method handle that receives the bindings. The
+     * argument types of the target method must match those of the binding
+     * types.  The resulting method handle accepts an argument which is the
+     * target type of the pattern, and which returns either {@code null}
+     * if the match fails or the result of the target method handle
+     * if the match succeeds.
+     *
+     * @param patternHandle the pattern handle
+     * @param target        a method handle that receives the bindings and
+     *                      produces a result
+     * @return the composed method handle
+     */
+    public static MethodHandle compose(PatternHandle patternHandle, MethodHandle target) {
+        int count = patternHandle.descriptor().parameterCount();
+        MethodHandle[] components = patternHandle.components().toArray(EMPTY_MH_ARRAY);
+        Class<?> carrierType = patternHandle.tryMatch().type().returnType();
+        Class<?> resultType = target.type().returnType();
+
+        MethodHandle mh = MethodHandles.filterArguments(target, 0, components);
+        mh = MethodHandles.permuteArguments(mh, MethodType.methodType(resultType, carrierType), new int[count]);
+        mh = MethodHandles.guardWithTest(MH_OBJECTS_NONNULL.asType(MethodType.methodType(boolean.class, carrierType)),
+                                         mh,
+                                         MethodHandles.dropArguments(MethodHandles.constant(resultType, null), 0, carrierType));
+        mh = MethodHandles.filterArguments(mh, 0, patternHandle.tryMatch());
+        return mh;
+    }
+
+    // Combinators
+
+    /**
+     * Adapts a {@linkplain PatternHandle} to a new target type.  If the
+     * pattern is of primitive type, it may be adapted to a supertype of its
+     * corresponding box type; if it is of reference type, it may be widened
+     * or narrowed to another reference type.
+     *
+     * @param pattern the pattern
+     * @param newTarget the new target type
+     * @return the adapted pattern
+     * @throws IllegalArgumentException if the new target type is not compatible
+     * with the target type of the pattern
+     */
+    public static PatternHandle adaptTarget(PatternHandle pattern, Class<?> newTarget) {
+        Class<?> oldTarget = pattern.descriptor().returnType();
+        if (oldTarget == newTarget)
+            return pattern;
+
+        Class<?> oldWrapperType = oldTarget.isPrimitive() ? Wrapper.forPrimitiveType(oldTarget).wrapperType() : null;
+        MethodType guardType = MethodType.methodType(boolean.class, newTarget);
+        MethodHandle guard;
+        if (oldWrapperType != null && newTarget.isAssignableFrom(oldWrapperType)) {
+            // Primitive boxing (with optional widening)
+            guard = MH_PRIMITIVE_ADAPT_HELPER.bindTo(oldWrapperType).asType(guardType);
+        }
+        else if (newTarget.isAssignableFrom(oldTarget) || oldTarget.isAssignableFrom(newTarget)) {
+            // reference narrowing or widening
+            guard = MH_REFERENCE_ADAPT_HELPER.bindTo(oldTarget).asType(guardType);
+        }
+        else {
+            throw new IllegalArgumentException(String.format("New target type %s not compatible with old target type %s",
+                                                             newTarget, oldTarget));
+        }
+
+        MethodType tryMatchType = pattern.tryMatch().type().changeParameterType(0, newTarget);
+        return new PatternHandleImpl(pattern.descriptor().changeReturnType(newTarget),
+                                     partialize(pattern.tryMatch().asType(tryMatchType),
+                                                guard),
+                                     pattern.components());
+    }
+
+    /**
+     * Returns a {@linkplain PatternHandle} that implements the same pattern
+     * as another {@linkplain PatternHandle}, but potentially with fewer binding
+     * variables.
+     *
+     * @param pattern the original pattern
+     * @param positions the indexes of the binding variables to drop
+     * @return the new pattern
+     * @throws IndexOutOfBoundsException if any of the indexes are out of range
+     * for the bindings of the original pattern
+     */
+    public static PatternHandle dropBindings(PatternHandle pattern, int... positions) {
+        MethodHandle[] mhs = pattern.components().toArray(EMPTY_MH_ARRAY);
+        for (int position : positions)
+            mhs[position] = null;
+        mhs = Stream.of(mhs).filter(Objects::nonNull).toArray(MethodHandle[]::new);
+        return new PatternHandleImpl(descriptor(pattern.descriptor().returnType(), mhs), pattern.tryMatch(), List.of(mhs));
+    }
+
+    /**
+     * Returns a {@linkplain PatternHandle} for a <em>nested</em> pattern.  A
+     * nested pattern first matches the target to the outer pattern, and if
+     * it matches successfully, then matches the resulting bindings to the inner
+     * patterns.  The resulting pattern matches if the outer pattern matches
+     * the target, and the bindings match the appropriate inner patterns.  The
+     * target type of the nested pattern is the same as the target type of
+     * the outer pattern.  The bindings are the bindings for the outer pattern,
+     * followed by the concatenation of the bindings for the inner patterns.
+     *
+     * @param outer  The outer pattern
+     * @param inners The inner patterns, which can be null if no nested pattern
+     *               for the corresponding binding is desired
+     * @return the nested pattern
+     */
+    public static PatternHandle nested(PatternHandle outer, PatternHandle... inners) {
+        PatternHandle[] patternHandles = inners.clone();
+        int outerCount = outer.descriptor().parameterCount();
+        Class<?> outerCarrierType = outer.tryMatch().type().returnType();
+
+        // Adapt inners to types of outer bindings
+        for (int i = 0; i < patternHandles.length; i++) {
+            PatternHandle patternHandle = patternHandles[i];
+            if (patternHandle.descriptor().returnType() != outer.descriptor().parameterType(i))
+                patternHandles[i] = adaptTarget(patternHandle, outer.descriptor().parameterType(i));
+        }
+
+        int[] innerPositions = IntStream.range(0, patternHandles.length)
+                                        .filter(i -> patternHandles[i] != null)
+                                        .toArray();
+        MethodHandle[] innerComponents = Stream.of(patternHandles)
+                                               .filter(Objects::nonNull)
+                                               .map(PatternHandle::components)
+                                               .flatMap(List::stream)
+                                               .toArray(MethodHandle[]::new);
+        MethodHandle[] innerTryMatches = Stream.of(patternHandles)
+                                               .filter(Objects::nonNull)
+                                               .map(PatternHandle::tryMatch)
+                                               .toArray(MethodHandle[]::new);
+        Class<?>[] innerCarriers = Stream.of(patternHandles)
+                                         .filter(Objects::nonNull)
+                                         .map(e -> e.tryMatch().type().returnType())
+                                         .toArray(Class[]::new);
+        Class<?>[] innerTypes = Stream.of(innerComponents)
+                                      .map(mh -> mh.type().returnType())
+                                      .toArray(Class[]::new);
+
+        MethodType descriptor = outer.descriptor().appendParameterTypes(innerTypes);
+
+        MethodHandle mh = PatternCarriers.carrierFactory(descriptor);
+        mh = MethodHandles.filterArguments(mh, outerCount, innerComponents);
+        int[] spreadInnerCarriers = new int[outerCount + innerComponents.length];
+        for (int i = 0; i < outerCount; i++)
+            spreadInnerCarriers[i] = i;
+        int k = outerCount;
+        int j = 0;
+        for (PatternHandle e : patternHandles) {
+            if (e == null)
+                continue;
+            for (int i = 0; i < e.descriptor().parameterCount(); i++)
+                spreadInnerCarriers[k++] = outerCount + j;
+            j++;
+        }
+        MethodType spreadInnerCarriersMT = outer.descriptor()
+                                                .appendParameterTypes(innerCarriers)
+                                                .changeReturnType(mh.type().returnType());
+        mh = MethodHandles.permuteArguments(mh, spreadInnerCarriersMT, spreadInnerCarriers);
+        for (int position : innerPositions)
+            mh = bailIfNthNull(mh, outerCount + position);
+        mh = MethodHandles.filterArguments(mh, outerCount, innerTryMatches);
+        int[] spreadNestedCarrier = new int[outerCount + innerPositions.length];
+        for (int i = 0; i < outerCount; i++)
+            spreadNestedCarrier[i] = i;
+        for (int i = 0; i < innerPositions.length; i++)
+            spreadNestedCarrier[outerCount + i] = innerPositions[i];
+        mh = MethodHandles.permuteArguments(mh, outer.descriptor().changeReturnType(mh.type().returnType()),
+                                            spreadNestedCarrier);
+        mh = MethodHandles.filterArguments(mh, 0, outer.components().toArray(EMPTY_MH_ARRAY));
+        mh = MethodHandles.permuteArguments(mh, MethodType.methodType(mh.type().returnType(), outerCarrierType),
+                                            new int[outerCount]);
+        mh = bailIfNthNull(mh, 0);
+        mh = MethodHandles.filterArguments(mh, 0, outer.tryMatch());
+
+        MethodHandle tryExtract = mh;
+
+        return new PatternHandleImpl(descriptor, tryExtract, PatternCarriers.carrierComponents(descriptor));
+    }
+
+    // @@@ AND combinator
+    // @@@ GUARDED combinator
+
+    // Bootstraps
+
+    /**
+     * Bootstrap method for creating a lazy projection pattern, as per
+     * {@link #ofLazyProjection(Class, MethodHandle...)},
+     * suitable for use as a {@code constantdynamic} bootstrap.  Suitable for use
+     * by compilers which are generating implementations of patterns whose bindings
+     * are independently derived from the target.
+     *
+     * @apiNote When the "bootstrap consolidation" project completes, this method
+     * can go away and {@link #ofLazyProjection(Class, MethodHandle...)}
+     * can be used directly as a condy bootstrap.
+     *
+     * @param lookup       ignored
+     * @param constantName ignored
+     * @param constantType Must be {@code PatternHandle.class}
+     * @param targetType   the target type of the pattern
+     * @param components   the pattern components
+     * @return a pattern handle
+     * @throws Throwable doc
+     */
+    public static PatternHandle ofLazyProjection(MethodHandles.Lookup lookup,
+                                                 String constantName,
+                                                 Class<?> constantType,
+                                                 Class<?> targetType,
+                                                 MethodHandle... components)
+            throws Throwable {
+        return ofLazyProjection(targetType, components);
+    }
+
+    /**
+     * Bootstrap method for finding named {@link PatternHandle}s that have been
+     * compiled according to the scheme outlined in JLS ?.?.
+     *
+     * @param lookup       the lookup context
+     * @param constantName ignored
+     * @param constantType must be {@code PatternHandle.class}
+     * @param owner        the class containing the pattern
+     * @param descriptor   the extractor descriptor
+     * @param name         the extractor name
+     * @param refKind      the kind of method
+     * @return the extractor
+     * @throws Throwable if something went wrong
+     */
+    public static PatternHandle ofNamed(MethodHandles.Lookup lookup,
+                                        String constantName,
+                                        Class<PatternHandle> constantType,
+                                        Class<?> owner,
+                                        MethodType descriptor,
+                                        String name,
+                                        int refKind) throws Throwable {
+        String dd = descriptor.toMethodDescriptorString();
+        String memberName = String.format("$pattern$%s$%s",
+                                      (refKind == REF_newInvokeSpecial ? owner.getSimpleName() : name),
+                                      dd.substring(0, dd.indexOf(')') + 1));
+        String patternMethodName = BytecodeName.toBytecodeName(memberName);
+        MethodType factoryDesc = MethodType.methodType(PatternHandle.class);
+        MethodHandle mh;
+        switch (refKind) {
+            case REF_invokeStatic:
+            case REF_newInvokeSpecial:
+                mh = lookup.findStatic(owner, patternMethodName, factoryDesc);
+                break;
+            case REF_invokeVirtual:
+            case REF_invokeInterface:
+                mh = lookup.findVirtual(owner, patternMethodName, factoryDesc);
+                break;
+            default:
+                throw new IllegalAccessException(Integer.toString(refKind));
+        }
+
+        return (PatternHandle) mh.invoke();
+    }
+
+    /**
+     * Bootstrap method for extracting the {@code tryMatch} method handle from a
+     * {@linkplain PatternHandle}.
+     *
+     * @apiNote When the "bootstrap consolidation" project completes, this method
+     * can go away and {@link PatternHandle#tryMatch()} can be used directly as
+     * a condy bootstrap.
+     *
+     * @param lookup        ignored
+     * @param constantName  ignored
+     * @param constantType  Must be {@code MethodHandle.class}
+     * @param patternHandle the pattern handle
+     * @return the {@code tryMatch} method handle
+     */
+    public static MethodHandle tryMatch(MethodHandles.Lookup lookup, String constantName, Class<MethodHandle> constantType,
+                                        PatternHandle patternHandle) {
+        return patternHandle.tryMatch();
+    }
+
+    /**
+     * Bootstrap method for extracting a {@code component} method handle from a
+     * {@linkplain PatternHandle}.
+     *
+     * @apiNote When the "bootstrap consolidation" project completes, this method
+     * can go away and {@link PatternHandle#component(int)} ()} can be used directly as
+     * a condy bootstrap.
+     *
+     * @param lookup        ignored
+     * @param constantName  ignored
+     * @param constantType  Must be {@code MethodHandle.class}
+     * @param patternHandle the pattern
+     * @param i the index of the desired component
+     * @return the component method handle
+     */
+    public static MethodHandle component(MethodHandles.Lookup lookup,
+                                         String constantName,
+                                         Class<MethodHandle> constantType,
+                                         PatternHandle patternHandle, int i) {
+        return patternHandle.component(i);
+    }
+
+    // Helpers
+
+    /**
+     * Construct a partial method handle that uses the predicate as
+     * guardWithTest, which applies the target if the test succeeds, and returns
+     * null if the test fails.  The resulting method handle is of the same type
+     * as the {@code target} method handle.
+     *
+     * @param target
+     * @param predicate
+     * @return
+     */
+    private static MethodHandle partialize(MethodHandle target,
+                                           MethodHandle predicate) {
+        Class<?> targetType = target.type().parameterType(0);
+        Class<?> carrierType = target.type().returnType();
+        return MethodHandles.guardWithTest(predicate,
+                                           target,
+                                           MethodHandles.dropArguments(MethodHandles.constant(carrierType, null),
+                                                                       0, targetType));
+    }
+
+    /**
+     * Construct a method handle that delegates to target, unless the nth
+     * argument is null, in which case it returns null
+     */
+    private static MethodHandle bailIfNthNull(MethodHandle target, int n) {
+        MethodHandle test = MH_OBJECTS_ISNULL
+                .asType(MH_OBJECTS_ISNULL.type()
+                                         .changeParameterType(0, target.type().parameterType(n)));
+        test = MethodHandles.permuteArguments(test, target.type().changeReturnType(boolean.class), n);
+        MethodHandle nullh = MethodHandles.dropArguments(MethodHandles.constant(target.type().returnType(), null),
+                                                         0, target.type().parameterArray());
+        return MethodHandles.guardWithTest(test, nullh, target);
+    }
+
+    private static MethodType descriptor(Class<?> targetType, MethodHandle[] components) {
+        Class<?>[] paramTypes = Stream.of(components)
+                                      .map(mh -> mh.type().returnType())
+                                      .toArray(Class[]::new);
+        return MethodType.methodType(targetType, paramTypes);
+    }
+
+    private static MethodHandle carrierTryExtract(MethodType descriptor, MethodHandle[] components) {
+        MethodHandle carrierFactory = PatternCarriers.carrierFactory(descriptor);
+        int[] reorder = new int[descriptor.parameterCount()]; // default value is what we want already
+
+        Class<?> targetType = descriptor.returnType();
+        return partialize(MethodHandles.permuteArguments(MethodHandles.filterArguments(carrierFactory, 0, components),
+                                                         MethodType.methodType(carrierFactory.type().returnType(), targetType),
+                                                         reorder),
+                          MH_OBJECTS_NONNULL.asType(MH_OBJECTS_NONNULL.type().changeParameterType(0, targetType)));
+    }
+
+    private static MethodHandle lookupStatic(Class<?> clazz,
+                                             String name,
+                                             Class<?> returnType,
+                                             Class<?>... paramTypes)
+            throws ExceptionInInitializerError {
+        try {
+            return MethodHandles.lookup().findStatic(clazz, name, MethodType.methodType(returnType, paramTypes));
+        }
+        catch (ReflectiveOperationException e) {
+            throw new ExceptionInInitializerError(e);
+        }
+    }
+
+    private static final MethodHandle MH_OF_TYPE_TRY_MATCH
+            = lookupStatic(PatternHandles.class, "ofTypeTryMatch",
+                           Object.class, Class.class, Object.class);
+    private static final MethodHandle MH_OF_TYPE_NULLABLE_TRY_MATCH
+            = lookupStatic(PatternHandles.class, "ofTypeNullableTryMatch",
+                           Object.class, Class.class, Object.class);
+    private static final MethodHandle MH_OF_TYPE_NULLABLE_COMPONENT
+            = lookupStatic(PatternHandles.class, "ofTypeNullableComponent",
+                           Object.class, Object.class);
+    private static final MethodHandle MH_PRIMITIVE_ADAPT_HELPER
+            = lookupStatic(PatternHandles.class, "primitiveAdaptHelper",
+                           boolean.class, Class.class, Object.class);
+    private static final MethodHandle MH_REFERENCE_ADAPT_HELPER
+            = lookupStatic(PatternHandles.class, "referenceAdaptHelper",
+                           boolean.class, Class.class, Object.class);
+    private static final MethodHandle MH_OBJECTS_ISNULL
+            = lookupStatic(Objects.class, "isNull",
+                           boolean.class, Object.class);
+    private static final MethodHandle MH_OBJECTS_NONNULL
+            = lookupStatic(Objects.class, "nonNull",
+                           boolean.class, Object.class);
+    private static final MethodHandle MH_OBJECTS_EQUAL
+            = lookupStatic(Objects.class, "equals",
+                           boolean.class, Object.class, Object.class);
+
+    private static Object ofTypeTryMatch(Class<?> type, Object o) {
+        return o != null && type.isAssignableFrom(o.getClass())
+               ? o
+               : null;
+    }
+
+    private static Object ofTypeNullableTryMatch(Class<?> type, Object o) {
+        if (o == null)
+            return NULL_SENTINEL;
+        else if (type.isAssignableFrom(o.getClass()))
+            return o;
+        else
+            return null;
+    }
+
+    private static Object ofTypeNullableComponent(Object o) {
+        return o == NULL_SENTINEL ? null : o;
+    }
+
+    private static boolean primitiveAdaptHelper(Class<?> type, Object o) {
+        return o != null && type.isAssignableFrom(o.getClass());
+    }
+
+    private static boolean referenceAdaptHelper(Class<?> type, Object o) {
+        return o == null || type.isAssignableFrom(o.getClass());
+    }
+
+    /**
+     * Non-public implementation of {@link PatternHandle}
+     */
+    private static class PatternHandleImpl implements PatternHandle {
+
+        private final MethodType descriptor;
+        private final MethodHandle tryMatch;
+        private final List<MethodHandle> components;
+
+
+        /**
+         * Construct an {@link PatternHandle} from components Constraints: -
+         * output of tryMatch must match input of components - input of tryMatch
+         * must match descriptor - output of components must match descriptor
+         *
+         * @param descriptor The {@code descriptor} method type
+         * @param tryMatch   The {@code tryMatch} method handle
+         * @param components The {@code component} method handles
+         */
+        PatternHandleImpl(MethodType descriptor, MethodHandle tryMatch,
+                          List<MethodHandle> components) {
+            MethodHandle[] componentsArray = components.toArray(new MethodHandle[0]);
+            Class<?> carrierType = tryMatch.type().returnType();
+            if (descriptor.parameterCount() != componentsArray.length)
+                throw new IllegalArgumentException(String.format("MethodType %s arity should match component count %d",
+                                                                 descriptor, componentsArray.length));
+            if (!descriptor.returnType().equals(tryMatch.type().parameterType(0)))
+                throw new IllegalArgumentException(String.format("Descriptor %s should match tryMatch input %s",
+                                                                 descriptor, tryMatch.type()));
+            for (int i = 0; i < componentsArray.length; i++) {
+                MethodType componentType = componentsArray[i].type();
+                if (componentType.parameterCount() != 1
+                    || componentType.returnType().equals(void.class)
+                    || !componentType.parameterType(0).equals(carrierType))
+                    throw new IllegalArgumentException("Invalid component descriptor " + componentType);
+                if (!componentType.returnType().equals(descriptor.parameterType(i)))
+                    throw new IllegalArgumentException(String.format("Descriptor %s should match %d'th component %s",
+                                                                     descriptor, i, componentsArray[i]));
+            }
+
+            if (!carrierType.equals(Object.class)) {
+                tryMatch = tryMatch.asType(tryMatch.type().changeReturnType(Object.class));
+                for (int i = 0; i < componentsArray.length; i++) {
+                    MethodHandle component = componentsArray[i];
+                    componentsArray[i] = component.asType(component.type().changeParameterType(0, Object.class));
+                }
+            }
+
+            this.descriptor = descriptor;
+            this.tryMatch = tryMatch;
+            this.components = List.of(componentsArray);
+        }
+
+        @Override
+        public MethodHandle tryMatch() {
+            return tryMatch;
+        }
+
+        @Override
+        public MethodHandle component(int i) {
+            return components.get(i);
+        }
+
+        @Override
+        public List<MethodHandle> components() {
+            return components;
+        }
+
+        @Override
+        public MethodType descriptor() {
+            return descriptor;
+        }
+
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java	Thu Jun 20 16:05:18 2019 -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.runtime;
+
+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,
+                                         PatternHandle... 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++) {
+            PatternHandle pattern = patterns[i];
+            if (pattern.descriptor().returnType() != targetType)
+                patterns[i] = PatternHandles.adaptTarget(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 PatternHandle[] patterns;
+
+        PatternSwitchCallSite(MethodType targetType,
+                              PatternHandle[] 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++) {
+                PatternHandle e = patterns[i];
+                Object o = e.tryMatch().invoke(target);
+                if (o != null)
+                    return new PatternSwitchResult(i, o);
+            }
+
+            return new PatternSwitchResult(patterns.length, null);
+
+        }
+    }
+}
--- a/src/java.base/share/classes/module-info.java	Fri Jun 14 15:59:12 2019 -0400
+++ b/src/java.base/share/classes/module-info.java	Thu Jun 20 16:05:18 2019 -0400
@@ -84,6 +84,7 @@
     exports java.lang.module;
     exports java.lang.ref;
     exports java.lang.reflect;
+    exports java.lang.runtime;
     exports java.math;
     exports java.net;
     exports java.net.spi;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/lang-runtime/PatternHandleTest.java	Thu Jun 20 16:05:18 2019 -0400
@@ -0,0 +1,445 @@
+/*
+ * 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.  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.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.runtime.PatternHandle;
+import java.lang.runtime.PatternHandles;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNotSame;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertSame;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+/**
+ * @test
+ * @run testng PatternHandleTest
+ * @summary Smoke tests for java.lang.runtime.Extractor
+ */
+@Test
+public class PatternHandleTest {
+
+    enum MatchKind {
+        /** Match succeeds, with a carrier object different form the target */
+        MATCH_CARRIER,
+        /** Match succeeds, with self-carrier */
+        MATCH_SELF,
+        /** Match succeeds, carrier provenance unknown */
+        MATCH,
+        /** Match fails */
+        NO_MATCH,
+        /** Match fails with a runtime exception */
+        ERROR;
+    }
+
+    // We have to resort to the subterfuge of laundering the tryMatch invocation
+    // through an ancillary object, because the only way to control the static
+    // signature is through the static types.  So we have a bunch of invokers,
+    // each of which embeds different assumptions about the target type.  This
+    // way we can test mismatches between the expected and actual types.
+
+    interface TryMatchInvoker<T> {
+        Object tryMatch(MethodHandle mh, T target) throws Throwable;
+    }
+
+    static final TryMatchInvoker<Object> objectInvoker = (MethodHandle mh, Object x) -> mh.invokeExact(x);
+    static final TryMatchInvoker<Number> numberInvoker = (MethodHandle mh, Number x) -> mh.invokeExact(x);
+    static final TryMatchInvoker<Integer> integerInvoker = (MethodHandle mh, Integer x) -> mh.invokeExact(x);
+    static final TryMatchInvoker<Integer> intInvoker = (MethodHandle mh, Integer x) -> mh.invokeExact((int) x);
+    static final TryMatchInvoker<String> stringInvoker = (MethodHandle mh, String x) -> mh.invokeExact(x);
+    static final TryMatchInvoker<List> listInvoker = (MethodHandle mh, List x) -> mh.invokeExact(x);
+    static final TryMatchInvoker<TestClass> testClassInvoker = (MethodHandle mh, TestClass x) -> mh.invokeExact(x);
+    static final TryMatchInvoker<TestClass2> testClass2Invoker = (MethodHandle mh, TestClass2 x) -> mh.invokeExact(x);
+    static final TryMatchInvoker rawObjectInvoker = objectInvoker;
+    static final TryMatchInvoker rawStringInvoker = stringInvoker;
+    static final TryMatchInvoker rawIntegerInvoker = integerInvoker;
+    static final TryMatchInvoker rawIntInvoker = intInvoker;
+
+    interface Throwing {
+        public void run() throws Throwable;
+    }
+    static void assertThrows(Class<? extends Throwable> exception, Throwing r) {
+        try {
+            r.run();
+            fail("Expected exception: " + exception);
+        }
+        catch (Throwable t) {
+            if (!exception.isAssignableFrom(t.getClass()))
+                fail(String.format("Expected exception %s, got %s", exception, t.getClass()), t);
+        }
+    }
+
+    static<T> void assertMatch(MatchKind expected,
+                               PatternHandle e,
+                               TryMatchInvoker<T> invoker,
+                               T target,
+                               Object... expectedBindings) throws Throwable {
+        int count = e.descriptor().parameterCount();
+        Object[] bindings = new Object[count];
+        Object carrier;
+        try {
+            carrier = invoker.tryMatch(e.tryMatch(), target);
+        }
+        catch (Throwable t) {
+            carrier = null;
+            if (expected == MatchKind.ERROR)
+                return;
+            else
+                fail("Unexpected exception in tryMatch", t);
+        }
+
+        if (carrier != null) {
+            for (int i = 0; i < count; i++)
+                bindings[i] = e.component(i).invoke(carrier);
+        }
+
+        if (expected == MatchKind.NO_MATCH)
+            assertNull(carrier);
+        else {
+            assertNotNull(carrier);
+            assertEquals(bindings.length, expectedBindings.length);
+            for (int i = 0; i < expectedBindings.length; i++)
+                assertEquals(bindings[i], expectedBindings[i]);
+
+            if (expected == MatchKind.MATCH_SELF)
+                assertSame(carrier, target);
+            else if (expected == MatchKind.MATCH_CARRIER)
+                assertNotSame(carrier, target);
+        }
+    }
+
+    private static class TestClass {
+        static TestClass INSTANCE_A = new TestClass("foo", 3, 4L, (byte) 5);
+        static TestClass INSTANCE_B = new TestClass(null, 0, 0L, (byte) 0);
+        static TestClass INSTANCE_C = new TestClass("foo", 2, 4L, (byte) 5);
+        static Object[] COMPONENTS_A = new Object[] { "foo", 3, 4L, (byte) 5 };
+        static Object[] COMPONENTS_B = new Object[] { null, 0, 0L, (byte) 0 };
+        static Object[] COMPONENTS_C = new Object[] { "foo", 2, 4L, (byte) 5 };
+
+        static Map<TestClass, Object[]> INSTANCES = Map.of(INSTANCE_A, COMPONENTS_A,
+                                                           INSTANCE_B, COMPONENTS_B,
+                                                           INSTANCE_C, COMPONENTS_C);
+
+        static MethodHandle MH_S, MH_I, MH_L, MH_B, MH_PRED;
+        static MethodHandle CONSTRUCTOR;
+        static MethodHandle DIGESTER;
+        static MethodHandle DIGESTER_PARTIAL;
+        static MethodType TYPE = MethodType.methodType(TestClass.class, String.class, int.class, long.class, byte.class);
+        static {
+            try {
+                MH_B = MethodHandles.lookup().findGetter(TestClass.class, "b", byte.class);
+                MH_S = MethodHandles.lookup().findGetter(TestClass.class, "s", String.class);
+                MH_I = MethodHandles.lookup().findGetter(TestClass.class, "i", int.class);
+                MH_L = MethodHandles.lookup().findGetter(TestClass.class, "l", long.class);
+                MH_PRED = MethodHandles.lookup().findVirtual(TestClass.class, "matches", MethodType.methodType(boolean.class));
+                CONSTRUCTOR = MethodHandles.lookup().findConstructor(TestClass.class, TYPE.changeReturnType(void.class));
+                DIGESTER = MethodHandles.lookup().findVirtual(TestClass.class, "digest", MethodType.methodType(Object.class, MethodHandle.class));
+                DIGESTER_PARTIAL = MethodHandles.lookup().findVirtual(TestClass.class, "digestPartial", MethodType.methodType(Object.class, MethodHandle.class));
+            }
+            catch (ReflectiveOperationException e) {
+                throw new ExceptionInInitializerError(e);
+            }
+        }
+        static MethodHandle[] COMPONENT_MHS = {TestClass.MH_S, TestClass.MH_I, TestClass.MH_L, TestClass.MH_B };
+
+        String s;
+        int i;
+        long l;
+        byte b;
+
+        TestClass(String s, int i, long l, byte b) {
+            this.s = s;
+            this.i = i;
+            this.l = l;
+            this.b = b;
+        }
+
+        TestClass copy() {
+            return new TestClass(s, i, l, b);
+        }
+
+        boolean matches() { return s != null && s.length() == i; }
+
+        Object digest(MethodHandle target) throws Throwable {
+            return target.invoke(s, i, l, b);
+        }
+
+        Object digestPartial(MethodHandle target) throws Throwable {
+            return matches() ? target.invoke(s, i, l, b) : null;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            TestClass aClass = (TestClass) o;
+            return i == aClass.i &&
+                   l == aClass.l &&
+                   b == aClass.b &&
+                   Objects.equals(s, aClass.s);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(s, i, l, b);
+        }
+    }
+
+    private static class TestClass2 {
+        static MethodHandle MH_X;
+        static MethodType TYPE = MethodType.methodType(TestClass2.class, Object.class);
+        static {
+            try {
+                MH_X = MethodHandles.lookup().findGetter(TestClass2.class, "x", Object.class);
+            }
+            catch (ReflectiveOperationException e) {
+                throw new ExceptionInInitializerError(e);
+            }
+        }
+
+        Object x;
+
+        public TestClass2(Object x) {
+            this.x = x;
+        }
+    }
+
+    PatternHandle TYPE_STRING = PatternHandles.ofType(String.class);
+    PatternHandle TYPE_LIST = PatternHandles.ofType(List.class);
+    PatternHandle TYPE_INTEGER = PatternHandles.ofType(Integer.class);
+    PatternHandle TYPE_NUMBER = PatternHandles.ofType(Number.class);
+    PatternHandle TYPE_OBJECT = PatternHandles.ofType(Object.class);
+    PatternHandle TYPE_INT = PatternHandles.ofType(int.class);
+    PatternHandle TYPE_STRING_NULLABLE = PatternHandles.ofTypeNullable(String.class);
+
+    public void testType() throws Throwable {
+        assertMatch(MatchKind.MATCH_SELF, TYPE_STRING, stringInvoker, "Foo", "Foo");
+        assertMatch(MatchKind.NO_MATCH, TYPE_STRING, stringInvoker, null);
+        assertMatch(MatchKind.ERROR, TYPE_STRING, rawStringInvoker, List.of());
+        assertMatch(MatchKind.ERROR, TYPE_STRING, rawStringInvoker, 3);
+
+        assertMatch(MatchKind.MATCH_SELF, TYPE_LIST, listInvoker, List.of(3), List.of(3));
+        assertMatch(MatchKind.MATCH_SELF, TYPE_LIST, listInvoker, List.of(), List.of());
+        assertMatch(MatchKind.MATCH_SELF, TYPE_LIST, listInvoker, new ArrayList<>(), List.of());
+        assertMatch(MatchKind.NO_MATCH, TYPE_LIST, listInvoker, null);
+
+        assertMatch(MatchKind.MATCH_SELF, TYPE_INTEGER, integerInvoker, 3, 3);
+        assertMatch(MatchKind.MATCH_SELF, TYPE_NUMBER, numberInvoker, 3, 3);
+        assertMatch(MatchKind.MATCH_SELF, TYPE_OBJECT, objectInvoker, 3, 3);
+        assertMatch(MatchKind.NO_MATCH, TYPE_OBJECT, objectInvoker, null);
+
+        assertMatch(MatchKind.ERROR, TYPE_INTEGER, rawIntegerInvoker, 3.14f);
+        assertMatch(MatchKind.ERROR, TYPE_INTEGER, rawIntegerInvoker, "foo");
+    }
+
+    public void testPrimitiveType() throws Throwable {
+        assertMatch(MatchKind.MATCH_SELF, TYPE_INT, intInvoker, 3, 3);
+        assertMatch(MatchKind.ERROR, TYPE_INT, rawIntInvoker, 3.14f);
+
+        PatternHandle asObject = PatternHandles.adaptTarget(TYPE_INT, Object.class);
+        assertMatch(MatchKind.MATCH_SELF, asObject, objectInvoker, 3, 3);
+        assertMatch(MatchKind.NO_MATCH, asObject, objectInvoker, 3.14f);
+        assertMatch(MatchKind.NO_MATCH, asObject, objectInvoker, null);
+
+        PatternHandle asInteger = PatternHandles.adaptTarget(TYPE_INT, Integer.class);
+        assertMatch(MatchKind.MATCH_SELF, asInteger, integerInvoker, 3, 3);
+        assertMatch(MatchKind.NO_MATCH, asInteger, integerInvoker, null);
+        assertMatch(MatchKind.ERROR, asInteger, rawIntegerInvoker, 3.14f);
+    }
+
+    public void testNullableType() throws Throwable {
+        assertMatch(MatchKind.MATCH_SELF, TYPE_STRING_NULLABLE, stringInvoker, "Foo", "Foo");
+        assertMatch(MatchKind.MATCH, TYPE_STRING_NULLABLE, stringInvoker, null, (Object) null);
+        assertMatch(MatchKind.ERROR, TYPE_STRING_NULLABLE, rawStringInvoker, 3);
+
+        PatternHandle asObjectNullable = PatternHandles.adaptTarget(TYPE_STRING_NULLABLE, Object.class);
+        assertMatch(MatchKind.MATCH_SELF, asObjectNullable, objectInvoker, "Foo", "Foo");
+        assertMatch(MatchKind.MATCH, asObjectNullable, objectInvoker, null, (Object) null);
+        assertMatch(MatchKind.NO_MATCH, asObjectNullable, objectInvoker, 3);
+    }
+
+    public void testAdapt() throws Throwable {
+        PatternHandle e = PatternHandles.ofTypeNullable(Number.class);
+        PatternHandle n = PatternHandles.adaptTarget(e, Integer.class);
+        PatternHandle w = PatternHandles.adaptTarget(e, Object.class);
+
+        assertEquals(e.descriptor().returnType(), Number.class);
+        assertEquals(n.descriptor().returnType(), Integer.class);
+        assertEquals(w.descriptor().returnType(), Object.class);
+
+        assertMatch(MatchKind.MATCH_SELF, e, numberInvoker, 1, 1);
+        assertMatch(MatchKind.MATCH_SELF, n, integerInvoker, 1, 1);
+        assertMatch(MatchKind.MATCH_SELF, w, objectInvoker, 1, 1);
+
+        assertMatch(MatchKind.MATCH_SELF, e, numberInvoker, 3.14f, 3.14f);
+        assertMatch(MatchKind.ERROR, n, rawIntegerInvoker, 3.14f);
+        assertMatch(MatchKind.MATCH_SELF, w, objectInvoker, 3.14f, 3.14f);
+
+        assertMatch(MatchKind.MATCH, e, numberInvoker, null, (Object) null);
+        assertMatch(MatchKind.MATCH, n, integerInvoker, null, (Object) null);
+        assertMatch(MatchKind.MATCH, w, objectInvoker, null, (Object) null);
+
+        e = PatternHandles.ofType(Number.class);
+        n = PatternHandles.adaptTarget(e, Integer.class);
+        w = PatternHandles.adaptTarget(e, Object.class);
+
+        assertMatch(MatchKind.MATCH_SELF, e, numberInvoker, 1, 1);
+        assertMatch(MatchKind.MATCH_SELF, n, integerInvoker, 1, 1);
+        assertMatch(MatchKind.MATCH_SELF, w, objectInvoker, 1, 1);
+        assertMatch(MatchKind.NO_MATCH, e, numberInvoker, null);
+        assertMatch(MatchKind.NO_MATCH, n, integerInvoker, null);
+        assertMatch(MatchKind.NO_MATCH, w, objectInvoker, null);
+
+        PatternHandle widenNarrow = PatternHandles.adaptTarget(PatternHandles.adaptTarget(TYPE_STRING, Object.class), String.class);
+        assertMatch(MatchKind.MATCH_SELF, widenNarrow, stringInvoker, "Foo", "Foo");
+        assertMatch(MatchKind.NO_MATCH, widenNarrow, stringInvoker, null);
+        assertMatch(MatchKind.ERROR, widenNarrow, rawStringInvoker, List.of());
+        assertMatch(MatchKind.ERROR, widenNarrow, rawStringInvoker, 3);
+
+        PatternHandle widenNarrowNullable = PatternHandles.adaptTarget(PatternHandles.adaptTarget(TYPE_STRING_NULLABLE, Object.class), String.class);
+        assertMatch(MatchKind.MATCH_SELF, widenNarrowNullable, stringInvoker, "Foo", "Foo");
+        assertMatch(MatchKind.MATCH, widenNarrowNullable, stringInvoker, null, (Object) null);
+        assertMatch(MatchKind.ERROR, widenNarrowNullable, rawStringInvoker, List.of());
+        assertMatch(MatchKind.ERROR, widenNarrowNullable, rawStringInvoker, 3);
+    }
+
+    public void testConstant() throws Throwable {
+        PatternHandle constantFoo = PatternHandles.ofConstant("foo");
+        assertMatch(MatchKind.MATCH, constantFoo, stringInvoker, "foo");
+        assertMatch(MatchKind.NO_MATCH, constantFoo, stringInvoker, "bar");
+        assertMatch(MatchKind.ERROR, constantFoo, rawStringInvoker, 3);
+        assertMatch(MatchKind.NO_MATCH, constantFoo, stringInvoker, null);
+
+        PatternHandle constantThree = PatternHandles.ofConstant(3);
+        assertMatch(MatchKind.MATCH, constantThree, integerInvoker, 3);
+        assertMatch(MatchKind.NO_MATCH, constantThree, integerInvoker, 4);
+        assertMatch(MatchKind.NO_MATCH, constantThree, integerInvoker, null);
+    }
+
+    public void testNullConstant() throws Throwable {
+        PatternHandle constantNull = PatternHandles.ofConstant(null);
+        assertMatch(MatchKind.MATCH, constantNull, objectInvoker, null);
+        assertMatch(MatchKind.NO_MATCH, constantNull, objectInvoker, "foo");
+        assertMatch(MatchKind.NO_MATCH, constantNull, objectInvoker, 3);
+    }
+
+    public void testProjections() throws Throwable {
+        Map<PatternHandle, MatchKind> m
+                = Map.of(PatternHandles.ofLazyProjection(TestClass.class, TestClass.COMPONENT_MHS), MatchKind.MATCH_SELF,
+                         PatternHandles.ofEagerProjection(TestClass.class, TestClass.COMPONENT_MHS), MatchKind.MATCH_CARRIER);
+        for (var ps : m.entrySet()) {
+            for (var entry : TestClass.INSTANCES.entrySet()) {
+                assertMatch(ps.getValue(), ps.getKey(), testClassInvoker, entry.getKey(), entry.getValue());
+            }
+            assertMatch(MatchKind.NO_MATCH, ps.getKey(), testClassInvoker, null);
+
+            PatternHandle asObject = PatternHandles.adaptTarget(ps.getKey(), Object.class);
+            for (var entry : TestClass.INSTANCES.entrySet())
+                assertMatch(ps.getValue(), asObject, objectInvoker, entry.getKey(), entry.getValue());
+            assertMatch(MatchKind.NO_MATCH, asObject, objectInvoker, null);
+
+            PatternHandle asTestClassAgain = PatternHandles.adaptTarget(asObject, TestClass.class);
+            for (var entry : TestClass.INSTANCES.entrySet())
+                assertMatch(ps.getValue(), asTestClassAgain, testClassInvoker, entry.getKey(), entry.getValue());
+            assertMatch(MatchKind.NO_MATCH, asTestClassAgain, testClassInvoker, null);
+        }
+    }
+
+    public void testDigest() throws Throwable {
+        PatternHandle e = PatternHandles.ofImperative(TestClass.TYPE, TestClass.DIGESTER);
+        for (var entry : TestClass.INSTANCES.entrySet())
+            assertMatch(MatchKind.MATCH_CARRIER, e, testClassInvoker, entry.getKey(), entry.getValue());
+        assertMatch(MatchKind.NO_MATCH, e, testClassInvoker, null);
+    }
+
+    public void testDigestPartial() throws Throwable {
+        PatternHandle e = PatternHandles.ofImperative(TestClass.TYPE, TestClass.DIGESTER_PARTIAL);
+        for (var entry : TestClass.INSTANCES.entrySet()) {
+            if (entry.getKey().matches())
+                assertMatch(MatchKind.MATCH_CARRIER, e, testClassInvoker, entry.getKey(), entry.getValue());
+            else
+                assertMatch(MatchKind.NO_MATCH, e, testClassInvoker, entry.getKey());
+        }
+        assertMatch(MatchKind.NO_MATCH, e, testClassInvoker, null);
+    }
+
+    public void testCompose() throws Throwable {
+        PatternHandle e = PatternHandles.ofLazyProjection(TestClass.class, TestClass.COMPONENT_MHS);
+        MethodHandle mh = PatternHandles.compose(e, TestClass.CONSTRUCTOR);
+        TestClass target = TestClass.INSTANCE_A;
+        Object o = mh.invoke(target);
+        assertTrue(o instanceof TestClass);
+        assertNotSame(target, o);
+        assertEquals(target, o);
+
+        assertNull(mh.invoke((Object) null));
+    }
+
+    public void testDropBindings() throws Throwable {
+        PatternHandle e = PatternHandles.ofEagerProjection(TestClass.class, TestClass.COMPONENT_MHS);
+        assertMatch(MatchKind.MATCH_CARRIER, e, testClassInvoker, TestClass.INSTANCE_A,
+                    TestClass.COMPONENTS_A);
+        assertMatch(MatchKind.MATCH_CARRIER, PatternHandles.dropBindings(e, 0), testClassInvoker, TestClass.INSTANCE_A,
+                    3, 4L, (byte) 5);
+        assertMatch(MatchKind.MATCH_CARRIER, PatternHandles.dropBindings(e, 0, 0), testClassInvoker, TestClass.INSTANCE_A,
+                    3, 4L, (byte) 5);
+        assertMatch(MatchKind.MATCH_CARRIER, PatternHandles.dropBindings(e, 3), testClassInvoker, TestClass.INSTANCE_A,
+                    "foo", 3, 4L);
+        assertMatch(MatchKind.MATCH_CARRIER, PatternHandles.dropBindings(e, 0, 1, 2, 3), testClassInvoker, TestClass.INSTANCE_A);
+
+        assertThrows(IndexOutOfBoundsException.class,
+                     () -> assertMatch(MatchKind.MATCH_CARRIER, PatternHandles.dropBindings(e, -1), testClassInvoker, TestClass.INSTANCE_A,
+                                       3, 4L, (byte) 5));
+        assertThrows(IndexOutOfBoundsException.class,
+                     () -> assertMatch(MatchKind.MATCH_CARRIER, PatternHandles.dropBindings(e, 4), testClassInvoker, TestClass.INSTANCE_A,
+                                       3, 4L, (byte) 5));
+    }
+
+    public void testNested() throws Throwable {
+        PatternHandle TC2 = PatternHandles.ofLazyProjection(TestClass2.class, TestClass2.MH_X);
+        PatternHandle TC2_STRING = PatternHandles.nested(TC2, TYPE_STRING);
+        PatternHandle TC2_OBJECT = PatternHandles.nested(TC2, TYPE_OBJECT);
+
+        assertMatch(MatchKind.MATCH_CARRIER, PatternHandles.dropBindings(TC2_STRING, 0), testClass2Invoker, new TestClass2("foo"),
+                    "foo");
+        assertMatch(MatchKind.MATCH_CARRIER, PatternHandles.dropBindings(TC2_OBJECT, 0), testClass2Invoker, new TestClass2("foo"),
+                    "foo");
+        assertMatch(MatchKind.NO_MATCH, PatternHandles.dropBindings(TC2_STRING, 0), testClass2Invoker, new TestClass2(List.of(3)),
+                    "foo");
+
+        assertMatch(MatchKind.MATCH_CARRIER, PatternHandles.dropBindings(PatternHandles.nested(TC2, TC2_STRING), 0, 1), testClass2Invoker, new TestClass2(new TestClass2("foo")),
+                    "foo");
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/lang-runtime/SwitchBootstrapsTest.java	Thu Jun 20 16:05:18 2019 -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.runtime.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
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/lang-runtime/boottest/TEST.properties	Thu Jun 20 16:05:18 2019 -0400
@@ -0,0 +1,3 @@
+# This file identifies root(s) of the test-ng hierarchy.
+
+TestNG.dirs = .
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/lang-runtime/boottest/java.base/java/lang/runtime/CarrierTest.java	Thu Jun 20 16:05:18 2019 -0400
@@ -0,0 +1,118 @@
+/*
+ * 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.
+ */
+package java.lang.runtime;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodType;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+
+/**
+ * @test
+ * @key randomness
+ * @run testng CarrierTest
+ * @summary unit tests for java.lang.runtime.ExtractorCarriers
+ */
+@Test
+public class CarrierTest {
+    static final int N_ITER = 1000;
+    static final Class<?>[] TYPES = { byte.class, short.class, char.class, int.class, long.class, float.class, double.class, boolean.class, Object.class };
+
+    static Object[] byteVals = { Byte.MIN_VALUE, Byte.MAX_VALUE, (byte) -1, (byte) 0, (byte) 1, (byte) 42 };
+    static Object[] shortVals = { Short.MIN_VALUE, Short.MAX_VALUE, (short) -1, (short) 0, (short) 1, (short) 42 };
+    static Object[] charVals = { Character.MIN_VALUE, Character.MAX_VALUE, (char) 0, 'a', 'Z' };
+    static Object[] intVals = { Integer.MIN_VALUE, Integer.MAX_VALUE, -1, 0, 1, 42 };
+    static Object[] longVals = { Long.MIN_VALUE, Long.MAX_VALUE, -1L, 0L, 1L, 42L };
+    static Object[] floatVals = { Float.MIN_VALUE, Float.MAX_VALUE, -1.0f, 0.0f, 1.0f, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY, Float.NaN };
+    static Object[] doubleVals = { Double.MIN_VALUE, Double.MAX_VALUE, -1.0d, 0.0d, 1.0d, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.NaN };
+    static Object[] booleanVals = { true, false };
+    static Object[] objectVals = {null, "", "Foo", "foo", List.of(), Collections.EMPTY_SET };
+
+    // @@@ Should use RandomFactory, but can't get that to link
+    private static final Random random = new Random(System.nanoTime());
+
+    static Map<Class<?>, Object[]> primVals = Map.of(byte.class, byteVals,
+                                                     short.class, shortVals,
+                                                     char.class, charVals,
+                                                     int.class, intVals,
+                                                     long.class, longVals,
+                                                     float.class, floatVals,
+                                                     double.class, doubleVals,
+                                                     boolean.class, booleanVals);
+
+    void testCarrier(MethodType type, Object[] values) throws Throwable {
+        for (PatternCarriers.CarrierFactory cf : PatternCarriers.CarrierFactories.values()) {
+            assertEquals(type.parameterCount(), values.length);
+            Object carrier = cf.constructor(type).invokeWithArguments(values);
+            for (int i = 0; i < values.length; i++)
+                assertEquals(values[i], cf.component(type, i).invoke(carrier));
+        }
+    }
+
+    void testCarrier(MethodType type) throws Throwable {
+        // generate data, in a loop
+        for (int i=0; i<N_ITER; i++) {
+            Object[] values = new Object[type.parameterCount()];
+            for (int j=0; j<type.parameterCount(); j++) {
+                Class<?> c = type.parameterType(j);
+                Object[] vals = c.isPrimitive() ? primVals.get(c) : objectVals;
+                values[j] = vals[random.nextInt(vals.length)];
+            }
+            testCarrier(type, values);
+        }
+    }
+
+    public void testCarrier() throws Throwable {
+        Class[] lotsOfInts = new Class[252];
+        Arrays.fill(lotsOfInts, int.class);
+
+        // known types
+        for (MethodType mt : List.of(
+                MethodType.methodType(Object.class),
+                MethodType.methodType(Object.class, int.class),
+                MethodType.methodType(Object.class, int.class, int.class),
+                MethodType.methodType(Object.class, Object.class),
+                MethodType.methodType(Object.class, Object.class, Object.class),
+                MethodType.methodType(Object.class, byte.class, short.class, char.class, int.class, long.class, float.class, double.class, boolean.class),
+                MethodType.methodType(Object.class, lotsOfInts))) {
+            testCarrier(mt);
+        }
+
+        // random types
+        for (int i=0; i<N_ITER; i++) {
+            int nTypes = random.nextInt(10);
+            Class[] paramTypes = new Class[nTypes];
+            Arrays.setAll(paramTypes, ix -> TYPES[random.nextInt(TYPES.length)]);
+            testCarrier(MethodType.methodType(Object.class, paramTypes));
+        }
+    }
+}