OpenJDK / amber / amber
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)); + } + } +}