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 }