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