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