1 /*
  2  * Copyright (c) 2017, 2025, 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.classfile.attribute.StackMapFrameInfo;
 31 import java.lang.classfile.attribute.StackMapTableAttribute;
 32 import java.lang.constant.ClassDesc;
 33 import java.lang.constant.ConstantDesc;
 34 import java.lang.constant.MethodTypeDesc;
 35 import java.lang.invoke.CallSite;
 36 import java.lang.invoke.ConstantCallSite;
 37 import java.lang.invoke.MethodHandle;
 38 import java.lang.invoke.MethodHandles;
 39 import java.lang.invoke.MethodType;
 40 import java.lang.reflect.AccessFlag;
 41 import java.util.ArrayList;
 42 import java.util.List;
 43 import java.util.Objects;
 44 import java.util.Optional;
 45 import java.util.function.BiPredicate;
 46 import java.util.function.Consumer;
 47 
 48 import jdk.internal.access.SharedSecrets;
 49 import java.lang.classfile.ClassFile;
 50 import java.lang.classfile.Label;
 51 import java.lang.classfile.instruction.SwitchCase;
 52 
 53 import jdk.internal.classfile.impl.DirectCodeBuilder;
 54 import jdk.internal.constant.ClassOrInterfaceDescImpl;
 55 import jdk.internal.constant.ConstantUtils;
 56 import jdk.internal.constant.MethodTypeDescImpl;
 57 import jdk.internal.misc.PreviewFeatures;
 58 import jdk.internal.vm.annotation.Stable;
 59 
 60 import static java.lang.constant.ConstantDescs.*;
 61 import static java.lang.invoke.MethodHandles.Lookup.ClassOption.NESTMATE;
 62 import static java.lang.invoke.MethodHandles.Lookup.ClassOption.STRONG;
 63 import java.util.Arrays;
 64 import java.util.HashMap;
 65 import java.util.Map;
 66 import static java.util.Objects.requireNonNull;
 67 import static jdk.internal.constant.ConstantUtils.classDesc;
 68 import static jdk.internal.constant.ConstantUtils.referenceClassDesc;
 69 
 70 import sun.invoke.util.Wrapper;
 71 
 72 /**
 73  * Bootstrap methods for linking {@code invokedynamic} call sites that implement
 74  * the selection functionality of the {@code switch} statement.  The bootstraps
 75  * take additional static arguments corresponding to the {@code case} labels
 76  * of the {@code switch}, implicitly numbered sequentially from {@code [0..N)}.
 77  *
 78  * @since 21
 79  */
 80 public final class SwitchBootstraps {
 81 
 82     private SwitchBootstraps() {}
 83 
 84     private static final Object SENTINEL = new Object();
 85     private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
 86     private static final boolean previewEnabled = PreviewFeatures.isEnabled();
 87 
 88     private static final ClassDesc CD_BiPredicate = ClassOrInterfaceDescImpl.ofValidated("Ljava/util/function/BiPredicate;");
 89     private static final ClassDesc CD_Objects = ClassOrInterfaceDescImpl.ofValidated("Ljava/util/Objects;");
 90 
 91     private static final MethodTypeDesc CHECK_INDEX_DESCRIPTOR =
 92             MethodTypeDescImpl.ofValidated(CD_int, CD_int, CD_int);
 93     private static final MethodTypeDesc MTD_TYPE_SWITCH = MethodTypeDescImpl.ofValidated(CD_int,
 94             CD_Object,
 95             CD_int);
 96     private static final MethodTypeDesc MTD_TYPE_SWITCH_EXTRA = MethodTypeDescImpl.ofValidated(CD_int,
 97             CD_Object,
 98             CD_int,
 99             CD_BiPredicate,
100             CD_List);
101     private static final MethodType MT_TYPE_SWITCH_EXTRA = MethodType.methodType(int.class,
102             Object.class,
103             int.class,
104             BiPredicate.class,
105             List.class);
106     private static final MethodType MT_TYPE_SWITCH = MethodType.methodType(int.class,
107             Object.class,
108             int.class);
109     private static final List<StackMapFrameInfo.VerificationTypeInfo> TYPE_SWITCH_LOCALS = List.of(
110             StackMapFrameInfo.ObjectVerificationTypeInfo.of(CD_Object), StackMapFrameInfo.SimpleVerificationTypeInfo.INTEGER
111     );
112     private static final List<StackMapFrameInfo.VerificationTypeInfo> TYPE_SWITCH_EXTRA_LOCALS = List.of(
113             StackMapFrameInfo.ObjectVerificationTypeInfo.of(CD_Object), StackMapFrameInfo.SimpleVerificationTypeInfo.INTEGER,
114             StackMapFrameInfo.ObjectVerificationTypeInfo.of(CD_BiPredicate), StackMapFrameInfo.ObjectVerificationTypeInfo.of(CD_List)
115     );
116 
117     private static class StaticHolders {
118         private static final MethodHandle MAPPED_ENUM_SWITCH;
119 
120         static {
121             try {
122                 MAPPED_ENUM_SWITCH = LOOKUP.findStatic(SwitchBootstraps.class, "mappedEnumSwitch",
123                                                        MethodType.methodType(int.class, Enum.class, int.class, MethodHandles.Lookup.class,
124                                                                              Class.class, EnumDesc[].class, MappedEnumCache.class));
125             }
126             catch (ReflectiveOperationException e) {
127                 throw new ExceptionInInitializerError(e);
128             }
129         }
130     }
131 
132     /**
133      * Bootstrap method for linking an {@code invokedynamic} call site that
134      * implements a {@code switch} on a target of a reference type.  The static
135      * arguments are an array of case labels which must be non-null and of type
136      * {@code String} or {@code Integer} or {@code Class} or {@code EnumDesc}.
137      * <p>
138      * The type of the returned {@code CallSite}'s method handle will have
139      * a return type of {@code int}.   It has two parameters: the first argument
140      * will be an {@code Object} instance ({@code target}) and the second
141      * will be {@code int} ({@code restart}).
142      * <p>
143      * If the {@code target} is {@code null}, then the method of the call site
144      * returns {@literal -1}.
145      * <p>
146      * If the {@code target} is not {@code null}, then the method of the call site
147      * returns the index of the first element in the {@code labels} array starting from
148      * the {@code restart} index matching one of the following conditions:
149      * <ul>
150      *   <li>the element is of type {@code Class} that is assignable
151      *       from the target's class; or</li>
152      *   <li>the element is of type {@code String} or {@code Integer} and
153      *       equals to the target.</li>
154      *   <li>the element is of type {@code EnumDesc}, that describes a constant that is
155      *       equals to the target.</li>
156      * </ul>
157      * <p>
158      * If no element in the {@code labels} array matches the target, then
159      * the method of the call site return the length of the {@code labels} array.
160      * <p>
161      * The value of the {@code restart} index must be between {@code 0} (inclusive) and
162      * the length of the {@code labels} array (inclusive),
163      * both  or an {@link IndexOutOfBoundsException} is thrown.
164      *
165      * @param lookup Represents a lookup context with the accessibility
166      *               privileges of the caller.  When used with {@code invokedynamic},
167      *               this is stacked automatically by the VM.
168      * @param invocationName unused
169      * @param invocationType The invocation type of the {@code CallSite} with two parameters,
170      *                       a reference type, an {@code int}, and {@code int} as a return type.
171      * @param labels case labels - {@code String} and {@code Integer} constants
172      *               and {@code Class} and {@code EnumDesc} instances, in any combination
173      * @return a {@code CallSite} returning the first matching element as described above
174      *
175      * @throws NullPointerException     if any argument is {@code null}
176      * @throws IllegalArgumentException if any element in the labels array is null
177      * @throws IllegalArgumentException if the invocation type is not a method type of first parameter of a reference type,
178      *                                  second parameter of type {@code int} and with {@code int} as its return type,
179      * @throws IllegalArgumentException if {@code labels} contains an element that is not of type {@code String},
180      *                                  {@code Integer}, {@code Long}, {@code Float}, {@code Double}, {@code Boolean},
181      *                                  {@code Class} or {@code EnumDesc}.
182      * @throws IllegalArgumentException if {@code labels} contains an element that is not of type {@code Boolean}
183      *                                  when {@code target} is a {@code Boolean.class}.
184      * @jvms 4.4.6 The CONSTANT_NameAndType_info Structure
185      * @jvms 4.4.10 The CONSTANT_Dynamic_info and CONSTANT_InvokeDynamic_info Structures
186      */
187     public static CallSite typeSwitch(MethodHandles.Lookup lookup,
188                                       String invocationName,
189                                       MethodType invocationType,
190                                       Object... labels) {
191         Class<?> selectorType = invocationType.parameterType(0);
192         if (invocationType.parameterCount() != 2
193             || (!invocationType.returnType().equals(int.class))
194             || !invocationType.parameterType(1).equals(int.class))
195             throw new IllegalArgumentException("Illegal invocation type " + invocationType);
196 
197         for (Object l : labels) { // implicit null-check
198             verifyLabel(l, selectorType);
199         }
200 
201         MethodHandle target = generateTypeSwitch(lookup, selectorType, labels);
202         target = target.asType(invocationType);
203         return new ConstantCallSite(target);
204     }
205 
206     private static void verifyLabel(Object label, Class<?> selectorType) {
207         if (label == null) {
208             throw new IllegalArgumentException("null label found");
209         }
210         Class<?> labelClass = label.getClass();
211 
212         if (labelClass != Class.class &&
213             labelClass != String.class &&
214             labelClass != Integer.class &&
215 
216             ((labelClass != Float.class &&
217               labelClass != Long.class &&
218               labelClass != Double.class &&
219               labelClass != Boolean.class) ||
220               ((selectorType.equals(boolean.class) || selectorType.equals(Boolean.class)) && labelClass != Boolean.class && labelClass != Class.class) ||
221              !previewEnabled) &&
222 
223             labelClass != EnumDesc.class) {
224             throw new IllegalArgumentException("label with illegal type found: " + label.getClass());
225         }
226     }
227 
228     /**
229      * Bootstrap method for linking an {@code invokedynamic} call site that
230      * implements a {@code switch} on a target of an enum type. The static
231      * arguments are used to encode the case labels associated to the switch
232      * construct, where each label can be encoded in two ways:
233      * <ul>
234      *   <li>as a {@code String} value, which represents the name of
235      *       the enum constant associated with the label</li>
236      *   <li>as a {@code Class} value, which represents the enum type
237      *       associated with a type test pattern</li>
238      * </ul>
239      * <p>
240      * The returned {@code CallSite}'s method handle will have
241      * a return type of {@code int} and accepts two parameters: the first argument
242      * will be an {@code Enum} instance ({@code target}) and the second
243      * will be {@code int} ({@code restart}).
244      * <p>
245      * If the {@code target} is {@code null}, then the method of the call site
246      * returns {@literal -1}.
247      * <p>
248      * If the {@code target} is not {@code null}, then the method of the call site
249      * returns the index of the first element in the {@code labels} array starting from
250      * the {@code restart} index matching one of the following conditions:
251      * <ul>
252      *   <li>the element is of type {@code Class} that is assignable
253      *       from the target's class; or</li>
254      *   <li>the element is of type {@code String} and equals to the target
255      *       enum constant's {@link Enum#name()}.</li>
256      * </ul>
257      * <p>
258      * If no element in the {@code labels} array matches the target, then
259      * the method of the call site return the length of the {@code labels} array.
260      * <p>
261      * The value of the {@code restart} index must be between {@code 0} (inclusive) and
262      * the length of the {@code labels} array (inclusive),
263      * both  or an {@link IndexOutOfBoundsException} is thrown.
264      *
265      * @param lookup Represents a lookup context with the accessibility
266      *               privileges of the caller. When used with {@code invokedynamic},
267      *               this is stacked automatically by the VM.
268      * @param invocationName unused
269      * @param invocationType The invocation type of the {@code CallSite} with two parameters,
270      *                       an enum type, an {@code int}, and {@code int} as a return type.
271      * @param labels case labels - {@code String} constants and {@code Class} instances,
272      *               in any combination
273      * @return a {@code CallSite} returning the first matching element as described above
274      *
275      * @throws NullPointerException if any argument is {@code null}
276      * @throws IllegalArgumentException if any element in the labels array is null, if the
277      * invocation type is not a method type whose first parameter type is an enum type,
278      * second parameter of type {@code int} and whose return type is {@code int},
279      * or if {@code labels} contains an element that is not of type {@code String} or
280      * {@code Class} of the target enum type.
281      * @jvms 4.4.6 The CONSTANT_NameAndType_info Structure
282      * @jvms 4.4.10 The CONSTANT_Dynamic_info and CONSTANT_InvokeDynamic_info Structures
283      */
284     public static CallSite enumSwitch(MethodHandles.Lookup lookup,
285                                       String invocationName,
286                                       MethodType invocationType,
287                                       Object... labels) {
288         if (invocationType.parameterCount() != 2
289             || (!invocationType.returnType().equals(int.class))
290             || invocationType.parameterType(0).isPrimitive()
291             || !invocationType.parameterType(0).isEnum()
292             || !invocationType.parameterType(1).equals(int.class))
293             throw new IllegalArgumentException("Illegal invocation type " + invocationType);
294 
295         labels = labels.clone(); // implicit null check
296 
297         Class<?> enumClass = invocationType.parameterType(0);
298         boolean constantsOnly = true;
299         int len = labels.length;
300 
301         for (int i = 0; i < len; i++) {
302             Object convertedLabel =
303                     convertEnumConstants(lookup, enumClass, labels[i]);
304             labels[i] = convertedLabel;
305             if (constantsOnly)
306                 constantsOnly = convertedLabel instanceof EnumDesc;
307         }
308 
309         MethodHandle target;
310 
311         if (labels.length > 0 && constantsOnly) {
312             //If all labels are enum constants, construct an optimized handle for repeat index 0:
313             //if (selector == null) return -1
314             //else if (idx == 0) return mappingArray[selector.ordinal()]; //mapping array created lazily
315             //else return "typeSwitch(labels)"
316             EnumDesc<?>[] enumDescLabels =
317                     Arrays.copyOf(labels, labels.length, EnumDesc[].class);
318             target = MethodHandles.insertArguments(StaticHolders.MAPPED_ENUM_SWITCH, 2, lookup, enumClass, enumDescLabels, new MappedEnumCache());
319         } else {
320             target = generateTypeSwitch(lookup, invocationType.parameterType(0), labels);
321         }
322         target = target.asType(invocationType);
323 
324         return new ConstantCallSite(target);
325     }
326 
327     private static <E extends Enum<E>> Object convertEnumConstants(MethodHandles.Lookup lookup, Class<?> enumClassTemplate, Object label) {
328         if (label == null) {
329             throw new IllegalArgumentException("null label found");
330         }
331         Class<?> labelClass = label.getClass();
332         if (labelClass == Class.class) {
333             if (label != enumClassTemplate) {
334                 throw new IllegalArgumentException("the Class label: " + label +
335                                                    ", expected the provided enum class: " + enumClassTemplate);
336             }
337             return label;
338         } else if (labelClass == String.class) {
339             return EnumDesc.of(referenceClassDesc(enumClassTemplate), (String) label);
340         } else {
341             throw new IllegalArgumentException("label with illegal type found: " + labelClass +
342                                                ", expected label of type either String or Class");
343         }
344     }
345 
346     private static <T extends Enum<T>> int mappedEnumSwitch(T value, int restartIndex, MethodHandles.Lookup lookup, Class<T> enumClass, EnumDesc<?>[] labels, MappedEnumCache enumCache) throws Throwable {
347         if (value == null) {
348             return -1;
349         }
350 
351         if (restartIndex != 0) {
352             MethodHandle generatedSwitch = enumCache.generatedSwitch;
353             if (generatedSwitch == null) {
354                 synchronized (enumCache) {
355                     generatedSwitch = enumCache.generatedSwitch;
356 
357                     if (generatedSwitch == null) {
358                         generatedSwitch =
359                                 generateTypeSwitch(lookup, enumClass, labels)
360                                         .asType(MethodType.methodType(int.class,
361                                                                       Enum.class,
362                                                                       int.class));
363                         enumCache.generatedSwitch = generatedSwitch;
364                     }
365                 }
366             }
367 
368             return (int) generatedSwitch.invokeExact(value, restartIndex);
369         }
370 
371         int[] constantsMap = enumCache.constantsMap;
372 
373         if (constantsMap == null) {
374             synchronized (enumCache) {
375                 constantsMap = enumCache.constantsMap;
376 
377                 if (constantsMap == null) {
378                     T[] constants = SharedSecrets.getJavaLangAccess()
379                                                  .getEnumConstantsShared(enumClass);
380                     constantsMap = new int[constants.length];
381                     int ordinal = 0;
382 
383                     for (T constant : constants) {
384                         constantsMap[ordinal] = labels.length;
385 
386                         for (int i = 0; i < labels.length; i++) {
387                             if (Objects.equals(labels[i].constantName(),
388                                                constant.name())) {
389                                 constantsMap[ordinal] = i;
390                                 break;
391                             }
392                         }
393 
394                         ordinal++;
395                     }
396 
397                     enumCache.constantsMap = constantsMap;
398                 }
399             }
400         }
401 
402         return constantsMap[value.ordinal()];
403     }
404 
405     private static final class ResolvedEnumLabels implements BiPredicate<Integer, Object> {
406 
407         private final MethodHandles.Lookup lookup;
408         private final EnumDesc<?>[] enumDescs;
409         @Stable
410         private final Object[] resolvedEnum;
411 
412         public ResolvedEnumLabels(MethodHandles.Lookup lookup, EnumDesc<?>[] enumDescs) {
413             this.lookup = lookup;
414             this.enumDescs = enumDescs;
415             this.resolvedEnum = new Object[enumDescs.length];
416         }
417 
418         @Override
419         public boolean test(Integer labelIndex, Object value) {
420             Object result = resolvedEnum[labelIndex];
421 
422             if (result == null) {
423                 try {
424                     if (!(value instanceof Enum<?> enumValue)) {
425                         return false;
426                     }
427 
428                     EnumDesc<?> label = enumDescs[labelIndex];
429                     Class<?> clazz = label.constantType().resolveConstantDesc(lookup);
430 
431                     if (enumValue.getDeclaringClass() != clazz) {
432                         return false;
433                     }
434 
435                     result = label.resolveConstantDesc(lookup);
436                 } catch (IllegalArgumentException | ReflectiveOperationException ex) {
437                     result = SENTINEL;
438                 }
439 
440                 resolvedEnum[labelIndex] = result;
441             }
442 
443             return result == value;
444         }
445     }
446 
447     private static final class MappedEnumCache {
448         @Stable
449         public int[] constantsMap;
450         @Stable
451         public MethodHandle generatedSwitch;
452     }
453 
454     /**
455      * Check if the labelConstants can be converted statically to bytecode, or
456      * whether we'll need to compute and pass in extra information at the call site.
457      */
458     private static boolean needsExtraInfo(Class<?> selectorType, Object[] labelConstants) {
459         for (int idx = labelConstants.length - 1; idx >= 0; idx--) {
460             Object currentLabel = labelConstants[idx];
461             if (currentLabel instanceof Class<?> classLabel) {
462                 // No extra info needed for exact matches or primitives
463                 if (unconditionalExactnessCheck(selectorType, classLabel) || classLabel.isPrimitive()) {
464                     continue;
465                 }
466                 // Hidden classes - or arrays thereof - can't be nominally
467                 // represented. Passed in as arguments.
468                 while (classLabel.isArray()) {
469                     classLabel = classLabel.getComponentType();
470                 }
471                 if (classLabel.isHidden()) {
472                     return true;
473                 }
474             } else if (currentLabel instanceof EnumDesc<?>) {
475                 // EnumDescs labels needs late binding
476                 return true;
477             }
478         }
479         return false;
480     }
481     /*
482      * Construct test chains for labels inside switch, to handle switch repeats:
483      * switch (idx) {
484      *     case 0 -> if (selector matches label[0]) return 0;
485      *     case 1 -> if (selector matches label[1]) return 1;
486      *     ...
487      * }
488      */
489     private static Consumer<CodeBuilder> generateTypeSwitchSkeleton(Class<?> selectorType, Object[] labelConstants, List<EnumDesc<?>> enumDescs, List<Class<?>> extraClassLabels) {
490         int SELECTOR_OBJ        = 0;
491         int RESTART_IDX         = 1;
492         int ENUM_CACHE          = 2;
493         int EXTRA_CLASS_LABELS  = 3;
494 
495         var locals = enumDescs == null && extraClassLabels == null ? TYPE_SWITCH_LOCALS : TYPE_SWITCH_EXTRA_LOCALS;
496 
497         return cb -> {
498             // Objects.checkIndex(RESTART_IDX, labelConstants + 1)
499             var stackMapFrames = new ArrayList<StackMapFrameInfo>(labelConstants.length * 2);
500             cb.iload(RESTART_IDX)
501               .loadConstant(labelConstants.length + 1)
502               .invokestatic(CD_Objects, "checkIndex", CHECK_INDEX_DESCRIPTOR)
503               .pop()
504               .aload(SELECTOR_OBJ);
505             Label nonNullLabel = cb.newLabel();
506             cb.ifnonnull(nonNullLabel)
507               .iconst_m1()
508               .ireturn()
509               .labelBinding(nonNullLabel);
510             stackMapFrames.add(StackMapFrameInfo.of(nonNullLabel, locals, List.of()));
511             if (labelConstants.length == 0) {
512                 cb.loadConstant(0)
513                   .ireturn()
514                   .with(StackMapTableAttribute.of(stackMapFrames));
515                 DirectCodeBuilder.withMaxs(cb, 2, locals.size()); // checkIndex uses 2
516                 return;
517             }
518             cb.iload(RESTART_IDX);
519             Label dflt = cb.newLabel();
520             Label[] caseTargets = new Label[labelConstants.length];
521             Label[] caseNext = new Label[labelConstants.length];
522             Object[] caseLabels = new Object[labelConstants.length];
523             SwitchCase[] switchCases = new SwitchCase[labelConstants.length];
524             Object lastLabel = null;
525             for (int idx = labelConstants.length - 1; idx >= 0; idx--) {
526                 Object currentLabel = labelConstants[idx];
527                 Label target = cb.newLabel();
528                 stackMapFrames.add(StackMapFrameInfo.of(target, locals, List.of()));
529                 Label next;
530                 if (lastLabel == null) {
531                     next = dflt;
532                 } else if (lastLabel.equals(currentLabel)) {
533                     next = caseNext[idx + 1];
534                 } else {
535                     next = caseTargets[idx + 1];
536                 }
537                 lastLabel = currentLabel;
538                 caseTargets[idx] = target;
539                 caseNext[idx] = next;
540                 caseLabels[idx] = currentLabel;
541                 switchCases[idx] = SwitchCase.of(idx, target);
542             }
543             cb.tableswitch(0, labelConstants.length - 1, dflt, Arrays.asList(switchCases));
544             for (int idx = 0; idx < labelConstants.length; idx++) {
545                 Label next = caseNext[idx];
546                 Object caseLabel = caseLabels[idx];
547                 cb.labelBinding(caseTargets[idx]);
548                 if (caseLabel instanceof Class<?> classLabel) {
549                     if (unconditionalExactnessCheck(selectorType, classLabel)) {
550                         //nothing - unconditionally use this case
551                     } else if (classLabel.isPrimitive()) {
552                         if (!selectorType.isPrimitive() && !Wrapper.isWrapperNumericOrBooleanType(selectorType)) {
553                             // Object o = ...
554                             // o instanceof Wrapped(float)
555                             cb.aload(SELECTOR_OBJ)
556                               .instanceOf(Wrapper.forBasicType(classLabel).wrapperClassDescriptor())
557                               .ifeq(next);
558                         } else if (!unconditionalExactnessCheck(Wrapper.asPrimitiveType(selectorType), classLabel)) {
559                             // Integer i = ... or int i = ...
560                             // o instanceof float
561                             Label notNumber = cb.newLabel(); // this label may end up unbound
562                             cb.aload(SELECTOR_OBJ)
563                               .instanceOf(CD_Number);
564                             if (selectorType == long.class || selectorType == float.class || selectorType == double.class ||
565                                 selectorType == Long.class || selectorType == Float.class || selectorType == Double.class) {
566                                 cb.ifeq(next);
567                             } else {
568                                 cb.ifeq(notNumber);
569                             }
570                             cb.aload(SELECTOR_OBJ)
571                               .checkcast(CD_Number);
572                             if (selectorType == long.class || selectorType == Long.class) {
573                                 cb.invokevirtual(CD_Number,
574                                         "longValue",
575                                         MethodTypeDesc.of(CD_long));
576                             } else if (selectorType == float.class || selectorType == Float.class) {
577                                 cb.invokevirtual(CD_Number,
578                                         "floatValue",
579                                         MethodTypeDesc.of(CD_float));
580                             } else if (selectorType == double.class || selectorType == Double.class) {
581                                 cb.invokevirtual(CD_Number,
582                                         "doubleValue",
583                                         MethodTypeDesc.of(CD_double));
584                             } else {
585                                 Label compare = cb.newLabel();
586                                 cb.invokevirtual(CD_Number,
587                                         "intValue",
588                                         MethodTypeDesc.of(CD_int))
589                                   .goto_(compare)
590                                   .labelBinding(notNumber);
591                                 stackMapFrames.add(StackMapFrameInfo.of(notNumber, locals, List.of()));
592                                 cb.aload(SELECTOR_OBJ)
593                                   .instanceOf(CD_Character)
594                                   .ifeq(next)
595                                   .aload(SELECTOR_OBJ)
596                                   .checkcast(CD_Character)
597                                   .invokevirtual(CD_Character,
598                                         "charValue",
599                                         MethodTypeDesc.of(CD_char))
600                                   .labelBinding(compare);
601                                 stackMapFrames.add(StackMapFrameInfo.of(compare, locals, List.of(StackMapFrameInfo.SimpleVerificationTypeInfo.INTEGER)));
602                             }
603 
604                             TypePairs typePair = TypePairs.of(Wrapper.asPrimitiveType(selectorType), classLabel);
605                             String methodName = TypePairs.typePairToName.get(typePair);
606                             cb.invokestatic(ConstantUtils.referenceClassDesc(ExactConversionsSupport.class),
607                                     methodName,
608                                     MethodTypeDesc.of(CD_boolean, classDesc(typePair.from)))
609                               .ifeq(next);
610                         }
611                     } else {
612                         Optional<ClassDesc> classLabelConstableOpt = classLabel.describeConstable();
613                         if (classLabelConstableOpt.isPresent()) {
614                             cb.aload(SELECTOR_OBJ)
615                               .instanceOf(classLabelConstableOpt.orElseThrow())
616                               .ifeq(next);
617                         } else {
618                             cb.aload(EXTRA_CLASS_LABELS)
619                               .loadConstant(extraClassLabels.size())
620                               .invokeinterface(CD_List,
621                                     "get",
622                                     MethodTypeDesc.of(CD_Object,
623                                             CD_int))
624                               .checkcast(CD_Class)
625                               .aload(SELECTOR_OBJ)
626                               .invokevirtual(CD_Class,
627                                     "isInstance",
628                                     MethodTypeDesc.of(CD_boolean,
629                                             CD_Object))
630                               .ifeq(next);
631                             extraClassLabels.add(classLabel);
632                         }
633                     }
634                 } else if (caseLabel instanceof EnumDesc<?> enumLabel) {
635                     int enumIdx = enumDescs.size();
636                     enumDescs.add(enumLabel);
637                     cb.aload(ENUM_CACHE)
638                       .loadConstant(enumIdx)
639                       .invokestatic(CD_Integer,
640                             "valueOf",
641                             MethodTypeDesc.of(CD_Integer,
642                                     CD_int))
643                       .aload(SELECTOR_OBJ)
644                       .invokeinterface(CD_BiPredicate,
645                             "test",
646                             MethodTypeDesc.of(CD_boolean,
647                                     CD_Object,
648                                     CD_Object))
649                       .ifeq(next);
650                 } else if (caseLabel instanceof String stringLabel) {
651                     cb.ldc(stringLabel)
652                       .aload(SELECTOR_OBJ)
653                       .invokevirtual(CD_Object,
654                             "equals",
655                             MethodTypeDesc.of(CD_boolean,
656                                     CD_Object))
657                       .ifeq(next);
658                 } else if (caseLabel instanceof Integer integerLabel) {
659                     Label compare = cb.newLabel();
660                     Label notNumber = cb.newLabel();
661                     cb.aload(SELECTOR_OBJ)
662                       .instanceOf(CD_Number)
663                       .ifeq(notNumber)
664                       .aload(SELECTOR_OBJ)
665                       .checkcast(CD_Number)
666                       .invokevirtual(CD_Number,
667                             "intValue",
668                             MethodTypeDesc.of(CD_int))
669                       .goto_(compare)
670                       .labelBinding(notNumber);
671                     stackMapFrames.add(StackMapFrameInfo.of(notNumber, locals, List.of()));
672                     cb.aload(SELECTOR_OBJ)
673                       .instanceOf(CD_Character)
674                       .ifeq(next)
675                       .aload(SELECTOR_OBJ)
676                       .checkcast(CD_Character)
677                       .invokevirtual(CD_Character,
678                             "charValue",
679                             MethodTypeDesc.of(CD_char))
680                       .labelBinding(compare);
681                     stackMapFrames.add(StackMapFrameInfo.of(compare, locals, List.of(StackMapFrameInfo.SimpleVerificationTypeInfo.INTEGER)));
682                     cb.loadConstant(integerLabel)
683                       .if_icmpne(next);
684                 } else if ((caseLabel instanceof Long ||
685                         caseLabel instanceof Float ||
686                         caseLabel instanceof Double ||
687                         caseLabel instanceof Boolean)) {
688                     if (caseLabel instanceof Boolean c) {
689                         cb.loadConstant(c ? 1 : 0);
690                     } else {
691                         cb.loadConstant((ConstantDesc) caseLabel);
692                     }
693                     var caseLabelWrapper = Wrapper.forWrapperType(caseLabel.getClass());
694                     cb.invokestatic(caseLabelWrapper.wrapperClassDescriptor(),
695                             "valueOf",
696                             MethodTypeDesc.of(caseLabelWrapper.wrapperClassDescriptor(),
697                                     caseLabelWrapper.basicClassDescriptor()))
698                       .aload(SELECTOR_OBJ)
699                       .invokevirtual(CD_Object,
700                             "equals",
701                             MethodTypeDesc.of(CD_boolean,
702                                     CD_Object))
703                       .ifeq(next);
704                 } else {
705                     throw new InternalError("Unsupported label type: " +
706                             caseLabel.getClass());
707                 }
708                 cb.loadConstant(idx)
709                   .ireturn();
710             }
711             stackMapFrames.add(StackMapFrameInfo.of(dflt, locals, List.of()));
712             cb.labelBinding(dflt)
713               .loadConstant(labelConstants.length)
714               .ireturn()
715               .with(StackMapTableAttribute.of(stackMapFrames));
716             DirectCodeBuilder.withMaxs(cb, 3, locals.size()); // enum labels use 3 stack, others use 2
717         };
718     }
719 
720     /*
721      * Construct the method handle that represents the method int typeSwitch(Object, int, BiPredicate, List)
722      */
723     private static MethodHandle generateTypeSwitch(MethodHandles.Lookup caller, Class<?> selectorType, Object[] labelConstants) {
724         boolean addExtraInfo = needsExtraInfo(selectorType, labelConstants);
725         List<EnumDesc<?>> enumDescs = addExtraInfo ? new ArrayList<>() : null;
726         List<Class<?>> extraClassLabels = addExtraInfo ? new ArrayList<>() : null;
727 
728         byte[] classBytes = ClassFile.of(ClassFile.StackMapsOption.DROP_STACK_MAPS).build(ConstantUtils.binaryNameToDesc(typeSwitchClassName(caller.lookupClass())),
729                 clb -> {
730                     clb.withFlags(AccessFlag.FINAL, AccessFlag.SUPER, AccessFlag.SYNTHETIC)
731                        .withMethodBody("typeSwitch",
732                                        addExtraInfo ? MTD_TYPE_SWITCH_EXTRA : MTD_TYPE_SWITCH,
733                                        ClassFile.ACC_FINAL | ClassFile.ACC_PUBLIC | ClassFile.ACC_STATIC,
734                                        generateTypeSwitchSkeleton(selectorType, labelConstants, enumDescs, extraClassLabels));
735         });
736 
737         try {
738             // this class is linked at the indy callsite; so define a hidden nestmate
739             MethodHandles.Lookup lookup;
740             lookup = caller.defineHiddenClass(classBytes, true, NESTMATE, STRONG);
741             MethodHandle typeSwitch = lookup.findStatic(lookup.lookupClass(),
742                                                         "typeSwitch",
743                                                         addExtraInfo ? MT_TYPE_SWITCH_EXTRA : MT_TYPE_SWITCH);
744             if (addExtraInfo) {
745                 typeSwitch = MethodHandles.insertArguments(typeSwitch, 2, new ResolvedEnumLabels(caller, enumDescs.toArray(new EnumDesc<?>[0])),
746                         List.copyOf(extraClassLabels));
747             }
748             return typeSwitch;
749         } catch (Throwable t) {
750             throw new IllegalArgumentException(t);
751         }
752     }
753 
754     //based on src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java:
755     private static String typeSwitchClassName(Class<?> targetClass) {
756         String name = targetClass.getName();
757         if (targetClass.isHidden()) {
758             // use the original class name
759             name = name.replace('/', '_');
760         }
761         return name + "$$TypeSwitch";
762     }
763 
764     // this method should be in sync with com.sun.tools.javac.code.Types.checkUnconditionallyExactPrimitives
765     private static boolean unconditionalExactnessCheck(Class<?> selectorType, Class<?> targetType) {
766         Wrapper selectorWrapper = Wrapper.forBasicType(selectorType);
767         Wrapper targetWrapper   = Wrapper.forBasicType(targetType);
768         if (selectorType.isPrimitive() && targetType.equals(selectorWrapper.wrapperType())) {
769             return true;
770         }
771         else if (selectorType.equals(targetType) ||
772                 ((selectorType.equals(byte.class) && !targetType.equals(char.class)) ||
773                  (selectorType.equals(short.class) && (selectorWrapper.isStrictSubRangeOf(targetWrapper))) ||
774                  (selectorType.equals(char.class)  && (selectorWrapper.isStrictSubRangeOf(targetWrapper)))  ||
775                  (selectorType.equals(int.class)   && (targetType.equals(double.class) || targetType.equals(long.class))) ||
776                  (selectorType.equals(float.class) && (selectorWrapper.isStrictSubRangeOf(targetWrapper))))) return true;
777         return false;
778     }
779 
780     // TypePairs should be in sync with the corresponding record in Lower
781     record TypePairs(Class<?> from, Class<?> to) {
782 
783         private static final Map<TypePairs, String> typePairToName = initialize();
784 
785         public static TypePairs of(Class<?> from,  Class<?> to) {
786             if (from == byte.class || from == short.class || from == char.class) {
787                 from = int.class;
788             }
789             return new TypePairs(from, to);
790         }
791 
792         public int hashCode() {
793             return 31 * from.hashCode() + to.hashCode();
794         }
795 
796         public boolean equals(Object other) {
797             if (other instanceof TypePairs otherPair) {
798                 return otherPair.from == from && otherPair.to == to;
799             }
800             return false;
801         }
802 
803         public static Map<TypePairs, String> initialize() {
804             Map<TypePairs, String> typePairToName = new HashMap<>();
805             typePairToName.put(new TypePairs(byte.class,   char.class),   "isIntToCharExact");      // redirected
806             typePairToName.put(new TypePairs(short.class,  byte.class),   "isIntToByteExact");      // redirected
807             typePairToName.put(new TypePairs(short.class,  char.class),   "isIntToCharExact");      // redirected
808             typePairToName.put(new TypePairs(char.class,   byte.class),   "isIntToByteExact");      // redirected
809             typePairToName.put(new TypePairs(char.class,   short.class),  "isIntToShortExact");     // redirected
810             typePairToName.put(new TypePairs(int.class,    byte.class),   "isIntToByteExact");
811             typePairToName.put(new TypePairs(int.class,    short.class),  "isIntToShortExact");
812             typePairToName.put(new TypePairs(int.class,    char.class),   "isIntToCharExact");
813             typePairToName.put(new TypePairs(int.class,    float.class),  "isIntToFloatExact");
814             typePairToName.put(new TypePairs(long.class,   byte.class),   "isLongToByteExact");
815             typePairToName.put(new TypePairs(long.class,   short.class),  "isLongToShortExact");
816             typePairToName.put(new TypePairs(long.class,   char.class),   "isLongToCharExact");
817             typePairToName.put(new TypePairs(long.class,   int.class),    "isLongToIntExact");
818             typePairToName.put(new TypePairs(long.class,   float.class),  "isLongToFloatExact");
819             typePairToName.put(new TypePairs(long.class,   double.class), "isLongToDoubleExact");
820             typePairToName.put(new TypePairs(float.class,  byte.class),   "isFloatToByteExact");
821             typePairToName.put(new TypePairs(float.class,  short.class),  "isFloatToShortExact");
822             typePairToName.put(new TypePairs(float.class,  char.class),   "isFloatToCharExact");
823             typePairToName.put(new TypePairs(float.class,  int.class),    "isFloatToIntExact");
824             typePairToName.put(new TypePairs(float.class,  long.class),   "isFloatToLongExact");
825             typePairToName.put(new TypePairs(double.class, byte.class),   "isDoubleToByteExact");
826             typePairToName.put(new TypePairs(double.class, short.class),  "isDoubleToShortExact");
827             typePairToName.put(new TypePairs(double.class, char.class),   "isDoubleToCharExact");
828             typePairToName.put(new TypePairs(double.class, int.class),    "isDoubleToIntExact");
829             typePairToName.put(new TypePairs(double.class, long.class),   "isDoubleToLongExact");
830             typePairToName.put(new TypePairs(double.class, float.class),  "isDoubleToFloatExact");
831             return typePairToName;
832         }
833     }
834 }