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 (isNotValidPair(selectorType, caseLabel)){ 550 cb.goto_(next); 551 continue; 552 } 553 else if (unconditionalExactnessCheck(selectorType, classLabel)) { 554 //nothing - unconditionally use this case 555 } else if (classLabel.isPrimitive()) { 556 if (!selectorType.isPrimitive() && !Wrapper.isWrapperNumericOrBooleanType(selectorType)) { 557 // Object o = ... 558 // o instanceof Wrapped(float) 559 cb.aload(SELECTOR_OBJ) 560 .instanceOf(Wrapper.forBasicType(classLabel).wrapperClassDescriptor()) 561 .ifeq(next); 562 } else if (!unconditionalExactnessCheck(Wrapper.asPrimitiveType(selectorType), classLabel)) { 563 // Integer i = ... or int i = ... 564 // o instanceof float 565 Label notNumber = cb.newLabel(); // this label may end up unbound 566 cb.aload(SELECTOR_OBJ) 567 .instanceOf(CD_Number); 568 if (selectorType == long.class || selectorType == float.class || selectorType == double.class || 569 selectorType == Long.class || selectorType == Float.class || selectorType == Double.class) { 570 cb.ifeq(next); 571 } else { 572 cb.ifeq(notNumber); 573 } 574 cb.aload(SELECTOR_OBJ) 575 .checkcast(CD_Number); 576 if (selectorType == long.class || selectorType == Long.class) { 577 cb.invokevirtual(CD_Number, 578 "longValue", 579 MethodTypeDesc.of(CD_long)); 580 } else if (selectorType == float.class || selectorType == Float.class) { 581 cb.invokevirtual(CD_Number, 582 "floatValue", 583 MethodTypeDesc.of(CD_float)); 584 } else if (selectorType == double.class || selectorType == Double.class) { 585 cb.invokevirtual(CD_Number, 586 "doubleValue", 587 MethodTypeDesc.of(CD_double)); 588 } else { 589 Label compare = cb.newLabel(); 590 cb.invokevirtual(CD_Number, 591 "intValue", 592 MethodTypeDesc.of(CD_int)) 593 .goto_(compare) 594 .labelBinding(notNumber); 595 stackMapFrames.add(StackMapFrameInfo.of(notNumber, locals, List.of())); 596 cb.aload(SELECTOR_OBJ) 597 .instanceOf(CD_Character) 598 .ifeq(next) 599 .aload(SELECTOR_OBJ) 600 .checkcast(CD_Character) 601 .invokevirtual(CD_Character, 602 "charValue", 603 MethodTypeDesc.of(CD_char)) 604 .labelBinding(compare); 605 stackMapFrames.add(StackMapFrameInfo.of(compare, locals, List.of(StackMapFrameInfo.SimpleVerificationTypeInfo.INTEGER))); 606 } 607 608 TypePairs typePair = TypePairs.of(Wrapper.asPrimitiveType(selectorType), classLabel); 609 String methodName = TypePairs.typePairToName.get(typePair); 610 cb.invokestatic(ConstantUtils.referenceClassDesc(ExactConversionsSupport.class), 611 methodName, 612 MethodTypeDesc.of(CD_boolean, classDesc(typePair.from))) 613 .ifeq(next); 614 } 615 } else { 616 Optional<ClassDesc> classLabelConstableOpt = classLabel.describeConstable(); 617 if (classLabelConstableOpt.isPresent()) { 618 cb.aload(SELECTOR_OBJ) 619 .instanceOf(classLabelConstableOpt.orElseThrow()) 620 .ifeq(next); 621 } else { 622 cb.aload(EXTRA_CLASS_LABELS) 623 .loadConstant(extraClassLabels.size()) 624 .invokeinterface(CD_List, 625 "get", 626 MethodTypeDesc.of(CD_Object, 627 CD_int)) 628 .checkcast(CD_Class) 629 .aload(SELECTOR_OBJ) 630 .invokevirtual(CD_Class, 631 "isInstance", 632 MethodTypeDesc.of(CD_boolean, 633 CD_Object)) 634 .ifeq(next); 635 extraClassLabels.add(classLabel); 636 } 637 } 638 } else if (caseLabel instanceof EnumDesc<?> enumLabel) { 639 int enumIdx = enumDescs.size(); 640 enumDescs.add(enumLabel); 641 cb.aload(ENUM_CACHE) 642 .loadConstant(enumIdx) 643 .invokestatic(CD_Integer, 644 "valueOf", 645 MethodTypeDesc.of(CD_Integer, 646 CD_int)) 647 .aload(SELECTOR_OBJ) 648 .invokeinterface(CD_BiPredicate, 649 "test", 650 MethodTypeDesc.of(CD_boolean, 651 CD_Object, 652 CD_Object)) 653 .ifeq(next); 654 } else if (caseLabel instanceof String stringLabel) { 655 cb.ldc(stringLabel) 656 .aload(SELECTOR_OBJ) 657 .invokevirtual(CD_Object, 658 "equals", 659 MethodTypeDesc.of(CD_boolean, 660 CD_Object)) 661 .ifeq(next); 662 } else if (caseLabel instanceof Integer integerLabel) { 663 Label compare = cb.newLabel(); 664 Label notNumber = cb.newLabel(); 665 cb.aload(SELECTOR_OBJ) 666 .instanceOf(CD_Number) 667 .ifeq(notNumber) 668 .aload(SELECTOR_OBJ) 669 .checkcast(CD_Number) 670 .invokevirtual(CD_Number, 671 "intValue", 672 MethodTypeDesc.of(CD_int)) 673 .goto_(compare) 674 .labelBinding(notNumber); 675 stackMapFrames.add(StackMapFrameInfo.of(notNumber, locals, List.of())); 676 cb.aload(SELECTOR_OBJ) 677 .instanceOf(CD_Character) 678 .ifeq(next) 679 .aload(SELECTOR_OBJ) 680 .checkcast(CD_Character) 681 .invokevirtual(CD_Character, 682 "charValue", 683 MethodTypeDesc.of(CD_char)) 684 .labelBinding(compare); 685 stackMapFrames.add(StackMapFrameInfo.of(compare, locals, List.of(StackMapFrameInfo.SimpleVerificationTypeInfo.INTEGER))); 686 cb.loadConstant(integerLabel) 687 .if_icmpne(next); 688 } else if ((caseLabel instanceof Long || 689 caseLabel instanceof Float || 690 caseLabel instanceof Double || 691 caseLabel instanceof Boolean)) { 692 if (caseLabel instanceof Boolean c) { 693 cb.loadConstant(c ? 1 : 0); 694 } else { 695 cb.loadConstant((ConstantDesc) caseLabel); 696 } 697 var caseLabelWrapper = Wrapper.forWrapperType(caseLabel.getClass()); 698 cb.invokestatic(caseLabelWrapper.wrapperClassDescriptor(), 699 "valueOf", 700 MethodTypeDesc.of(caseLabelWrapper.wrapperClassDescriptor(), 701 caseLabelWrapper.basicClassDescriptor())) 702 .aload(SELECTOR_OBJ) 703 .invokevirtual(CD_Object, 704 "equals", 705 MethodTypeDesc.of(CD_boolean, 706 CD_Object)) 707 .ifeq(next); 708 } else { 709 throw new InternalError("Unsupported label type: " + 710 caseLabel.getClass()); 711 } 712 cb.loadConstant(idx) 713 .ireturn(); 714 } 715 stackMapFrames.add(StackMapFrameInfo.of(dflt, locals, List.of())); 716 cb.labelBinding(dflt) 717 .loadConstant(labelConstants.length) 718 .ireturn() 719 .with(StackMapTableAttribute.of(stackMapFrames)); 720 DirectCodeBuilder.withMaxs(cb, 3, locals.size()); // enum labels use 3 stack, others use 2 721 }; 722 } 723 724 private static boolean isNotValidPair(Class<?> selectorType, Object caseLabel) { 725 return (selectorType == boolean.class && caseLabel != boolean.class && caseLabel != Boolean.class) || 726 (selectorType != boolean.class && selectorType.isPrimitive() && (caseLabel == boolean.class || caseLabel == Boolean.class)); 727 } 728 729 /* 730 * Construct the method handle that represents the method int typeSwitch(Object, int, BiPredicate, List) 731 */ 732 private static MethodHandle generateTypeSwitch(MethodHandles.Lookup caller, Class<?> selectorType, Object[] labelConstants) { 733 boolean addExtraInfo = needsExtraInfo(selectorType, labelConstants); 734 List<EnumDesc<?>> enumDescs = addExtraInfo ? new ArrayList<>() : null; 735 List<Class<?>> extraClassLabels = addExtraInfo ? new ArrayList<>() : null; 736 737 byte[] classBytes = ClassFile.of(ClassFile.StackMapsOption.DROP_STACK_MAPS).build(ConstantUtils.binaryNameToDesc(typeSwitchClassName(caller.lookupClass())), 738 clb -> { 739 clb.withFlags(AccessFlag.FINAL, AccessFlag.SUPER, AccessFlag.SYNTHETIC) 740 .withMethodBody("typeSwitch", 741 addExtraInfo ? MTD_TYPE_SWITCH_EXTRA : MTD_TYPE_SWITCH, 742 ClassFile.ACC_FINAL | ClassFile.ACC_PUBLIC | ClassFile.ACC_STATIC, 743 generateTypeSwitchSkeleton(selectorType, labelConstants, enumDescs, extraClassLabels)); 744 }); 745 746 try { 747 // this class is linked at the indy callsite; so define a hidden nestmate 748 MethodHandles.Lookup lookup; 749 lookup = caller.defineHiddenClass(classBytes, true, NESTMATE, STRONG); 750 MethodHandle typeSwitch = lookup.findStatic(lookup.lookupClass(), 751 "typeSwitch", 752 addExtraInfo ? MT_TYPE_SWITCH_EXTRA : MT_TYPE_SWITCH); 753 if (addExtraInfo) { 754 typeSwitch = MethodHandles.insertArguments(typeSwitch, 2, new ResolvedEnumLabels(caller, enumDescs.toArray(new EnumDesc<?>[0])), 755 List.copyOf(extraClassLabels)); 756 } 757 return typeSwitch; 758 } catch (Throwable t) { 759 throw new IllegalArgumentException(t); 760 } 761 } 762 763 //based on src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java: 764 private static String typeSwitchClassName(Class<?> targetClass) { 765 String name = targetClass.getName(); 766 if (targetClass.isHidden()) { 767 // use the original class name 768 name = name.replace('/', '_'); 769 } 770 return name + "$$TypeSwitch"; 771 } 772 773 // this method should be in sync with com.sun.tools.javac.code.Types.checkUnconditionallyExactPrimitives 774 private static boolean unconditionalExactnessCheck(Class<?> selectorType, Class<?> targetType) { 775 Wrapper selectorWrapper = Wrapper.forBasicType(selectorType); 776 Wrapper targetWrapper = Wrapper.forBasicType(targetType); 777 if (selectorType.isPrimitive() && targetType.equals(selectorWrapper.wrapperType())) { 778 return true; 779 } 780 else if (selectorType.equals(targetType) || 781 (targetType.isPrimitive() && selectorType.isPrimitive() && 782 (selectorWrapper.isStrictSubRangeOf(targetWrapper) && 783 !((selectorType.equals(byte.class) && targetType.equals(char.class)) || 784 (selectorType.equals(int.class) && targetType.equals(float.class)) || 785 (selectorType.equals(long.class) && (targetType.equals(double.class) || targetType.equals(float.class))))))) return true; 786 return false; 787 } 788 789 // TypePairs should be in sync with the corresponding record in Lower 790 record TypePairs(Class<?> from, Class<?> to) { 791 792 private static final Map<TypePairs, String> typePairToName = initialize(); 793 794 public static TypePairs of(Class<?> from, Class<?> to) { 795 if (from == byte.class || from == short.class || from == char.class) { 796 from = int.class; 797 } 798 return new TypePairs(from, to); 799 } 800 801 public int hashCode() { 802 return 31 * from.hashCode() + to.hashCode(); 803 } 804 805 public boolean equals(Object other) { 806 if (other instanceof TypePairs otherPair) { 807 return otherPair.from == from && otherPair.to == to; 808 } 809 return false; 810 } 811 812 public static Map<TypePairs, String> initialize() { 813 Map<TypePairs, String> typePairToName = new HashMap<>(); 814 typePairToName.put(new TypePairs(byte.class, char.class), "isIntToCharExact"); // redirected 815 typePairToName.put(new TypePairs(short.class, byte.class), "isIntToByteExact"); // redirected 816 typePairToName.put(new TypePairs(short.class, char.class), "isIntToCharExact"); // redirected 817 typePairToName.put(new TypePairs(char.class, byte.class), "isIntToByteExact"); // redirected 818 typePairToName.put(new TypePairs(char.class, short.class), "isIntToShortExact"); // redirected 819 typePairToName.put(new TypePairs(int.class, byte.class), "isIntToByteExact"); 820 typePairToName.put(new TypePairs(int.class, short.class), "isIntToShortExact"); 821 typePairToName.put(new TypePairs(int.class, char.class), "isIntToCharExact"); 822 typePairToName.put(new TypePairs(int.class, float.class), "isIntToFloatExact"); 823 typePairToName.put(new TypePairs(long.class, byte.class), "isLongToByteExact"); 824 typePairToName.put(new TypePairs(long.class, short.class), "isLongToShortExact"); 825 typePairToName.put(new TypePairs(long.class, char.class), "isLongToCharExact"); 826 typePairToName.put(new TypePairs(long.class, int.class), "isLongToIntExact"); 827 typePairToName.put(new TypePairs(long.class, float.class), "isLongToFloatExact"); 828 typePairToName.put(new TypePairs(long.class, double.class), "isLongToDoubleExact"); 829 typePairToName.put(new TypePairs(float.class, byte.class), "isFloatToByteExact"); 830 typePairToName.put(new TypePairs(float.class, short.class), "isFloatToShortExact"); 831 typePairToName.put(new TypePairs(float.class, char.class), "isFloatToCharExact"); 832 typePairToName.put(new TypePairs(float.class, int.class), "isFloatToIntExact"); 833 typePairToName.put(new TypePairs(float.class, long.class), "isFloatToLongExact"); 834 typePairToName.put(new TypePairs(double.class, byte.class), "isDoubleToByteExact"); 835 typePairToName.put(new TypePairs(double.class, short.class), "isDoubleToShortExact"); 836 typePairToName.put(new TypePairs(double.class, char.class), "isDoubleToCharExact"); 837 typePairToName.put(new TypePairs(double.class, int.class), "isDoubleToIntExact"); 838 typePairToName.put(new TypePairs(double.class, long.class), "isDoubleToLongExact"); 839 typePairToName.put(new TypePairs(double.class, float.class), "isDoubleToFloatExact"); 840 return typePairToName; 841 } 842 } 843 }