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