/*
* Copyright (c) 2017, 2023, 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.Enum.EnumDesc;
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.List;
import java.util.Objects;
import java.util.stream.Stream;
import jdk.internal.access.SharedSecrets;
import jdk.internal.vm.annotation.Stable;
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)}.
*
* @since 21
*/
public class SwitchBootstraps {
private SwitchBootstraps() {}
private static final Object SENTINEL = new Object();
private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
private static final MethodHandle INSTANCEOF_CHECK;
private static final MethodHandle INTEGER_EQ_CHECK;
private static final MethodHandle OBJECT_EQ_CHECK;
private static final MethodHandle ENUM_EQ_CHECK;
private static final MethodHandle NULL_CHECK;
private static final MethodHandle IS_ZERO;
private static final MethodHandle CHECK_INDEX;
private static final MethodHandle MAPPED_ENUM_LOOKUP;
static {
try {
INSTANCEOF_CHECK = MethodHandles.permuteArguments(LOOKUP.findVirtual(Class.class, "isInstance",
MethodType.methodType(boolean.class, Object.class)),
MethodType.methodType(boolean.class, Object.class, Class.class), 1, 0);
INTEGER_EQ_CHECK = LOOKUP.findStatic(SwitchBootstraps.class, "integerEqCheck",
MethodType.methodType(boolean.class, Object.class, Integer.class));
OBJECT_EQ_CHECK = LOOKUP.findStatic(Objects.class, "equals",
MethodType.methodType(boolean.class, Object.class, Object.class));
ENUM_EQ_CHECK = LOOKUP.findStatic(SwitchBootstraps.class, "enumEqCheck",
MethodType.methodType(boolean.class, Object.class, EnumDesc.class, MethodHandles.Lookup.class, ResolvedEnumLabel.class));
NULL_CHECK = LOOKUP.findStatic(Objects.class, "isNull",
MethodType.methodType(boolean.class, Object.class));
IS_ZERO = LOOKUP.findStatic(SwitchBootstraps.class, "isZero",
MethodType.methodType(boolean.class, int.class));
CHECK_INDEX = LOOKUP.findStatic(Objects.class, "checkIndex",
MethodType.methodType(int.class, int.class, int.class));
MAPPED_ENUM_LOOKUP = LOOKUP.findStatic(SwitchBootstraps.class, "mappedEnumLookup",
MethodType.methodType(int.class, Enum.class, MethodHandles.Lookup.class,
Class.class, EnumDesc[].class, EnumMap.class));
}
catch (ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e);
}
}
/**
* Bootstrap method for linking an {@code invokedynamic} call site that
* implements a {@code switch} on a target of a reference type. The static
* arguments are an array of case labels which must be non-null and of type
* {@code String} or {@code Integer} or {@code Class} or {@code EnumDesc}.
*
* The type of the returned {@code CallSite}'s method handle will have
* a return type of {@code int}. It has two parameters: the first argument
* will be an {@code Object} instance ({@code target}) and the second
* will be {@code int} ({@code restart}).
*
* If the {@code target} is {@code null}, then the method of the call site
* returns {@literal -1}.
*
* 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:
*
*
the element is of type {@code Class} that is assignable
* from the target's class; or
*
the element is of type {@code String} or {@code Integer} and
* equals to the target.
*
the element is of type {@code EnumDesc}, that describes a constant that is
* equals to the target.
*
*
* 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.
*
* The value of the {@code restart} index must be between {@code 0} (inclusive) and
* the length of the {@code labels} array (inclusive),
* both or an {@link IndexOutOfBoundsException} is thrown.
*
* @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,
* a reference type, an {@code int}, and {@code int} as a return type.
* @param labels case labels - {@code String} and {@code Integer} constants
* and {@code Class} and {@code EnumDesc} 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 not a method type of first parameter of a reference type,
* 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}, {@code Class} or {@code EnumDesc}.
* @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) {
if (invocationType.parameterCount() != 2
|| (!invocationType.returnType().equals(int.class))
|| invocationType.parameterType(0).isPrimitive()
|| !invocationType.parameterType(1).equals(int.class))
throw new IllegalArgumentException("Illegal invocation type " + invocationType);
requireNonNull(labels);
labels = labels.clone();
Stream.of(labels).forEach(SwitchBootstraps::verifyLabel);
MethodHandle target = createMethodHandleSwitch(lookup, labels);
return new ConstantCallSite(target);
}
private static void verifyLabel(Object label) {
if (label == null) {
throw new IllegalArgumentException("null label found");
}
Class> labelClass = label.getClass();
if (labelClass != Class.class &&
labelClass != String.class &&
labelClass != Integer.class &&
labelClass != EnumDesc.class) {
throw new IllegalArgumentException("label with illegal type found: " + label.getClass());
}
}
/*
* Construct test chains for labels inside switch, to handle switch repeats:
* switch (idx) {
* case 0 -> if (selector matches label[0]) return 0; else if (selector matches label[1]) return 1; else ...
* case 1 -> if (selector matches label[1]) return 1; else ...
* ...
* }
*/
private static MethodHandle createRepeatIndexSwitch(MethodHandles.Lookup lookup, Object[] labels) {
MethodHandle def = MethodHandles.dropArguments(MethodHandles.constant(int.class, labels.length), 0, Object.class);
MethodHandle[] testChains = new MethodHandle[labels.length];
List