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