8268766: Desugaring of pattern matching enum switch should be improved

Reviewed-by: mcimadamore, psandoz
This commit is contained in:
Jan Lahoda 2021-07-08 11:56:53 +00:00
parent 4f70759175
commit fa08cc62df
7 changed files with 422 additions and 134 deletions

View file

@ -26,12 +26,11 @@
package java.lang.runtime;
import java.lang.invoke.CallSite;
import java.lang.invoke.ConstantBootstraps;
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.Objects;
import java.util.stream.Stream;
import jdk.internal.javac.PreviewFeature;
@ -53,12 +52,15 @@ public class SwitchBootstraps {
private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
private static final MethodHandle DO_SWITCH;
private static final MethodHandle DO_TYPE_SWITCH;
private static final MethodHandle DO_ENUM_SWITCH;
static {
try {
DO_SWITCH = LOOKUP.findStatic(SwitchBootstraps.class, "doSwitch",
DO_TYPE_SWITCH = LOOKUP.findStatic(SwitchBootstraps.class, "doTypeSwitch",
MethodType.methodType(int.class, Object.class, int.class, Object[].class));
DO_ENUM_SWITCH = LOOKUP.findStatic(SwitchBootstraps.class, "doEnumSwitch",
MethodType.methodType(int.class, Enum.class, int.class, Object[].class));
}
catch (ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e);
@ -108,14 +110,13 @@ public class SwitchBootstraps {
* second parameter of type {@code int} and with {@code int} as its return type,
* or if {@code labels} contains an element that is not of type {@code String},
* {@code Integer} or {@code Class}.
* @throws Throwable if there is any error linking the call site
* @jvms 4.4.6 The CONSTANT_NameAndType_info Structure
* @jvms 4.4.10 The CONSTANT_Dynamic_info and CONSTANT_InvokeDynamic_info Structures
*/
public static CallSite typeSwitch(MethodHandles.Lookup lookup,
String invocationName,
MethodType invocationType,
Object... labels) throws Throwable {
Object... labels) {
if (invocationType.parameterCount() != 2
|| (!invocationType.returnType().equals(int.class))
|| invocationType.parameterType(0).isPrimitive()
@ -126,7 +127,7 @@ public class SwitchBootstraps {
labels = labels.clone();
Stream.of(labels).forEach(SwitchBootstraps::verifyLabel);
MethodHandle target = MethodHandles.insertArguments(DO_SWITCH, 2, (Object) labels);
MethodHandle target = MethodHandles.insertArguments(DO_TYPE_SWITCH, 2, (Object) labels);
return new ConstantCallSite(target);
}
@ -142,7 +143,7 @@ public class SwitchBootstraps {
}
}
private static int doSwitch(Object target, int startIndex, Object[] labels) {
private static int doTypeSwitch(Object target, int startIndex, Object[] labels) {
if (target == null)
return -1;
@ -167,4 +168,124 @@ public class SwitchBootstraps {
return labels.length;
}
/**
* Bootstrap method for linking an {@code invokedynamic} call site that
* implements a {@code switch} on a target of an enum type. The static
* arguments are used to encode the case labels associated to the switch
* construct, where each label can be encoded in two ways:
* <ul>
* <li>as a {@code String} value, which represents the name of
* the enum constant associated with the label</li>
* <li>as a {@code Class} value, which represents the enum type
* associated with a type test pattern</li>
* </ul>
* <p>
* The returned {@code CallSite}'s method handle will have
* a return type of {@code int} and accepts two parameters: the first argument
* will be an {@code Enum} instance ({@code target}) and the second
* will be {@code int} ({@code restart}).
* <p>
* If the {@code target} is {@code null}, then the method of the call site
* returns {@literal -1}.
* <p>
* If the {@code target} is not {@code null}, then the method of the call site
* returns the index of the first element in the {@code labels} array starting from
* the {@code restart} index matching one of the following conditions:
* <ul>
* <li>the element is of type {@code Class} that is assignable
* from the target's class; or</li>
* <li>the element is of type {@code String} and equals to the target
* enum constant's {@link Enum#name()}.</li>
* </ul>
* <p>
* If no element in the {@code labels} array matches the target, then
* the method of the call site return the length of the {@code labels} array.
*
* @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 unused
* @param invocationType The invocation type of the {@code CallSite} with two parameters,
* an enum type, an {@code int}, and {@code int} as a return type.
* @param labels case labels - {@code String} constants and {@code Class} instances,
* in any combination
* @return a {@code CallSite} returning the first matching element as described above
*
* @throws NullPointerException if any argument is {@code null}
* @throws IllegalArgumentException if any element in the labels array is null, if the
* invocation type is not a method type whose first parameter type is an enum type,
* second parameter of type {@code int} and whose return type is {@code int},
* or if {@code labels} contains an element that is not of type {@code String} or
* {@code Class} of the target enum type.
* @jvms 4.4.6 The CONSTANT_NameAndType_info Structure
* @jvms 4.4.10 The CONSTANT_Dynamic_info and CONSTANT_InvokeDynamic_info Structures
*/
public static CallSite enumSwitch(MethodHandles.Lookup lookup,
String invocationName,
MethodType invocationType,
Object... labels) {
if (invocationType.parameterCount() != 2
|| (!invocationType.returnType().equals(int.class))
|| invocationType.parameterType(0).isPrimitive()
|| !invocationType.parameterType(0).isEnum()
|| !invocationType.parameterType(1).equals(int.class))
throw new IllegalArgumentException("Illegal invocation type " + invocationType);
requireNonNull(labels);
labels = labels.clone();
Class<?> enumClass = invocationType.parameterType(0);
labels = Stream.of(labels).map(l -> convertEnumConstants(lookup, enumClass, l)).toArray();
MethodHandle target =
MethodHandles.insertArguments(DO_ENUM_SWITCH, 2, (Object) labels);
target = target.asType(invocationType);
return new ConstantCallSite(target);
}
private static <E extends Enum<E>> Object convertEnumConstants(MethodHandles.Lookup lookup, Class<?> enumClassTemplate, Object label) {
if (label == null) {
throw new IllegalArgumentException("null label found");
}
Class<?> labelClass = label.getClass();
if (labelClass == Class.class) {
if (label != enumClassTemplate) {
throw new IllegalArgumentException("the Class label: " + label +
", expected the provided enum class: " + enumClassTemplate);
}
return label;
} else if (labelClass == String.class) {
@SuppressWarnings("unchecked")
Class<E> enumClass = (Class<E>) enumClassTemplate;
try {
return ConstantBootstraps.enumConstant(lookup, (String) label, enumClass);
} catch (IllegalArgumentException ex) {
return null;
}
} else {
throw new IllegalArgumentException("label with illegal type found: " + labelClass +
", expected label of type either String or Class");
}
}
private static int doEnumSwitch(Enum<?> target, int startIndex, Object[] labels) {
if (target == null)
return -1;
// Dumbest possible strategy
Class<?> targetClass = target.getClass();
for (int i = startIndex; i < labels.length; i++) {
Object label = labels[i];
if (label instanceof Class<?> c) {
if (c.isAssignableFrom(targetClass))
return i;
} else if (label == target) {
return i;
}
}
return labels.length;
}
}