mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-27 14:54:52 +02:00
8303374: Implement JEP 455: Primitive Types in Patterns, instanceof, and switch (Preview)
Co-authored-by: Jan Lahoda <jlahoda@openjdk.org> Co-authored-by: Maurizio Cimadamore <mcimadamore@openjdk.org> Co-authored-by: Gavin Bierman <gbierman@openjdk.org> Co-authored-by: Brian Goetz <briangoetz@openjdk.org> Co-authored-by: Raffaello Giulietti <rgiulietti@openjdk.org> Co-authored-by: Aggelos Biboudis <abimpoudis@openjdk.org> Reviewed-by: vromero, jlahoda
This commit is contained in:
parent
66971600f7
commit
1733d2ea24
43 changed files with 3564 additions and 391 deletions
|
@ -0,0 +1,296 @@
|
|||
/*
|
||||
* Copyright (c) 2024, 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;
|
||||
|
||||
/**
|
||||
* A testing conversion of a value is exact if it yields a result without loss
|
||||
* of information or throwing an exception. Otherwise, it is inexact. Some
|
||||
* conversions are always exact regardless of the value. These conversions are
|
||||
* said to be unconditionally exact.
|
||||
* <p>
|
||||
* For example, a conversion from {@code int} to {@code byte} for the value 10
|
||||
* is exact because the result, 10, is the same as the original value. In
|
||||
* contrast, if the {@code int} variable {@code i} stores the value 1000 then a
|
||||
* narrowing primitive conversion to {@code byte} will yield the result -24.
|
||||
* Loss of information has occurred: both the magnitude and the sign of the
|
||||
* result are different than those of the original value. As such, a conversion
|
||||
* from {@code int} to {@code byte} for the value 1000 is inexact. Finally a
|
||||
* widening primitive conversion from {@code byte} to {@code int} is
|
||||
* unconditionally exact because it will always succeed with no loss of
|
||||
* information about the magnitude of the numeric value.
|
||||
* <p>
|
||||
* The methods in this class provide the run-time support for the exactness
|
||||
* checks of testing conversions from a primitive type to primitive type. These
|
||||
* methods may be used, for example, by Java compiler implementations to
|
||||
* implement checks for {@code instanceof} and pattern matching runtime
|
||||
* implementations. Unconditionally exact testing conversions do not require a
|
||||
* corresponding action at run time and, for this reason, methods corresponding
|
||||
* to these exactness checks are omitted here.
|
||||
* <p>
|
||||
* The run time conversion checks examine whether loss of information would
|
||||
* occur if a testing conversion would be to be applied. In those cases where a
|
||||
* floating-point primitive type is involved, and the value of the testing
|
||||
* conversion is either signed zero, signed infinity or {@code NaN}, these
|
||||
* methods comply with the following:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Converting a floating-point negative zero to an integer type is considered
|
||||
* inexact.</li>
|
||||
* <li>Converting a floating-point {@code NaN} or infinity to an integer type is
|
||||
* considered inexact.</li>
|
||||
* <li>Converting a floating-point {@code NaN} or infinity or signed zero to another
|
||||
* floating-point type is considered exact.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @jls 5.7.1 Exact Testing Conversions
|
||||
* @jls 5.7.2 Unconditionally Exact Testing Conversions
|
||||
* @jls 15.20.2 The instanceof Operator
|
||||
*
|
||||
* @implNote Some exactness checks describe a test which can be redirected
|
||||
* safely through one of the existing methods. Those are omitted too (i.e.,
|
||||
* {@code byte} to {@code char} can be redirected to
|
||||
* {@link ExactConversionsSupport#isIntToCharExact(int)}, {@code short} to
|
||||
* {@code byte} can be redirected to
|
||||
* {@link ExactConversionsSupport#isIntToByteExact(int)} and similarly for
|
||||
* {@code short} to {@code char}, {@code char} to {@code byte} and {@code char}
|
||||
* to {@code short} to the corresponding methods that take an {@code int}).
|
||||
*
|
||||
* @since 23
|
||||
*/
|
||||
public final class ExactConversionsSupport {
|
||||
|
||||
private ExactConversionsSupport() { }
|
||||
|
||||
/**
|
||||
* Exactness method from int to byte
|
||||
* @param n value
|
||||
* @return true if and only if the passed value can be converted exactly to the target type
|
||||
*/
|
||||
public static boolean isIntToByteExact(int n) {return n == (int)(byte)n;}
|
||||
|
||||
/**
|
||||
* Exactness method from int to short
|
||||
* @param n value
|
||||
* @return true if and only if the passed value can be converted exactly to the target type
|
||||
*/
|
||||
public static boolean isIntToShortExact(int n) {return n == (int)(short)n;}
|
||||
|
||||
/**
|
||||
* Exactness method from int to char
|
||||
* @param n value
|
||||
* @return true if and only if the passed value can be converted exactly to the target type
|
||||
*/
|
||||
public static boolean isIntToCharExact(int n) {return n == (int)(char)n;}
|
||||
|
||||
/**
|
||||
* Exactness method from int to float
|
||||
* @param n value
|
||||
* @return true if and only if the passed value can be converted exactly to the target type
|
||||
*
|
||||
* @implSpec relies on the notion of representation equivalence defined in the
|
||||
* specification of the {@linkplain Double} class.
|
||||
*/
|
||||
public static boolean isIntToFloatExact(int n) {
|
||||
return n == (int)(float)n && n != Integer.MAX_VALUE;
|
||||
}
|
||||
/**
|
||||
* Exactness method from long to byte
|
||||
* @param n value
|
||||
* @return true if and only if the passed value can be converted exactly to the target type
|
||||
*/
|
||||
public static boolean isLongToByteExact(long n) {return n == (long)(byte)n;}
|
||||
|
||||
/**
|
||||
* Exactness method from long to short
|
||||
* @param n value
|
||||
* @return true if and only if the passed value can be converted exactly to the target type
|
||||
*/
|
||||
public static boolean isLongToShortExact(long n) {return n == (long)(short)n;}
|
||||
|
||||
/**
|
||||
* Exactness method from long to char
|
||||
* @param n value
|
||||
* @return true if and only if the passed value can be converted exactly to the target type
|
||||
*/
|
||||
public static boolean isLongToCharExact(long n) {return n == (long)(char)n;}
|
||||
|
||||
/**
|
||||
* Exactness method from long to int
|
||||
* @param n value
|
||||
* @return true if and only if the passed value can be converted exactly to the target type
|
||||
*/
|
||||
public static boolean isLongToIntExact(long n) {return n == (long)(int)n;}
|
||||
|
||||
/**
|
||||
* Exactness method from long to float
|
||||
* @param n value
|
||||
* @return true if and only if the passed value can be converted exactly to the target type
|
||||
* @implSpec relies on the notion of representation equivalence defined in the
|
||||
* specification of the {@linkplain Double} class.
|
||||
*/
|
||||
public static boolean isLongToFloatExact(long n) {
|
||||
return n == (long)(float)n && n != Long.MAX_VALUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exactness method from long to double
|
||||
* @param n value
|
||||
* @return true if and only if the passed value can be converted exactly to the target type
|
||||
* @implSpec relies on the notion of representation equivalence defined in the
|
||||
* specification of the {@linkplain Double} class.
|
||||
*/
|
||||
public static boolean isLongToDoubleExact(long n) {
|
||||
return n == (long)(double)n && n != Long.MAX_VALUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exactness method from float to byte
|
||||
* @param n value
|
||||
* @return true if and only if the passed value can be converted exactly to the target type
|
||||
* @implSpec relies on the notion of representation equivalence defined in the
|
||||
* specification of the {@linkplain Double} class.
|
||||
*/
|
||||
public static boolean isFloatToByteExact(float n) {
|
||||
return n == (float)(byte)n && !isNegativeZero(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exactness method from float to short
|
||||
* @param n value
|
||||
* @return true if and only if the passed value can be converted exactly to the target type
|
||||
* @implSpec relies on the notion of representation equivalence defined in the
|
||||
* specification of the {@linkplain Double} class.
|
||||
*/
|
||||
public static boolean isFloatToShortExact(float n) {
|
||||
return n == (float)(short)n && !isNegativeZero(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exactness method from float to char
|
||||
* @param n value
|
||||
* @return true if and only if the passed value can be converted exactly to the target type
|
||||
* @implSpec relies on the notion of representation equivalence defined in the
|
||||
* specification of the {@linkplain Double} class.
|
||||
*/
|
||||
public static boolean isFloatToCharExact(float n) {
|
||||
return n == (float)(char)n && !isNegativeZero(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exactness method from float to int
|
||||
* @param n value
|
||||
* @return true if and only if the passed value can be converted exactly to the target type
|
||||
* @implSpec relies on the notion of representation equivalence defined in the
|
||||
* specification of the {@linkplain Double} class.
|
||||
*/
|
||||
public static boolean isFloatToIntExact(float n) {
|
||||
return n == (float)(int)n && n != 0x1p31f && !isNegativeZero(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exactness method from float to long
|
||||
* @param n value
|
||||
* @return true if and only if the passed value can be converted exactly to the target type
|
||||
* @implSpec relies on the notion of representation equivalence defined in the
|
||||
* specification of the {@linkplain Double} class.
|
||||
*/
|
||||
public static boolean isFloatToLongExact(float n) {
|
||||
return n == (float)(long)n && n != 0x1p63f && !isNegativeZero(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exactness method from double to byte
|
||||
* @param n value
|
||||
* @return true if and only if the passed value can be converted exactly to the target type
|
||||
* @implSpec relies on the notion of representation equivalence defined in the
|
||||
* specification of the {@linkplain Double} class.
|
||||
*/
|
||||
public static boolean isDoubleToByteExact(double n) {
|
||||
return n == (double)(byte)n && !isNegativeZero(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exactness method from double to short
|
||||
* @param n value
|
||||
* @return true if and only if the passed value can be converted exactly to the target type
|
||||
* @implSpec relies on the notion of representation equivalence defined in the
|
||||
* specification of the {@linkplain Double} class.
|
||||
*/
|
||||
public static boolean isDoubleToShortExact(double n){
|
||||
return n == (double)(short)n && !isNegativeZero(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exactness method from double to char
|
||||
* @param n value
|
||||
* @return true if and only if the passed value can be converted exactly to the target type
|
||||
* @implSpec relies on the notion of representation equivalence defined in the
|
||||
* specification of the {@linkplain Double} class.
|
||||
*/
|
||||
public static boolean isDoubleToCharExact(double n) {
|
||||
return n == (double)(char)n && !isNegativeZero(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exactness method from double to int
|
||||
* @param n value
|
||||
* @return true if and only if the passed value can be converted exactly to the target type
|
||||
* @implSpec relies on the notion of representation equivalence defined in the
|
||||
* specification of the {@linkplain Double} class.
|
||||
*/
|
||||
public static boolean isDoubleToIntExact(double n) {
|
||||
return n == (double)(int)n && !isNegativeZero(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exactness method from double to long
|
||||
* @param n value
|
||||
* @return true if and only if the passed value can be converted exactly to the target type
|
||||
* @implSpec relies on the notion of representation equivalence defined in the
|
||||
* specification of the {@linkplain Double} class.
|
||||
*/
|
||||
public static boolean isDoubleToLongExact(double n) {
|
||||
return n == (double)(long)n && n != 0x1p63 && !isNegativeZero(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exactness method from double to float
|
||||
* @param n value
|
||||
* @return true if and only if the passed value can be converted exactly to the target type
|
||||
* @implSpec relies on the notion of representation equivalence defined in the
|
||||
* specification of the {@linkplain Double} class.
|
||||
*/
|
||||
public static boolean isDoubleToFloatExact(double n) {
|
||||
return n == (double)(float)n || n != n;
|
||||
}
|
||||
|
||||
private static boolean isNegativeZero(float n) {
|
||||
return Float.floatToRawIntBits(n) == Integer.MIN_VALUE;
|
||||
}
|
||||
|
||||
private static boolean isNegativeZero(double n) {
|
||||
return Double.doubleToRawLongBits(n) == Long.MIN_VALUE;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2017, 2024, 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
|
||||
|
@ -26,7 +26,9 @@
|
|||
package java.lang.runtime;
|
||||
|
||||
import java.lang.Enum.EnumDesc;
|
||||
import java.lang.classfile.CodeBuilder;
|
||||
import java.lang.constant.ClassDesc;
|
||||
import java.lang.constant.ConstantDesc;
|
||||
import java.lang.constant.ConstantDescs;
|
||||
import java.lang.constant.MethodTypeDesc;
|
||||
import java.lang.invoke.CallSite;
|
||||
|
@ -40,16 +42,21 @@ import java.util.List;
|
|||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
import jdk.internal.access.SharedSecrets;
|
||||
import java.lang.classfile.ClassFile;
|
||||
import java.lang.classfile.Label;
|
||||
import java.lang.classfile.instruction.SwitchCase;
|
||||
import jdk.internal.misc.PreviewFeatures;
|
||||
import jdk.internal.vm.annotation.Stable;
|
||||
|
||||
import static java.lang.invoke.MethodHandles.Lookup.ClassOption.NESTMATE;
|
||||
import static java.lang.invoke.MethodHandles.Lookup.ClassOption.STRONG;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import sun.invoke.util.Wrapper;
|
||||
|
||||
/**
|
||||
* Bootstrap methods for linking {@code invokedynamic} call sites that implement
|
||||
|
@ -65,6 +72,7 @@ public class SwitchBootstraps {
|
|||
|
||||
private static final Object SENTINEL = new Object();
|
||||
private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
|
||||
private static final boolean previewEnabled = PreviewFeatures.isEnabled();
|
||||
|
||||
private static final MethodHandle NULL_CHECK;
|
||||
private static final MethodHandle IS_ZERO;
|
||||
|
@ -74,6 +82,8 @@ public class SwitchBootstraps {
|
|||
private static final MethodTypeDesc TYPES_SWITCH_DESCRIPTOR =
|
||||
MethodTypeDesc.ofDescriptor("(Ljava/lang/Object;ILjava/util/function/BiPredicate;Ljava/util/List;)I");
|
||||
|
||||
private static final Map<TypePairs, String> typePairToName;
|
||||
|
||||
static {
|
||||
try {
|
||||
NULL_CHECK = LOOKUP.findStatic(Objects.class, "isNull",
|
||||
|
@ -89,6 +99,7 @@ public class SwitchBootstraps {
|
|||
catch (ReflectiveOperationException e) {
|
||||
throw new ExceptionInInitializerError(e);
|
||||
}
|
||||
typePairToName = TypePairs.initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -134,12 +145,15 @@ public class SwitchBootstraps {
|
|||
* 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}.
|
||||
* @throws NullPointerException if any argument is {@code null}
|
||||
* @throws IllegalArgumentException if any element in the labels array is null
|
||||
* @throws IllegalArgumentException if the invocation type is 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,
|
||||
* @throws IllegalArgumentException if {@code labels} contains an element that is not of type {@code String},
|
||||
* {@code Integer}, {@code Long}, {@code Float}, {@code Double}, {@code Boolean},
|
||||
* {@code Class} or {@code EnumDesc}.
|
||||
* @throws IllegalArgumentException if {@code labels} contains an element that is not of type {@code Boolean}
|
||||
* when {@code target} is a {@code Boolean.class}.
|
||||
* @jvms 4.4.6 The CONSTANT_NameAndType_info Structure
|
||||
* @jvms 4.4.10 The CONSTANT_Dynamic_info and CONSTANT_InvokeDynamic_info Structures
|
||||
*/
|
||||
|
@ -147,31 +161,39 @@ public class SwitchBootstraps {
|
|||
String invocationName,
|
||||
MethodType invocationType,
|
||||
Object... labels) {
|
||||
Class<?> selectorType = invocationType.parameterType(0);
|
||||
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);
|
||||
Stream.of(labels).forEach(l -> verifyLabel(l, selectorType));
|
||||
|
||||
MethodHandle target = generateInnerClass(lookup, labels);
|
||||
MethodHandle target = generateTypeSwitch(lookup, selectorType, labels);
|
||||
|
||||
target = withIndexCheck(target, labels.length);
|
||||
|
||||
return new ConstantCallSite(target);
|
||||
}
|
||||
|
||||
private static void verifyLabel(Object label) {
|
||||
private static void verifyLabel(Object label, Class<?> selectorType) {
|
||||
if (label == null) {
|
||||
throw new IllegalArgumentException("null label found");
|
||||
}
|
||||
Class<?> labelClass = label.getClass();
|
||||
|
||||
if (labelClass != Class.class &&
|
||||
labelClass != String.class &&
|
||||
labelClass != Integer.class &&
|
||||
|
||||
((labelClass != Float.class &&
|
||||
labelClass != Long.class &&
|
||||
labelClass != Double.class &&
|
||||
labelClass != Boolean.class) ||
|
||||
((selectorType.equals(boolean.class) || selectorType.equals(Boolean.class)) && labelClass != Boolean.class && labelClass != Class.class) ||
|
||||
!previewEnabled) &&
|
||||
|
||||
labelClass != EnumDesc.class) {
|
||||
throw new IllegalArgumentException("label with illegal type found: " + label.getClass());
|
||||
}
|
||||
|
@ -266,11 +288,11 @@ public class SwitchBootstraps {
|
|||
MethodHandles.guardWithTest(MethodHandles.dropArguments(NULL_CHECK, 0, int.class),
|
||||
MethodHandles.dropArguments(MethodHandles.constant(int.class, -1), 0, int.class, Object.class),
|
||||
MethodHandles.guardWithTest(MethodHandles.dropArguments(IS_ZERO, 1, Object.class),
|
||||
generateInnerClass(lookup, labels),
|
||||
generateTypeSwitch(lookup, invocationType.parameterType(0), labels),
|
||||
MethodHandles.insertArguments(MAPPED_ENUM_LOOKUP, 1, lookup, enumClass, labels, new EnumMap())));
|
||||
target = MethodHandles.permuteArguments(body, MethodType.methodType(int.class, Object.class, int.class), 1, 0);
|
||||
} else {
|
||||
target = generateInnerClass(lookup, labels);
|
||||
target = generateTypeSwitch(lookup, invocationType.parameterType(0), labels);
|
||||
}
|
||||
|
||||
target = target.asType(invocationType);
|
||||
|
@ -381,137 +403,233 @@ public class SwitchBootstraps {
|
|||
* ...
|
||||
* }
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
private static MethodHandle generateInnerClass(MethodHandles.Lookup caller, Object[] labels) {
|
||||
private static Consumer<CodeBuilder> generateTypeSwitchSkeleton(Class<?> selectorType, Object[] labelConstants, List<EnumDesc<?>> enumDescs, List<Class<?>> extraClassLabels) {
|
||||
int SELECTOR_OBJ = 0;
|
||||
int RESTART_IDX = 1;
|
||||
int ENUM_CACHE = 2;
|
||||
int EXTRA_CLASS_LABELS = 3;
|
||||
|
||||
return cb -> {
|
||||
cb.aload(SELECTOR_OBJ);
|
||||
Label nonNullLabel = cb.newLabel();
|
||||
cb.if_nonnull(nonNullLabel);
|
||||
cb.iconst_m1();
|
||||
cb.ireturn();
|
||||
cb.labelBinding(nonNullLabel);
|
||||
if (labelConstants.length == 0) {
|
||||
cb.constantInstruction(0)
|
||||
.ireturn();
|
||||
return;
|
||||
}
|
||||
cb.iload(RESTART_IDX);
|
||||
Label dflt = cb.newLabel();
|
||||
record Element(Label target, Label next, Object caseLabel) { }
|
||||
List<Element> cases = new ArrayList<>();
|
||||
List<SwitchCase> switchCases = new ArrayList<>();
|
||||
Object lastLabel = null;
|
||||
for (int idx = labelConstants.length - 1; idx >= 0; idx--) {
|
||||
Object currentLabel = labelConstants[idx];
|
||||
Label target = cb.newLabel();
|
||||
Label next;
|
||||
if (lastLabel == null) {
|
||||
next = dflt;
|
||||
} else if (lastLabel.equals(currentLabel)) {
|
||||
next = cases.getLast().next();
|
||||
} else {
|
||||
next = cases.getLast().target();
|
||||
}
|
||||
lastLabel = currentLabel;
|
||||
cases.add(new Element(target, next, currentLabel));
|
||||
switchCases.add(SwitchCase.of(idx, target));
|
||||
}
|
||||
cases = cases.reversed();
|
||||
switchCases = switchCases.reversed();
|
||||
cb.tableswitch(0, labelConstants.length - 1, dflt, switchCases);
|
||||
for (int idx = 0; idx < cases.size(); idx++) {
|
||||
Element element = cases.get(idx);
|
||||
Label next = element.next();
|
||||
cb.labelBinding(element.target());
|
||||
if (element.caseLabel() instanceof Class<?> classLabel) {
|
||||
if (unconditionalExactnessCheck(selectorType, classLabel)) {
|
||||
//nothing - unconditionally use this case
|
||||
} else if (classLabel.isPrimitive()) {
|
||||
if (!selectorType.isPrimitive() && !Wrapper.isWrapperNumericOrBooleanType(selectorType)) {
|
||||
// Object o = ...
|
||||
// o instanceof Wrapped(float)
|
||||
cb.aload(SELECTOR_OBJ);
|
||||
cb.instanceof_(Wrapper.forBasicType(classLabel)
|
||||
.wrapperType()
|
||||
.describeConstable()
|
||||
.orElseThrow());
|
||||
cb.ifeq(next);
|
||||
} else if (!unconditionalExactnessCheck(Wrapper.asPrimitiveType(selectorType), classLabel)) {
|
||||
// Integer i = ... or int i = ...
|
||||
// o instanceof float
|
||||
Label notNumber = cb.newLabel();
|
||||
cb.aload(SELECTOR_OBJ);
|
||||
cb.instanceof_(ConstantDescs.CD_Number);
|
||||
if (selectorType == long.class || selectorType == float.class || selectorType == double.class) {
|
||||
cb.ifeq(next);
|
||||
} else {
|
||||
cb.ifeq(notNumber);
|
||||
}
|
||||
cb.aload(SELECTOR_OBJ);
|
||||
cb.checkcast(ConstantDescs.CD_Number);
|
||||
if (selectorType == long.class) {
|
||||
cb.invokevirtual(ConstantDescs.CD_Number,
|
||||
"longValue",
|
||||
MethodTypeDesc.of(ConstantDescs.CD_long));
|
||||
} else if (selectorType == float.class) {
|
||||
cb.invokevirtual(ConstantDescs.CD_Number,
|
||||
"floatValue",
|
||||
MethodTypeDesc.of(ConstantDescs.CD_float));
|
||||
} else if (selectorType == double.class) {
|
||||
cb.invokevirtual(ConstantDescs.CD_Number,
|
||||
"doubleValue",
|
||||
MethodTypeDesc.of(ConstantDescs.CD_double));
|
||||
} else {
|
||||
Label compare = cb.newLabel();
|
||||
cb.invokevirtual(ConstantDescs.CD_Number,
|
||||
"intValue",
|
||||
MethodTypeDesc.of(ConstantDescs.CD_int));
|
||||
cb.goto_(compare);
|
||||
cb.labelBinding(notNumber);
|
||||
cb.aload(SELECTOR_OBJ);
|
||||
cb.instanceof_(ConstantDescs.CD_Character);
|
||||
cb.ifeq(next);
|
||||
cb.aload(SELECTOR_OBJ);
|
||||
cb.checkcast(ConstantDescs.CD_Character);
|
||||
cb.invokevirtual(ConstantDescs.CD_Character,
|
||||
"charValue",
|
||||
MethodTypeDesc.of(ConstantDescs.CD_char));
|
||||
cb.labelBinding(compare);
|
||||
}
|
||||
|
||||
TypePairs typePair = TypePairs.of(Wrapper.asPrimitiveType(selectorType), classLabel);
|
||||
String methodName = typePairToName.get(typePair);
|
||||
cb.invokestatic(ExactConversionsSupport.class.describeConstable().orElseThrow(),
|
||||
methodName,
|
||||
MethodTypeDesc.of(ConstantDescs.CD_boolean, typePair.from.describeConstable().orElseThrow()));
|
||||
cb.ifeq(next);
|
||||
}
|
||||
} else {
|
||||
Optional<ClassDesc> classLabelConstableOpt = classLabel.describeConstable();
|
||||
if (classLabelConstableOpt.isPresent()) {
|
||||
cb.aload(SELECTOR_OBJ);
|
||||
cb.instanceof_(classLabelConstableOpt.orElseThrow());
|
||||
cb.ifeq(next);
|
||||
} else {
|
||||
cb.aload(EXTRA_CLASS_LABELS);
|
||||
cb.constantInstruction(extraClassLabels.size());
|
||||
cb.invokeinterface(ConstantDescs.CD_List,
|
||||
"get",
|
||||
MethodTypeDesc.of(ConstantDescs.CD_Object,
|
||||
ConstantDescs.CD_int));
|
||||
cb.checkcast(ConstantDescs.CD_Class);
|
||||
cb.aload(SELECTOR_OBJ);
|
||||
cb.invokevirtual(ConstantDescs.CD_Class,
|
||||
"isInstance",
|
||||
MethodTypeDesc.of(ConstantDescs.CD_boolean,
|
||||
ConstantDescs.CD_Object));
|
||||
cb.ifeq(next);
|
||||
extraClassLabels.add(classLabel);
|
||||
}
|
||||
}
|
||||
} else if (element.caseLabel() instanceof EnumDesc<?> enumLabel) {
|
||||
int enumIdx = enumDescs.size();
|
||||
enumDescs.add(enumLabel);
|
||||
cb.aload(ENUM_CACHE);
|
||||
cb.constantInstruction(enumIdx);
|
||||
cb.invokestatic(ConstantDescs.CD_Integer,
|
||||
"valueOf",
|
||||
MethodTypeDesc.of(ConstantDescs.CD_Integer,
|
||||
ConstantDescs.CD_int));
|
||||
cb.aload(SELECTOR_OBJ);
|
||||
cb.invokeinterface(BiPredicate.class.describeConstable().orElseThrow(),
|
||||
"test",
|
||||
MethodTypeDesc.of(ConstantDescs.CD_boolean,
|
||||
ConstantDescs.CD_Object,
|
||||
ConstantDescs.CD_Object));
|
||||
cb.ifeq(next);
|
||||
} else if (element.caseLabel() instanceof String stringLabel) {
|
||||
cb.ldc(stringLabel);
|
||||
cb.aload(SELECTOR_OBJ);
|
||||
cb.invokevirtual(ConstantDescs.CD_Object,
|
||||
"equals",
|
||||
MethodTypeDesc.of(ConstantDescs.CD_boolean,
|
||||
ConstantDescs.CD_Object));
|
||||
cb.ifeq(next);
|
||||
} else if (element.caseLabel() instanceof Integer integerLabel) {
|
||||
Label compare = cb.newLabel();
|
||||
Label notNumber = cb.newLabel();
|
||||
cb.aload(SELECTOR_OBJ);
|
||||
cb.instanceof_(ConstantDescs.CD_Number);
|
||||
cb.ifeq(notNumber);
|
||||
cb.aload(SELECTOR_OBJ);
|
||||
cb.checkcast(ConstantDescs.CD_Number);
|
||||
cb.invokevirtual(ConstantDescs.CD_Number,
|
||||
"intValue",
|
||||
MethodTypeDesc.of(ConstantDescs.CD_int));
|
||||
cb.goto_(compare);
|
||||
cb.labelBinding(notNumber);
|
||||
cb.aload(SELECTOR_OBJ);
|
||||
cb.instanceof_(ConstantDescs.CD_Character);
|
||||
cb.ifeq(next);
|
||||
cb.aload(SELECTOR_OBJ);
|
||||
cb.checkcast(ConstantDescs.CD_Character);
|
||||
cb.invokevirtual(ConstantDescs.CD_Character,
|
||||
"charValue",
|
||||
MethodTypeDesc.of(ConstantDescs.CD_char));
|
||||
cb.labelBinding(compare);
|
||||
|
||||
cb.ldc(integerLabel);
|
||||
cb.if_icmpne(next);
|
||||
} else if ((element.caseLabel() instanceof Long ||
|
||||
element.caseLabel() instanceof Float ||
|
||||
element.caseLabel() instanceof Double ||
|
||||
element.caseLabel() instanceof Boolean)) {
|
||||
if (element.caseLabel() instanceof Boolean c) {
|
||||
cb.constantInstruction(c ? 1 : 0);
|
||||
} else {
|
||||
cb.constantInstruction((ConstantDesc) element.caseLabel());
|
||||
}
|
||||
cb.invokestatic(element.caseLabel().getClass().describeConstable().orElseThrow(),
|
||||
"valueOf",
|
||||
MethodTypeDesc.of(element.caseLabel().getClass().describeConstable().orElseThrow(),
|
||||
Wrapper.asPrimitiveType(element.caseLabel().getClass()).describeConstable().orElseThrow()));
|
||||
cb.aload(SELECTOR_OBJ);
|
||||
cb.invokevirtual(ConstantDescs.CD_Object,
|
||||
"equals",
|
||||
MethodTypeDesc.of(ConstantDescs.CD_boolean,
|
||||
ConstantDescs.CD_Object));
|
||||
cb.ifeq(next);
|
||||
} else {
|
||||
throw new InternalError("Unsupported label type: " +
|
||||
element.caseLabel().getClass());
|
||||
}
|
||||
cb.constantInstruction(idx);
|
||||
cb.ireturn();
|
||||
}
|
||||
cb.labelBinding(dflt);
|
||||
cb.constantInstruction(cases.size());
|
||||
cb.ireturn();
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Construct the method handle that represents the method int typeSwitch(Object, int, BiPredicate, List)
|
||||
*/
|
||||
private static MethodHandle generateTypeSwitch(MethodHandles.Lookup caller, Class<?> selectorType, Object[] labelConstants) {
|
||||
List<EnumDesc<?>> enumDescs = new ArrayList<>();
|
||||
List<Class<?>> extraClassLabels = new ArrayList<>();
|
||||
|
||||
byte[] classBytes = ClassFile.of().build(ClassDesc.of(typeSwitchClassName(caller.lookupClass())), clb -> {
|
||||
clb.withFlags(AccessFlag.FINAL, AccessFlag.SUPER, AccessFlag.SYNTHETIC)
|
||||
.withMethodBody("typeSwitch",
|
||||
TYPES_SWITCH_DESCRIPTOR,
|
||||
ClassFile.ACC_FINAL | ClassFile.ACC_PUBLIC | ClassFile.ACC_STATIC,
|
||||
cb -> {
|
||||
cb.aload(0);
|
||||
Label nonNullLabel = cb.newLabel();
|
||||
cb.if_nonnull(nonNullLabel);
|
||||
cb.iconst_m1();
|
||||
cb.ireturn();
|
||||
cb.labelBinding(nonNullLabel);
|
||||
if (labels.length == 0) {
|
||||
cb.constantInstruction(0)
|
||||
.ireturn();
|
||||
return ;
|
||||
}
|
||||
cb.iload(1);
|
||||
Label dflt = cb.newLabel();
|
||||
record Element(Label target, Label next, Object caseLabel) {}
|
||||
List<Element> cases = new ArrayList<>();
|
||||
List<SwitchCase> switchCases = new ArrayList<>();
|
||||
Object lastLabel = null;
|
||||
for (int idx = labels.length - 1; idx >= 0; idx--) {
|
||||
Object currentLabel = labels[idx];
|
||||
Label target = cb.newLabel();
|
||||
Label next;
|
||||
if (lastLabel == null) {
|
||||
next = dflt;
|
||||
} else if (lastLabel.equals(currentLabel)) {
|
||||
next = cases.getLast().next();
|
||||
} else {
|
||||
next = cases.getLast().target();
|
||||
}
|
||||
lastLabel = currentLabel;
|
||||
cases.add(new Element(target, next, currentLabel));
|
||||
switchCases.add(SwitchCase.of(idx, target));
|
||||
}
|
||||
cases = cases.reversed();
|
||||
switchCases = switchCases.reversed();
|
||||
cb.tableswitch(0, labels.length - 1, dflt, switchCases);
|
||||
for (int idx = 0; idx < cases.size(); idx++) {
|
||||
Element element = cases.get(idx);
|
||||
Label next = element.next();
|
||||
cb.labelBinding(element.target());
|
||||
if (element.caseLabel() instanceof Class<?> classLabel) {
|
||||
Optional<ClassDesc> classLabelConstableOpt = classLabel.describeConstable();
|
||||
if (classLabelConstableOpt.isPresent()) {
|
||||
cb.aload(0);
|
||||
cb.instanceof_(classLabelConstableOpt.orElseThrow());
|
||||
cb.ifeq(next);
|
||||
} else {
|
||||
cb.aload(3);
|
||||
cb.constantInstruction(extraClassLabels.size());
|
||||
cb.invokeinterface(ConstantDescs.CD_List,
|
||||
"get",
|
||||
MethodTypeDesc.of(ConstantDescs.CD_Object,
|
||||
ConstantDescs.CD_int));
|
||||
cb.checkcast(ConstantDescs.CD_Class);
|
||||
cb.aload(0);
|
||||
cb.invokevirtual(ConstantDescs.CD_Class,
|
||||
"isInstance",
|
||||
MethodTypeDesc.of(ConstantDescs.CD_boolean,
|
||||
ConstantDescs.CD_Object));
|
||||
cb.ifeq(next);
|
||||
extraClassLabels.add(classLabel);
|
||||
}
|
||||
} else if (element.caseLabel() instanceof EnumDesc<?> enumLabel) {
|
||||
int enumIdx = enumDescs.size();
|
||||
enumDescs.add(enumLabel);
|
||||
cb.aload(2);
|
||||
cb.constantInstruction(enumIdx);
|
||||
cb.invokestatic(ConstantDescs.CD_Integer,
|
||||
"valueOf",
|
||||
MethodTypeDesc.of(ConstantDescs.CD_Integer,
|
||||
ConstantDescs.CD_int));
|
||||
cb.aload(0);
|
||||
cb.invokeinterface(BiPredicate.class.describeConstable().orElseThrow(),
|
||||
"test",
|
||||
MethodTypeDesc.of(ConstantDescs.CD_boolean,
|
||||
ConstantDescs.CD_Object,
|
||||
ConstantDescs.CD_Object));
|
||||
cb.ifeq(next);
|
||||
} else if (element.caseLabel() instanceof String stringLabel) {
|
||||
cb.ldc(stringLabel);
|
||||
cb.aload(0);
|
||||
cb.invokevirtual(ConstantDescs.CD_Object,
|
||||
"equals",
|
||||
MethodTypeDesc.of(ConstantDescs.CD_boolean,
|
||||
ConstantDescs.CD_Object));
|
||||
cb.ifeq(next);
|
||||
} else if (element.caseLabel() instanceof Integer integerLabel) {
|
||||
Label compare = cb.newLabel();
|
||||
Label notNumber = cb.newLabel();
|
||||
cb.aload(0);
|
||||
cb.instanceof_(ConstantDescs.CD_Number);
|
||||
cb.ifeq(notNumber);
|
||||
cb.aload(0);
|
||||
cb.checkcast(ConstantDescs.CD_Number);
|
||||
cb.invokevirtual(ConstantDescs.CD_Number,
|
||||
"intValue",
|
||||
MethodTypeDesc.of(ConstantDescs.CD_int));
|
||||
cb.goto_(compare);
|
||||
cb.labelBinding(notNumber);
|
||||
cb.aload(0);
|
||||
cb.instanceof_(ConstantDescs.CD_Character);
|
||||
cb.ifeq(next);
|
||||
cb.aload(0);
|
||||
cb.checkcast(ConstantDescs.CD_Character);
|
||||
cb.invokevirtual(ConstantDescs.CD_Character,
|
||||
"charValue",
|
||||
MethodTypeDesc.of(ConstantDescs.CD_char));
|
||||
cb.labelBinding(compare);
|
||||
cb.ldc(integerLabel);
|
||||
cb.if_icmpne(next);
|
||||
} else {
|
||||
throw new InternalError("Unsupported label type: " +
|
||||
element.caseLabel().getClass());
|
||||
}
|
||||
cb.constantInstruction(idx);
|
||||
cb.ireturn();
|
||||
}
|
||||
cb.labelBinding(dflt);
|
||||
cb.constantInstruction(cases.size());
|
||||
cb.ireturn();
|
||||
});
|
||||
byte[] classBytes = ClassFile.of().build(ClassDesc.of(typeSwitchClassName(caller.lookupClass())),
|
||||
clb -> {
|
||||
clb.withFlags(AccessFlag.FINAL, AccessFlag.SUPER, AccessFlag.SYNTHETIC)
|
||||
.withMethodBody("typeSwitch",
|
||||
TYPES_SWITCH_DESCRIPTOR,
|
||||
ClassFile.ACC_FINAL | ClassFile.ACC_PUBLIC | ClassFile.ACC_STATIC,
|
||||
generateTypeSwitchSkeleton(selectorType, labelConstants, enumDescs, extraClassLabels));
|
||||
});
|
||||
|
||||
try {
|
||||
|
@ -525,8 +643,13 @@ public class SwitchBootstraps {
|
|||
int.class,
|
||||
BiPredicate.class,
|
||||
List.class));
|
||||
return MethodHandles.insertArguments(typeSwitch, 2, new ResolvedEnumLabels(caller, enumDescs.toArray(EnumDesc[]::new)),
|
||||
List.copyOf(extraClassLabels));
|
||||
typeSwitch = MethodHandles.insertArguments(typeSwitch, 2, new ResolvedEnumLabels(caller, enumDescs.toArray(EnumDesc[]::new)),
|
||||
List.copyOf(extraClassLabels));
|
||||
typeSwitch = MethodHandles.explicitCastArguments(typeSwitch,
|
||||
MethodType.methodType(int.class,
|
||||
selectorType,
|
||||
int.class));
|
||||
return typeSwitch;
|
||||
} catch (Throwable t) {
|
||||
throw new IllegalArgumentException(t);
|
||||
}
|
||||
|
@ -541,4 +664,61 @@ public class SwitchBootstraps {
|
|||
}
|
||||
return name + "$$TypeSwitch";
|
||||
}
|
||||
|
||||
// this method should be in sync with com.sun.tools.javac.code.Types.checkUnconditionallyExactPrimitives
|
||||
private static boolean unconditionalExactnessCheck(Class<?> selectorType, Class<?> targetType) {
|
||||
Wrapper selectorWrapper = Wrapper.forBasicType(selectorType);
|
||||
Wrapper targetWrapper = Wrapper.forBasicType(targetType);
|
||||
if (selectorType.isPrimitive() && targetType.equals(selectorWrapper.wrapperType())) {
|
||||
return true;
|
||||
}
|
||||
else if (selectorType.equals(targetType) ||
|
||||
((selectorType.equals(byte.class) && !targetType.equals(char.class)) ||
|
||||
(selectorType.equals(short.class) && (selectorWrapper.isStrictSubRangeOf(targetWrapper))) ||
|
||||
(selectorType.equals(char.class) && (selectorWrapper.isStrictSubRangeOf(targetWrapper))) ||
|
||||
(selectorType.equals(int.class) && (targetType.equals(double.class) || targetType.equals(long.class))) ||
|
||||
(selectorType.equals(float.class) && (selectorWrapper.isStrictSubRangeOf(targetWrapper))))) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// TypePairs should be in sync with the corresponding record in Lower
|
||||
record TypePairs(Class<?> from, Class<?> to) {
|
||||
public static TypePairs of(Class<?> from, Class<?> to) {
|
||||
if (from == byte.class || from == short.class || from == char.class) {
|
||||
from = int.class;
|
||||
}
|
||||
return new TypePairs(from, to);
|
||||
}
|
||||
|
||||
public static Map<TypePairs, String> initialize() {
|
||||
Map<TypePairs, String> typePairToName = new HashMap<>();
|
||||
typePairToName.put(new TypePairs(byte.class, char.class), "isIntToCharExact"); // redirected
|
||||
typePairToName.put(new TypePairs(short.class, byte.class), "isIntToByteExact"); // redirected
|
||||
typePairToName.put(new TypePairs(short.class, char.class), "isIntToCharExact"); // redirected
|
||||
typePairToName.put(new TypePairs(char.class, byte.class), "isIntToByteExact"); // redirected
|
||||
typePairToName.put(new TypePairs(char.class, short.class), "isIntToShortExact"); // redirected
|
||||
typePairToName.put(new TypePairs(int.class, byte.class), "isIntToByteExact");
|
||||
typePairToName.put(new TypePairs(int.class, short.class), "isIntToShortExact");
|
||||
typePairToName.put(new TypePairs(int.class, char.class), "isIntToCharExact");
|
||||
typePairToName.put(new TypePairs(int.class, float.class), "isIntToFloatExact");
|
||||
typePairToName.put(new TypePairs(long.class, byte.class), "isLongToByteExact");
|
||||
typePairToName.put(new TypePairs(long.class, short.class), "isLongToShortExact");
|
||||
typePairToName.put(new TypePairs(long.class, char.class), "isLongToCharExact");
|
||||
typePairToName.put(new TypePairs(long.class, int.class), "isLongToIntExact");
|
||||
typePairToName.put(new TypePairs(long.class, float.class), "isLongToFloatExact");
|
||||
typePairToName.put(new TypePairs(long.class, double.class), "isLongToDoubleExact");
|
||||
typePairToName.put(new TypePairs(float.class, byte.class), "isFloatToByteExact");
|
||||
typePairToName.put(new TypePairs(float.class, short.class), "isFloatToShortExact");
|
||||
typePairToName.put(new TypePairs(float.class, char.class), "isFloatToCharExact");
|
||||
typePairToName.put(new TypePairs(float.class, int.class), "isFloatToIntExact");
|
||||
typePairToName.put(new TypePairs(float.class, long.class), "isFloatToLongExact");
|
||||
typePairToName.put(new TypePairs(double.class, byte.class), "isDoubleToByteExact");
|
||||
typePairToName.put(new TypePairs(double.class, short.class), "isDoubleToShortExact");
|
||||
typePairToName.put(new TypePairs(double.class, char.class), "isDoubleToCharExact");
|
||||
typePairToName.put(new TypePairs(double.class, int.class), "isDoubleToIntExact");
|
||||
typePairToName.put(new TypePairs(double.class, long.class), "isDoubleToLongExact");
|
||||
typePairToName.put(new TypePairs(double.class, float.class), "isDoubleToFloatExact");
|
||||
return typePairToName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue