1 /*
  2  * Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 
 26 package java.lang.runtime;
 27 
 28 import java.lang.Enum.EnumDesc;
 29 import java.lang.classfile.CodeBuilder;
 30 import java.lang.constant.ClassDesc;
 31 import java.lang.constant.ConstantDesc;
 32 import java.lang.constant.ConstantDescs;
 33 import java.lang.constant.MethodTypeDesc;
 34 import java.lang.invoke.CallSite;
 35 import java.lang.invoke.ConstantCallSite;
 36 import java.lang.invoke.MethodHandle;
 37 import java.lang.invoke.MethodHandles;
 38 import java.lang.invoke.MethodType;
 39 import java.lang.reflect.AccessFlag;
 40 import java.util.ArrayList;
 41 import java.util.List;
 42 import java.util.Objects;
 43 import java.util.Optional;
 44 import java.util.function.BiPredicate;
 45 import java.util.function.Consumer;
 46 import java.util.stream.Stream;
 47 import jdk.internal.access.SharedSecrets;
 48 import java.lang.classfile.ClassFile;
 49 import java.lang.classfile.Label;
 50 import java.lang.classfile.instruction.SwitchCase;
 51 import jdk.internal.misc.PreviewFeatures;
 52 import jdk.internal.vm.annotation.Stable;
 53 
 54 import static java.lang.invoke.MethodHandles.Lookup.ClassOption.NESTMATE;
 55 import static java.lang.invoke.MethodHandles.Lookup.ClassOption.STRONG;
 56 import java.util.HashMap;
 57 import java.util.Map;
 58 import static java.util.Objects.requireNonNull;
 59 import sun.invoke.util.Wrapper;
 60 
 61 /**
 62  * Bootstrap methods for linking {@code invokedynamic} call sites that implement
 63  * the selection functionality of the {@code switch} statement.  The bootstraps
 64  * take additional static arguments corresponding to the {@code case} labels
 65  * of the {@code switch}, implicitly numbered sequentially from {@code [0..N)}.
 66  *
 67  * @since 21
 68  */
 69 public class SwitchBootstraps {
 70 
 71     private SwitchBootstraps() {}
 72 
 73     private static final Object SENTINEL = new Object();
 74     private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
 75     private static final boolean previewEnabled = PreviewFeatures.isEnabled();
 76 
 77     private static final MethodHandle NULL_CHECK;
 78     private static final MethodHandle IS_ZERO;
 79     private static final MethodHandle CHECK_INDEX;
 80     private static final MethodHandle MAPPED_ENUM_LOOKUP;
 81 
 82     private static final MethodTypeDesc TYPES_SWITCH_DESCRIPTOR =
 83             MethodTypeDesc.ofDescriptor("(Ljava/lang/Object;ILjava/util/function/BiPredicate;Ljava/util/List;)I");
 84 
 85     private static final Map<TypePairs, String> typePairToName;
 86 
 87     static {
 88         try {
 89             NULL_CHECK = LOOKUP.findStatic(Objects.class, "isNull",
 90                                            MethodType.methodType(boolean.class, Object.class));
 91             IS_ZERO = LOOKUP.findStatic(SwitchBootstraps.class, "isZero",
 92                                            MethodType.methodType(boolean.class, int.class));
 93             CHECK_INDEX = LOOKUP.findStatic(Objects.class, "checkIndex",
 94                                            MethodType.methodType(int.class, int.class, int.class));
 95             MAPPED_ENUM_LOOKUP = LOOKUP.findStatic(SwitchBootstraps.class, "mappedEnumLookup",
 96                                                    MethodType.methodType(int.class, Enum.class, MethodHandles.Lookup.class,
 97                                                                          Class.class, EnumDesc[].class, EnumMap.class));
 98         }
 99         catch (ReflectiveOperationException e) {
100             throw new ExceptionInInitializerError(e);
101         }
102         typePairToName = TypePairs.initialize();
103     }
104 
105     /**
106      * Bootstrap method for linking an {@code invokedynamic} call site that
107      * implements a {@code switch} on a target of a reference type.  The static
108      * arguments are an array of case labels which must be non-null and of type
109      * {@code String} or {@code Integer} or {@code Class} or {@code EnumDesc}.
110      * <p>
111      * The type of the returned {@code CallSite}'s method handle will have
112      * a return type of {@code int}.   It has two parameters: the first argument
113      * will be an {@code Object} instance ({@code target}) and the second
114      * will be {@code int} ({@code restart}).
115      * <p>
116      * If the {@code target} is {@code null}, then the method of the call site
117      * returns {@literal -1}.
118      * <p>
119      * If the {@code target} is not {@code null}, then the method of the call site
120      * returns the index of the first element in the {@code labels} array starting from
121      * the {@code restart} index matching one of the following conditions:
122      * <ul>
123      *   <li>the element is of type {@code Class} that is assignable
124      *       from the target's class; or</li>
125      *   <li>the element is of type {@code String} or {@code Integer} and
126      *       equals to the target.</li>
127      *   <li>the element is of type {@code EnumDesc}, that describes a constant that is
128      *       equals to the target.</li>
129      * </ul>
130      * <p>
131      * If no element in the {@code labels} array matches the target, then
132      * the method of the call site return the length of the {@code labels} array.
133      * <p>
134      * The value of the {@code restart} index must be between {@code 0} (inclusive) and
135      * the length of the {@code labels} array (inclusive),
136      * both  or an {@link IndexOutOfBoundsException} is thrown.
137      *
138      * @param lookup Represents a lookup context with the accessibility
139      *               privileges of the caller.  When used with {@code invokedynamic},
140      *               this is stacked automatically by the VM.
141      * @param invocationName unused
142      * @param invocationType The invocation type of the {@code CallSite} with two parameters,
143      *                       a reference type, an {@code int}, and {@code int} as a return type.
144      * @param labels case labels - {@code String} and {@code Integer} constants
145      *               and {@code Class} and {@code EnumDesc} instances, in any combination
146      * @return a {@code CallSite} returning the first matching element as described above
147      *
148      * @throws NullPointerException     if any argument is {@code null}
149      * @throws IllegalArgumentException if any element in the labels array is null
150      * @throws IllegalArgumentException if the invocation type is not a method type of first parameter of a reference type,
151      *                                  second parameter of type {@code int} and with {@code int} as its return type,
152      * @throws IllegalArgumentException if {@code labels} contains an element that is not of type {@code String},
153      *                                  {@code Integer}, {@code Long}, {@code Float}, {@code Double}, {@code Boolean},
154      *                                  {@code Class} or {@code EnumDesc}.
155      * @throws IllegalArgumentException if {@code labels} contains an element that is not of type {@code Boolean}
156      *                                  when {@code target} is a {@code Boolean.class}.
157      * @jvms 4.4.6 The CONSTANT_NameAndType_info Structure
158      * @jvms 4.4.10 The CONSTANT_Dynamic_info and CONSTANT_InvokeDynamic_info Structures
159      */
160     public static CallSite typeSwitch(MethodHandles.Lookup lookup,
161                                       String invocationName,
162                                       MethodType invocationType,
163                                       Object... labels) {
164         Class<?> selectorType = invocationType.parameterType(0);
165         if (invocationType.parameterCount() != 2
166             || (!invocationType.returnType().equals(int.class))
167             || !invocationType.parameterType(1).equals(int.class))
168             throw new IllegalArgumentException("Illegal invocation type " + invocationType);
169         requireNonNull(labels);
170 
171         Stream.of(labels).forEach(l -> verifyLabel(l, selectorType));
172 
173         MethodHandle target = generateTypeSwitch(lookup, selectorType, labels);
174 
175         target = withIndexCheck(target, labels.length);
176 
177         return new ConstantCallSite(target);
178     }
179 
180     private static void verifyLabel(Object label, Class<?> selectorType) {
181         if (label == null) {
182             throw new IllegalArgumentException("null label found");
183         }
184         Class<?> labelClass = label.getClass();
185 
186         if (labelClass != Class.class &&
187             labelClass != String.class &&
188             labelClass != Integer.class &&
189 
190             ((labelClass != Float.class &&
191               labelClass != Long.class &&
192               labelClass != Double.class &&
193               labelClass != Boolean.class) ||
194               ((selectorType.equals(boolean.class) || selectorType.equals(Boolean.class)) && labelClass != Boolean.class && labelClass != Class.class) ||
195              !previewEnabled) &&
196 
197             labelClass != EnumDesc.class) {
198             throw new IllegalArgumentException("label with illegal type found: " + label.getClass());
199         }
200     }
201 
202     private static boolean isZero(int value) {
203         return value == 0;
204     }
205 
206     /**
207      * Bootstrap method for linking an {@code invokedynamic} call site that
208      * implements a {@code switch} on a target of an enum type. The static
209      * arguments are used to encode the case labels associated to the switch
210      * construct, where each label can be encoded in two ways:
211      * <ul>
212      *   <li>as a {@code String} value, which represents the name of
213      *       the enum constant associated with the label</li>
214      *   <li>as a {@code Class} value, which represents the enum type
215      *       associated with a type test pattern</li>
216      * </ul>
217      * <p>
218      * The returned {@code CallSite}'s method handle will have
219      * a return type of {@code int} and accepts two parameters: the first argument
220      * will be an {@code Enum} instance ({@code target}) and the second
221      * will be {@code int} ({@code restart}).
222      * <p>
223      * If the {@code target} is {@code null}, then the method of the call site
224      * returns {@literal -1}.
225      * <p>
226      * If the {@code target} is not {@code null}, then the method of the call site
227      * returns the index of the first element in the {@code labels} array starting from
228      * the {@code restart} index matching one of the following conditions:
229      * <ul>
230      *   <li>the element is of type {@code Class} that is assignable
231      *       from the target's class; or</li>
232      *   <li>the element is of type {@code String} and equals to the target
233      *       enum constant's {@link Enum#name()}.</li>
234      * </ul>
235      * <p>
236      * If no element in the {@code labels} array matches the target, then
237      * the method of the call site return the length of the {@code labels} array.
238      * <p>
239      * The value of the {@code restart} index must be between {@code 0} (inclusive) and
240      * the length of the {@code labels} array (inclusive),
241      * both  or an {@link IndexOutOfBoundsException} is thrown.
242      *
243      * @param lookup Represents a lookup context with the accessibility
244      *               privileges of the caller. When used with {@code invokedynamic},
245      *               this is stacked automatically by the VM.
246      * @param invocationName unused
247      * @param invocationType The invocation type of the {@code CallSite} with two parameters,
248      *                       an enum type, an {@code int}, and {@code int} as a return type.
249      * @param labels case labels - {@code String} constants and {@code Class} instances,
250      *               in any combination
251      * @return a {@code CallSite} returning the first matching element as described above
252      *
253      * @throws NullPointerException if any argument is {@code null}
254      * @throws IllegalArgumentException if any element in the labels array is null, if the
255      * invocation type is not a method type whose first parameter type is an enum type,
256      * second parameter of type {@code int} and whose return type is {@code int},
257      * or if {@code labels} contains an element that is not of type {@code String} or
258      * {@code Class} of the target enum type.
259      * @jvms 4.4.6 The CONSTANT_NameAndType_info Structure
260      * @jvms 4.4.10 The CONSTANT_Dynamic_info and CONSTANT_InvokeDynamic_info Structures
261      */
262     public static CallSite enumSwitch(MethodHandles.Lookup lookup,
263                                       String invocationName,
264                                       MethodType invocationType,
265                                       Object... labels) {
266         if (invocationType.parameterCount() != 2
267             || (!invocationType.returnType().equals(int.class))
268             || invocationType.parameterType(0).isPrimitive()
269             || !invocationType.parameterType(0).isEnum()
270             || !invocationType.parameterType(1).equals(int.class))
271             throw new IllegalArgumentException("Illegal invocation type " + invocationType);
272         requireNonNull(labels);
273 
274         labels = labels.clone();
275 
276         Class<?> enumClass = invocationType.parameterType(0);
277         labels = Stream.of(labels).map(l -> convertEnumConstants(lookup, enumClass, l)).toArray();
278 
279         MethodHandle target;
280         boolean constantsOnly = Stream.of(labels).allMatch(l -> enumClass.isAssignableFrom(EnumDesc.class));
281 
282         if (labels.length > 0 && constantsOnly) {
283             //If all labels are enum constants, construct an optimized handle for repeat index 0:
284             //if (selector == null) return -1
285             //else if (idx == 0) return mappingArray[selector.ordinal()]; //mapping array created lazily
286             //else return "typeSwitch(labels)"
287             MethodHandle body =
288                     MethodHandles.guardWithTest(MethodHandles.dropArguments(NULL_CHECK, 0, int.class),
289                                                 MethodHandles.dropArguments(MethodHandles.constant(int.class, -1), 0, int.class, Object.class),
290                                                 MethodHandles.guardWithTest(MethodHandles.dropArguments(IS_ZERO, 1, Object.class),
291                                                                             generateTypeSwitch(lookup, invocationType.parameterType(0), labels),
292                                                                             MethodHandles.insertArguments(MAPPED_ENUM_LOOKUP, 1, lookup, enumClass, labels, new EnumMap())));
293             target = MethodHandles.permuteArguments(body, MethodType.methodType(int.class, Object.class, int.class), 1, 0);
294         } else {
295             target = generateTypeSwitch(lookup, invocationType.parameterType(0), labels);
296         }
297 
298         target = target.asType(invocationType);
299         target = withIndexCheck(target, labels.length);
300 
301         return new ConstantCallSite(target);
302     }
303 
304     private static <E extends Enum<E>> Object convertEnumConstants(MethodHandles.Lookup lookup, Class<?> enumClassTemplate, Object label) {
305         if (label == null) {
306             throw new IllegalArgumentException("null label found");
307         }
308         Class<?> labelClass = label.getClass();
309         if (labelClass == Class.class) {
310             if (label != enumClassTemplate) {
311                 throw new IllegalArgumentException("the Class label: " + label +
312                                                    ", expected the provided enum class: " + enumClassTemplate);
313             }
314             return label;
315         } else if (labelClass == String.class) {
316             return EnumDesc.of(enumClassTemplate.describeConstable().orElseThrow(), (String) label);
317         } else {
318             throw new IllegalArgumentException("label with illegal type found: " + labelClass +
319                                                ", expected label of type either String or Class");
320         }
321     }
322 
323     private static <T extends Enum<T>> int mappedEnumLookup(T value, MethodHandles.Lookup lookup, Class<T> enumClass, EnumDesc<?>[] labels, EnumMap enumMap) {
324         if (enumMap.map == null) {
325             T[] constants = SharedSecrets.getJavaLangAccess().getEnumConstantsShared(enumClass);
326             int[] map = new int[constants.length];
327             int ordinal = 0;
328 
329             for (T constant : constants) {
330                 map[ordinal] = labels.length;
331 
332                 for (int i = 0; i < labels.length; i++) {
333                     if (Objects.equals(labels[i].constantName(), constant.name())) {
334                         map[ordinal] = i;
335                         break;
336                     }
337                 }
338 
339                 ordinal++;
340             }
341         }
342         return enumMap.map[value.ordinal()];
343     }
344 
345     private static MethodHandle withIndexCheck(MethodHandle target, int labelsCount) {
346         MethodHandle checkIndex = MethodHandles.insertArguments(CHECK_INDEX, 1, labelsCount + 1);
347 
348         return MethodHandles.filterArguments(target, 1, checkIndex);
349     }
350 
351     private static final class ResolvedEnumLabels implements BiPredicate<Integer, Object> {
352 
353         private final MethodHandles.Lookup lookup;
354         private final EnumDesc<?>[] enumDescs;
355         @Stable
356         private Object[] resolvedEnum;
357 
358         public ResolvedEnumLabels(MethodHandles.Lookup lookup, EnumDesc<?>[] enumDescs) {
359             this.lookup = lookup;
360             this.enumDescs = enumDescs;
361             this.resolvedEnum = new Object[enumDescs.length];
362         }
363 
364         @Override
365         public boolean test(Integer labelIndex, Object value) {
366             Object result = resolvedEnum[labelIndex];
367 
368             if (result == null) {
369                 try {
370                     if (!(value instanceof Enum<?> enumValue)) {
371                         return false;
372                     }
373 
374                     EnumDesc<?> label = enumDescs[labelIndex];
375                     Class<?> clazz = label.constantType().resolveConstantDesc(lookup);
376 
377                     if (enumValue.getDeclaringClass() != clazz) {
378                         return false;
379                     }
380 
381                     result = label.resolveConstantDesc(lookup);
382                 } catch (IllegalArgumentException | ReflectiveOperationException ex) {
383                     result = SENTINEL;
384                 }
385 
386                 resolvedEnum[labelIndex] = result;
387             }
388 
389             return result == value;
390         }
391     }
392 
393     private static final class EnumMap {
394         @Stable
395         public int[] map;
396     }
397 
398     /*
399      * Construct test chains for labels inside switch, to handle switch repeats:
400      * switch (idx) {
401      *     case 0 -> if (selector matches label[0]) return 0;
402      *     case 1 -> if (selector matches label[1]) return 1;
403      *     ...
404      * }
405      */
406     private static Consumer<CodeBuilder> generateTypeSwitchSkeleton(Class<?> selectorType, Object[] labelConstants, List<EnumDesc<?>> enumDescs, List<Class<?>> extraClassLabels) {
407         int SELECTOR_OBJ        = 0;
408         int RESTART_IDX         = 1;
409         int ENUM_CACHE          = 2;
410         int EXTRA_CLASS_LABELS  = 3;
411 
412         return cb -> {
413             cb.aload(SELECTOR_OBJ);
414             Label nonNullLabel = cb.newLabel();
415             cb.if_nonnull(nonNullLabel);
416             cb.iconst_m1();
417             cb.ireturn();
418             cb.labelBinding(nonNullLabel);
419             if (labelConstants.length == 0) {
420                 cb.constantInstruction(0)
421                         .ireturn();
422                 return;
423             }
424             cb.iload(RESTART_IDX);
425             Label dflt = cb.newLabel();
426             record Element(Label target, Label next, Object caseLabel) { }
427             List<Element> cases = new ArrayList<>();
428             List<SwitchCase> switchCases = new ArrayList<>();
429             Object lastLabel = null;
430             for (int idx = labelConstants.length - 1; idx >= 0; idx--) {
431                 Object currentLabel = labelConstants[idx];
432                 Label target = cb.newLabel();
433                 Label next;
434                 if (lastLabel == null) {
435                     next = dflt;
436                 } else if (lastLabel.equals(currentLabel)) {
437                     next = cases.getLast().next();
438                 } else {
439                     next = cases.getLast().target();
440                 }
441                 lastLabel = currentLabel;
442                 cases.add(new Element(target, next, currentLabel));
443                 switchCases.add(SwitchCase.of(idx, target));
444             }
445             cases = cases.reversed();
446             switchCases = switchCases.reversed();
447             cb.tableswitch(0, labelConstants.length - 1, dflt, switchCases);
448             for (int idx = 0; idx < cases.size(); idx++) {
449                 Element element = cases.get(idx);
450                 Label next = element.next();
451                 cb.labelBinding(element.target());
452                 if (element.caseLabel() instanceof Class<?> classLabel) {
453                     if (unconditionalExactnessCheck(selectorType, classLabel)) {
454                         //nothing - unconditionally use this case
455                     } else if (classLabel.isPrimitive()) {
456                         if (!selectorType.isPrimitive() && !Wrapper.isWrapperNumericOrBooleanType(selectorType)) {
457                             // Object o = ...
458                             // o instanceof Wrapped(float)
459                             cb.aload(SELECTOR_OBJ);
460                             cb.instanceof_(Wrapper.forBasicType(classLabel)
461                                     .wrapperType()
462                                     .describeConstable()
463                                     .orElseThrow());
464                             cb.ifeq(next);
465                         } else if (!unconditionalExactnessCheck(Wrapper.asPrimitiveType(selectorType), classLabel)) {
466                             // Integer i = ... or int i = ...
467                             // o instanceof float
468                             Label notNumber = cb.newLabel();
469                             cb.aload(SELECTOR_OBJ);
470                             cb.instanceof_(ConstantDescs.CD_Number);
471                             if (selectorType == long.class || selectorType == float.class || selectorType == double.class) {
472                                 cb.ifeq(next);
473                             } else {
474                                 cb.ifeq(notNumber);
475                             }
476                             cb.aload(SELECTOR_OBJ);
477                             cb.checkcast(ConstantDescs.CD_Number);
478                             if (selectorType == long.class) {
479                                 cb.invokevirtual(ConstantDescs.CD_Number,
480                                         "longValue",
481                                         MethodTypeDesc.of(ConstantDescs.CD_long));
482                             } else if (selectorType == float.class) {
483                                 cb.invokevirtual(ConstantDescs.CD_Number,
484                                         "floatValue",
485                                         MethodTypeDesc.of(ConstantDescs.CD_float));
486                             } else if (selectorType == double.class) {
487                                 cb.invokevirtual(ConstantDescs.CD_Number,
488                                         "doubleValue",
489                                         MethodTypeDesc.of(ConstantDescs.CD_double));
490                             } else {
491                                 Label compare = cb.newLabel();
492                                 cb.invokevirtual(ConstantDescs.CD_Number,
493                                         "intValue",
494                                         MethodTypeDesc.of(ConstantDescs.CD_int));
495                                 cb.goto_(compare);
496                                 cb.labelBinding(notNumber);
497                                 cb.aload(SELECTOR_OBJ);
498                                 cb.instanceof_(ConstantDescs.CD_Character);
499                                 cb.ifeq(next);
500                                 cb.aload(SELECTOR_OBJ);
501                                 cb.checkcast(ConstantDescs.CD_Character);
502                                 cb.invokevirtual(ConstantDescs.CD_Character,
503                                         "charValue",
504                                         MethodTypeDesc.of(ConstantDescs.CD_char));
505                                 cb.labelBinding(compare);
506                             }
507 
508                             TypePairs typePair = TypePairs.of(Wrapper.asPrimitiveType(selectorType), classLabel);
509                             String methodName = typePairToName.get(typePair);
510                             cb.invokestatic(ExactConversionsSupport.class.describeConstable().orElseThrow(),
511                                     methodName,
512                                     MethodTypeDesc.of(ConstantDescs.CD_boolean, typePair.from.describeConstable().orElseThrow()));
513                             cb.ifeq(next);
514                         }
515                     } else {
516                         Optional<ClassDesc> classLabelConstableOpt = classLabel.describeConstable();
517                         if (classLabelConstableOpt.isPresent()) {
518                             cb.aload(SELECTOR_OBJ);
519                             cb.instanceof_(classLabelConstableOpt.orElseThrow());
520                             cb.ifeq(next);
521                         } else {
522                             cb.aload(EXTRA_CLASS_LABELS);
523                             cb.constantInstruction(extraClassLabels.size());
524                             cb.invokeinterface(ConstantDescs.CD_List,
525                                     "get",
526                                     MethodTypeDesc.of(ConstantDescs.CD_Object,
527                                             ConstantDescs.CD_int));
528                             cb.checkcast(ConstantDescs.CD_Class);
529                             cb.aload(SELECTOR_OBJ);
530                             cb.invokevirtual(ConstantDescs.CD_Class,
531                                     "isInstance",
532                                     MethodTypeDesc.of(ConstantDescs.CD_boolean,
533                                             ConstantDescs.CD_Object));
534                             cb.ifeq(next);
535                             extraClassLabels.add(classLabel);
536                         }
537                     }
538                 } else if (element.caseLabel() instanceof EnumDesc<?> enumLabel) {
539                     int enumIdx = enumDescs.size();
540                     enumDescs.add(enumLabel);
541                     cb.aload(ENUM_CACHE);
542                     cb.constantInstruction(enumIdx);
543                     cb.invokestatic(ConstantDescs.CD_Integer,
544                             "valueOf",
545                             MethodTypeDesc.of(ConstantDescs.CD_Integer,
546                                     ConstantDescs.CD_int));
547                     cb.aload(SELECTOR_OBJ);
548                     cb.invokeinterface(BiPredicate.class.describeConstable().orElseThrow(),
549                             "test",
550                             MethodTypeDesc.of(ConstantDescs.CD_boolean,
551                                     ConstantDescs.CD_Object,
552                                     ConstantDescs.CD_Object));
553                     cb.ifeq(next);
554                 } else if (element.caseLabel() instanceof String stringLabel) {
555                     cb.ldc(stringLabel);
556                     cb.aload(SELECTOR_OBJ);
557                     cb.invokevirtual(ConstantDescs.CD_Object,
558                             "equals",
559                             MethodTypeDesc.of(ConstantDescs.CD_boolean,
560                                     ConstantDescs.CD_Object));
561                     cb.ifeq(next);
562                 } else if (element.caseLabel() instanceof Integer integerLabel) {
563                     Label compare = cb.newLabel();
564                     Label notNumber = cb.newLabel();
565                     cb.aload(SELECTOR_OBJ);
566                     cb.instanceof_(ConstantDescs.CD_Number);
567                     cb.ifeq(notNumber);
568                     cb.aload(SELECTOR_OBJ);
569                     cb.checkcast(ConstantDescs.CD_Number);
570                     cb.invokevirtual(ConstantDescs.CD_Number,
571                             "intValue",
572                             MethodTypeDesc.of(ConstantDescs.CD_int));
573                     cb.goto_(compare);
574                     cb.labelBinding(notNumber);
575                     cb.aload(SELECTOR_OBJ);
576                     cb.instanceof_(ConstantDescs.CD_Character);
577                     cb.ifeq(next);
578                     cb.aload(SELECTOR_OBJ);
579                     cb.checkcast(ConstantDescs.CD_Character);
580                     cb.invokevirtual(ConstantDescs.CD_Character,
581                             "charValue",
582                             MethodTypeDesc.of(ConstantDescs.CD_char));
583                     cb.labelBinding(compare);
584 
585                     cb.ldc(integerLabel);
586                     cb.if_icmpne(next);
587                 } else if ((element.caseLabel() instanceof Long ||
588                         element.caseLabel() instanceof Float ||
589                         element.caseLabel() instanceof Double ||
590                         element.caseLabel() instanceof Boolean)) {
591                     if (element.caseLabel() instanceof Boolean c) {
592                         cb.constantInstruction(c ? 1 : 0);
593                     } else {
594                         cb.constantInstruction((ConstantDesc) element.caseLabel());
595                     }
596                     cb.invokestatic(element.caseLabel().getClass().describeConstable().orElseThrow(),
597                             "valueOf",
598                             MethodTypeDesc.of(element.caseLabel().getClass().describeConstable().orElseThrow(),
599                                     Wrapper.asPrimitiveType(element.caseLabel().getClass()).describeConstable().orElseThrow()));
600                     cb.aload(SELECTOR_OBJ);
601                     cb.invokevirtual(ConstantDescs.CD_Object,
602                             "equals",
603                             MethodTypeDesc.of(ConstantDescs.CD_boolean,
604                                     ConstantDescs.CD_Object));
605                     cb.ifeq(next);
606                 } else {
607                     throw new InternalError("Unsupported label type: " +
608                             element.caseLabel().getClass());
609                 }
610                 cb.constantInstruction(idx);
611                 cb.ireturn();
612             }
613             cb.labelBinding(dflt);
614             cb.constantInstruction(cases.size());
615             cb.ireturn();
616         };
617     }
618 
619     /*
620      * Construct the method handle that represents the method int typeSwitch(Object, int, BiPredicate, List)
621      */
622     private static MethodHandle generateTypeSwitch(MethodHandles.Lookup caller, Class<?> selectorType, Object[] labelConstants) {
623         List<EnumDesc<?>> enumDescs = new ArrayList<>();
624         List<Class<?>> extraClassLabels = new ArrayList<>();
625 
626         byte[] classBytes = ClassFile.of().build(ClassDesc.of(typeSwitchClassName(caller.lookupClass())),
627                 clb -> {
628                     clb.withFlags(AccessFlag.FINAL, (PreviewFeatures.isEnabled())  ? AccessFlag.IDENTITY : AccessFlag.SUPER, AccessFlag.SYNTHETIC)
629                        .withMethodBody("typeSwitch",
630                                        TYPES_SWITCH_DESCRIPTOR,
631                                        ClassFile.ACC_FINAL | ClassFile.ACC_PUBLIC | ClassFile.ACC_STATIC,
632                                        generateTypeSwitchSkeleton(selectorType, labelConstants, enumDescs, extraClassLabels));
633         });
634 
635         try {
636             // this class is linked at the indy callsite; so define a hidden nestmate
637             MethodHandles.Lookup lookup;
638             lookup = caller.defineHiddenClass(classBytes, true, NESTMATE, STRONG);
639             MethodHandle typeSwitch = lookup.findStatic(lookup.lookupClass(),
640                                                         "typeSwitch",
641                                                         MethodType.methodType(int.class,
642                                                                               Object.class,
643                                                                               int.class,
644                                                                               BiPredicate.class,
645                                                                               List.class));
646             typeSwitch = MethodHandles.insertArguments(typeSwitch, 2, new ResolvedEnumLabels(caller, enumDescs.toArray(EnumDesc[]::new)),
647                                                        List.copyOf(extraClassLabels));
648             typeSwitch = MethodHandles.explicitCastArguments(typeSwitch,
649                                                              MethodType.methodType(int.class,
650                                                                                    selectorType,
651                                                                                    int.class));
652             return typeSwitch;
653         } catch (Throwable t) {
654             throw new IllegalArgumentException(t);
655         }
656     }
657 
658     //based on src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java:
659     private static String typeSwitchClassName(Class<?> targetClass) {
660         String name = targetClass.getName();
661         if (targetClass.isHidden()) {
662             // use the original class name
663             name = name.replace('/', '_');
664         }
665         return name + "$$TypeSwitch";
666     }
667 
668     // this method should be in sync with com.sun.tools.javac.code.Types.checkUnconditionallyExactPrimitives
669     private static boolean unconditionalExactnessCheck(Class<?> selectorType, Class<?> targetType) {
670         Wrapper selectorWrapper = Wrapper.forBasicType(selectorType);
671         Wrapper targetWrapper   = Wrapper.forBasicType(targetType);
672         if (selectorType.isPrimitive() && targetType.equals(selectorWrapper.wrapperType())) {
673             return true;
674         }
675         else if (selectorType.equals(targetType) ||
676                 ((selectorType.equals(byte.class) && !targetType.equals(char.class)) ||
677                  (selectorType.equals(short.class) && (selectorWrapper.isStrictSubRangeOf(targetWrapper))) ||
678                  (selectorType.equals(char.class)  && (selectorWrapper.isStrictSubRangeOf(targetWrapper)))  ||
679                  (selectorType.equals(int.class)   && (targetType.equals(double.class) || targetType.equals(long.class))) ||
680                  (selectorType.equals(float.class) && (selectorWrapper.isStrictSubRangeOf(targetWrapper))))) return true;
681         return false;
682     }
683 
684     // TypePairs should be in sync with the corresponding record in Lower
685     record TypePairs(Class<?> from, Class<?> to) {
686         public static TypePairs of(Class<?> from,  Class<?> to) {
687             if (from == byte.class || from == short.class || from == char.class) {
688                 from = int.class;
689             }
690             return new TypePairs(from, to);
691         }
692 
693         public static Map<TypePairs, String> initialize() {
694             Map<TypePairs, String> typePairToName = new HashMap<>();
695             typePairToName.put(new TypePairs(byte.class,   char.class),   "isIntToCharExact");      // redirected
696             typePairToName.put(new TypePairs(short.class,  byte.class),   "isIntToByteExact");      // redirected
697             typePairToName.put(new TypePairs(short.class,  char.class),   "isIntToCharExact");      // redirected
698             typePairToName.put(new TypePairs(char.class,   byte.class),   "isIntToByteExact");      // redirected
699             typePairToName.put(new TypePairs(char.class,   short.class),  "isIntToShortExact");     // redirected
700             typePairToName.put(new TypePairs(int.class,    byte.class),   "isIntToByteExact");
701             typePairToName.put(new TypePairs(int.class,    short.class),  "isIntToShortExact");
702             typePairToName.put(new TypePairs(int.class,    char.class),   "isIntToCharExact");
703             typePairToName.put(new TypePairs(int.class,    float.class),  "isIntToFloatExact");
704             typePairToName.put(new TypePairs(long.class,   byte.class),   "isLongToByteExact");
705             typePairToName.put(new TypePairs(long.class,   short.class),  "isLongToShortExact");
706             typePairToName.put(new TypePairs(long.class,   char.class),   "isLongToCharExact");
707             typePairToName.put(new TypePairs(long.class,   int.class),    "isLongToIntExact");
708             typePairToName.put(new TypePairs(long.class,   float.class),  "isLongToFloatExact");
709             typePairToName.put(new TypePairs(long.class,   double.class), "isLongToDoubleExact");
710             typePairToName.put(new TypePairs(float.class,  byte.class),   "isFloatToByteExact");
711             typePairToName.put(new TypePairs(float.class,  short.class),  "isFloatToShortExact");
712             typePairToName.put(new TypePairs(float.class,  char.class),   "isFloatToCharExact");
713             typePairToName.put(new TypePairs(float.class,  int.class),    "isFloatToIntExact");
714             typePairToName.put(new TypePairs(float.class,  long.class),   "isFloatToLongExact");
715             typePairToName.put(new TypePairs(double.class, byte.class),   "isDoubleToByteExact");
716             typePairToName.put(new TypePairs(double.class, short.class),  "isDoubleToShortExact");
717             typePairToName.put(new TypePairs(double.class, char.class),   "isDoubleToCharExact");
718             typePairToName.put(new TypePairs(double.class, int.class),    "isDoubleToIntExact");
719             typePairToName.put(new TypePairs(double.class, long.class),   "isDoubleToLongExact");
720             typePairToName.put(new TypePairs(double.class, float.class),  "isDoubleToFloatExact");
721             return typePairToName;
722         }
723     }
724 }