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