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 }