1 /* 2 * Copyright (c) 2017, 2023, 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.invoke.CallSite; 30 import java.lang.invoke.ConstantCallSite; 31 import java.lang.invoke.MethodHandle; 32 import java.lang.invoke.MethodHandles; 33 import java.lang.invoke.MethodType; 34 import java.util.List; 35 import java.util.Objects; 36 import java.util.stream.Stream; 37 import jdk.internal.access.SharedSecrets; 38 import jdk.internal.vm.annotation.Stable; 39 40 import static java.util.Objects.requireNonNull; 41 42 /** 43 * Bootstrap methods for linking {@code invokedynamic} call sites that implement 44 * the selection functionality of the {@code switch} statement. The bootstraps 45 * take additional static arguments corresponding to the {@code case} labels 46 * of the {@code switch}, implicitly numbered sequentially from {@code [0..N)}. 47 * 48 * @since 21 49 */ 50 public class SwitchBootstraps { 51 SwitchBootstraps()52 private SwitchBootstraps() {} 53 54 private static final Object SENTINEL = new Object(); 55 private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); 56 57 private static final MethodHandle INSTANCEOF_CHECK; 58 private static final MethodHandle INTEGER_EQ_CHECK; 59 private static final MethodHandle OBJECT_EQ_CHECK; 60 private static final MethodHandle ENUM_EQ_CHECK; 61 private static final MethodHandle NULL_CHECK; 62 private static final MethodHandle IS_ZERO; 63 private static final MethodHandle CHECK_INDEX; 64 private static final MethodHandle MAPPED_ENUM_LOOKUP; 65 66 static { 67 try { 68 INSTANCEOF_CHECK = MethodHandles.permuteArguments(LOOKUP.findVirtual(Class.class, "isInstance", 69 MethodType.methodType(boolean.class, Object.class)), 70 MethodType.methodType(boolean.class, Object.class, Class.class), 1, 0); 71 INTEGER_EQ_CHECK = LOOKUP.findStatic(SwitchBootstraps.class, "integerEqCheck", 72 MethodType.methodType(boolean.class, Object.class, Integer.class)); 73 OBJECT_EQ_CHECK = LOOKUP.findStatic(Objects.class, "equals", 74 MethodType.methodType(boolean.class, Object.class, Object.class)); 75 ENUM_EQ_CHECK = LOOKUP.findStatic(SwitchBootstraps.class, "enumEqCheck", 76 MethodType.methodType(boolean.class, Object.class, EnumDesc.class, MethodHandles.Lookup.class, ResolvedEnumLabel.class)); 77 NULL_CHECK = LOOKUP.findStatic(Objects.class, "isNull", 78 MethodType.methodType(boolean.class, Object.class)); 79 IS_ZERO = LOOKUP.findStatic(SwitchBootstraps.class, "isZero", 80 MethodType.methodType(boolean.class, int.class)); 81 CHECK_INDEX = LOOKUP.findStatic(Objects.class, "checkIndex", 82 MethodType.methodType(int.class, int.class, int.class)); 83 MAPPED_ENUM_LOOKUP = LOOKUP.findStatic(SwitchBootstraps.class, "mappedEnumLookup", 84 MethodType.methodType(int.class, Enum.class, MethodHandles.Lookup.class, 85 Class.class, EnumDesc[].class, EnumMap.class)); 86 } 87 catch (ReflectiveOperationException e) { 88 throw new ExceptionInInitializerError(e); 89 } 90 } 91 92 /** 93 * Bootstrap method for linking an {@code invokedynamic} call site that 94 * implements a {@code switch} on a target of a reference type. The static 95 * arguments are an array of case labels which must be non-null and of type 96 * {@code String} or {@code Integer} or {@code Class} or {@code EnumDesc}. 97 * <p> 98 * The type of the returned {@code CallSite}'s method handle will have 99 * a return type of {@code int}. It has two parameters: the first argument 100 * will be an {@code Object} instance ({@code target}) and the second 101 * will be {@code int} ({@code restart}). 102 * <p> 103 * If the {@code target} is {@code null}, then the method of the call site 104 * returns {@literal -1}. 105 * <p> 106 * If the {@code target} is not {@code null}, then the method of the call site 107 * returns the index of the first element in the {@code labels} array starting from 108 * the {@code restart} index matching one of the following conditions: 109 * <ul> 110 * <li>the element is of type {@code Class} that is assignable 111 * from the target's class; or</li> 112 * <li>the element is of type {@code String} or {@code Integer} and 113 * equals to the target.</li> 114 * <li>the element is of type {@code EnumDesc}, that describes a constant that is 115 * equals to the target.</li> 116 * </ul> 117 * <p> 118 * If no element in the {@code labels} array matches the target, then 119 * the method of the call site return the length of the {@code labels} array. 120 * <p> 121 * The value of the {@code restart} index must be between {@code 0} (inclusive) and 122 * the length of the {@code labels} array (inclusive), 123 * both or an {@link IndexOutOfBoundsException} is thrown. 124 * 125 * @param lookup Represents a lookup context with the accessibility 126 * privileges of the caller. When used with {@code invokedynamic}, 127 * this is stacked automatically by the VM. 128 * @param invocationName unused 129 * @param invocationType The invocation type of the {@code CallSite} with two parameters, 130 * a reference type, an {@code int}, and {@code int} as a return type. 131 * @param labels case labels - {@code String} and {@code Integer} constants 132 * and {@code Class} and {@code EnumDesc} instances, in any combination 133 * @return a {@code CallSite} returning the first matching element as described above 134 * 135 * @throws NullPointerException if any argument is {@code null} 136 * @throws IllegalArgumentException if any element in the labels array is null, if the 137 * invocation type is not not a method type of first parameter of a reference type, 138 * second parameter of type {@code int} and with {@code int} as its return type, 139 * or if {@code labels} contains an element that is not of type {@code String}, 140 * {@code Integer}, {@code Class} or {@code EnumDesc}. 141 * @jvms 4.4.6 The CONSTANT_NameAndType_info Structure 142 * @jvms 4.4.10 The CONSTANT_Dynamic_info and CONSTANT_InvokeDynamic_info Structures 143 */ typeSwitch(MethodHandles.Lookup lookup, String invocationName, MethodType invocationType, Object... labels)144 public static CallSite typeSwitch(MethodHandles.Lookup lookup, 145 String invocationName, 146 MethodType invocationType, 147 Object... labels) { 148 if (invocationType.parameterCount() != 2 149 || (!invocationType.returnType().equals(int.class)) 150 || invocationType.parameterType(0).isPrimitive() 151 || !invocationType.parameterType(1).equals(int.class)) 152 throw new IllegalArgumentException("Illegal invocation type " + invocationType); 153 requireNonNull(labels); 154 155 labels = labels.clone(); 156 Stream.of(labels).forEach(SwitchBootstraps::verifyLabel); 157 158 MethodHandle target = createMethodHandleSwitch(lookup, labels); 159 160 return new ConstantCallSite(target); 161 } 162 verifyLabel(Object label)163 private static void verifyLabel(Object label) { 164 if (label == null) { 165 throw new IllegalArgumentException("null label found"); 166 } 167 Class<?> labelClass = label.getClass(); 168 if (labelClass != Class.class && 169 labelClass != String.class && 170 labelClass != Integer.class && 171 labelClass != EnumDesc.class) { 172 throw new IllegalArgumentException("label with illegal type found: " + label.getClass()); 173 } 174 } 175 176 /* 177 * Construct test chains for labels inside switch, to handle switch repeats: 178 * switch (idx) { 179 * case 0 -> if (selector matches label[0]) return 0; else if (selector matches label[1]) return 1; else ... 180 * case 1 -> if (selector matches label[1]) return 1; else ... 181 * ... 182 * } 183 */ createRepeatIndexSwitch(MethodHandles.Lookup lookup, Object[] labels)184 private static MethodHandle createRepeatIndexSwitch(MethodHandles.Lookup lookup, Object[] labels) { 185 MethodHandle def = MethodHandles.dropArguments(MethodHandles.constant(int.class, labels.length), 0, Object.class); 186 MethodHandle[] testChains = new MethodHandle[labels.length]; 187 List<Object> labelsList = List.of(labels).reversed(); 188 189 for (int i = 0; i < labels.length; i++) { 190 MethodHandle test = def; 191 int idx = labels.length - 1; 192 List<Object> currentLabels = labelsList.subList(0, labels.length - i); 193 194 for (int j = 0; j < currentLabels.size(); j++, idx--) { 195 Object currentLabel = currentLabels.get(j); 196 if (j + 1 < currentLabels.size() && currentLabels.get(j + 1) == currentLabel) continue; 197 MethodHandle currentTest; 198 if (currentLabel instanceof Class<?>) { 199 currentTest = INSTANCEOF_CHECK; 200 } else if (currentLabel instanceof Integer) { 201 currentTest = INTEGER_EQ_CHECK; 202 } else if (currentLabel instanceof EnumDesc) { 203 currentTest = MethodHandles.insertArguments(ENUM_EQ_CHECK, 2, lookup, new ResolvedEnumLabel()); 204 } else { 205 currentTest = OBJECT_EQ_CHECK; 206 } 207 test = MethodHandles.guardWithTest(MethodHandles.insertArguments(currentTest, 1, currentLabel), 208 MethodHandles.dropArguments(MethodHandles.constant(int.class, idx), 0, Object.class), 209 test); 210 } 211 testChains[i] = MethodHandles.dropArguments(test, 0, int.class); 212 } 213 214 return MethodHandles.tableSwitch(MethodHandles.dropArguments(def, 0, int.class), testChains); 215 } 216 217 /* 218 * Construct code that maps the given selector and repeat index to a case label number: 219 * if (selector == null) return -1; 220 * else return "createRepeatIndexSwitch(labels)" 221 */ createMethodHandleSwitch(MethodHandles.Lookup lookup, Object[] labels)222 private static MethodHandle createMethodHandleSwitch(MethodHandles.Lookup lookup, Object[] labels) { 223 MethodHandle mainTest; 224 MethodHandle def = MethodHandles.dropArguments(MethodHandles.constant(int.class, labels.length), 0, Object.class); 225 if (labels.length > 0) { 226 mainTest = createRepeatIndexSwitch(lookup, labels); 227 } else { 228 mainTest = MethodHandles.dropArguments(def, 0, int.class); 229 } 230 MethodHandle body = 231 MethodHandles.guardWithTest(MethodHandles.dropArguments(NULL_CHECK, 0, int.class), 232 MethodHandles.dropArguments(MethodHandles.constant(int.class, -1), 0, int.class, Object.class), 233 mainTest); 234 MethodHandle switchImpl = 235 MethodHandles.permuteArguments(body, MethodType.methodType(int.class, Object.class, int.class), 1, 0); 236 return withIndexCheck(switchImpl, labels.length); 237 } 238 integerEqCheck(Object value, Integer constant)239 private static boolean integerEqCheck(Object value, Integer constant) { 240 if (value instanceof Number input && constant.intValue() == input.intValue()) { 241 return true; 242 } else if (value instanceof Character input && constant.intValue() == input.charValue()) { 243 return true; 244 } 245 246 return false; 247 } 248 isZero(int value)249 private static boolean isZero(int value) { 250 return value == 0; 251 } 252 253 /** 254 * Bootstrap method for linking an {@code invokedynamic} call site that 255 * implements a {@code switch} on a target of an enum type. The static 256 * arguments are used to encode the case labels associated to the switch 257 * construct, where each label can be encoded in two ways: 258 * <ul> 259 * <li>as a {@code String} value, which represents the name of 260 * the enum constant associated with the label</li> 261 * <li>as a {@code Class} value, which represents the enum type 262 * associated with a type test pattern</li> 263 * </ul> 264 * <p> 265 * The returned {@code CallSite}'s method handle will have 266 * a return type of {@code int} and accepts two parameters: the first argument 267 * will be an {@code Enum} instance ({@code target}) and the second 268 * will be {@code int} ({@code restart}). 269 * <p> 270 * If the {@code target} is {@code null}, then the method of the call site 271 * returns {@literal -1}. 272 * <p> 273 * If the {@code target} is not {@code null}, then the method of the call site 274 * returns the index of the first element in the {@code labels} array starting from 275 * the {@code restart} index matching one of the following conditions: 276 * <ul> 277 * <li>the element is of type {@code Class} that is assignable 278 * from the target's class; or</li> 279 * <li>the element is of type {@code String} and equals to the target 280 * enum constant's {@link Enum#name()}.</li> 281 * </ul> 282 * <p> 283 * If no element in the {@code labels} array matches the target, then 284 * the method of the call site return the length of the {@code labels} array. 285 * <p> 286 * The value of the {@code restart} index must be between {@code 0} (inclusive) and 287 * the length of the {@code labels} array (inclusive), 288 * both or an {@link IndexOutOfBoundsException} is thrown. 289 * 290 * @param lookup Represents a lookup context with the accessibility 291 * privileges of the caller. When used with {@code invokedynamic}, 292 * this is stacked automatically by the VM. 293 * @param invocationName unused 294 * @param invocationType The invocation type of the {@code CallSite} with two parameters, 295 * an enum type, an {@code int}, and {@code int} as a return type. 296 * @param labels case labels - {@code String} constants and {@code Class} instances, 297 * in any combination 298 * @return a {@code CallSite} returning the first matching element as described above 299 * 300 * @throws NullPointerException if any argument is {@code null} 301 * @throws IllegalArgumentException if any element in the labels array is null, if the 302 * invocation type is not a method type whose first parameter type is an enum type, 303 * second parameter of type {@code int} and whose return type is {@code int}, 304 * or if {@code labels} contains an element that is not of type {@code String} or 305 * {@code Class} of the target enum type. 306 * @jvms 4.4.6 The CONSTANT_NameAndType_info Structure 307 * @jvms 4.4.10 The CONSTANT_Dynamic_info and CONSTANT_InvokeDynamic_info Structures 308 */ enumSwitch(MethodHandles.Lookup lookup, String invocationName, MethodType invocationType, Object... labels)309 public static CallSite enumSwitch(MethodHandles.Lookup lookup, 310 String invocationName, 311 MethodType invocationType, 312 Object... labels) { 313 if (invocationType.parameterCount() != 2 314 || (!invocationType.returnType().equals(int.class)) 315 || invocationType.parameterType(0).isPrimitive() 316 || !invocationType.parameterType(0).isEnum() 317 || !invocationType.parameterType(1).equals(int.class)) 318 throw new IllegalArgumentException("Illegal invocation type " + invocationType); 319 requireNonNull(labels); 320 321 labels = labels.clone(); 322 323 Class<?> enumClass = invocationType.parameterType(0); 324 labels = Stream.of(labels).map(l -> convertEnumConstants(lookup, enumClass, l)).toArray(); 325 326 MethodHandle target; 327 boolean constantsOnly = Stream.of(labels).allMatch(l -> enumClass.isAssignableFrom(EnumDesc.class)); 328 329 if (labels.length > 0 && constantsOnly) { 330 //If all labels are enum constants, construct an optimized handle for repeat index 0: 331 //if (selector == null) return -1 332 //else if (idx == 0) return mappingArray[selector.ordinal()]; //mapping array created lazily 333 //else return "createRepeatIndexSwitch(labels)" 334 MethodHandle body = 335 MethodHandles.guardWithTest(MethodHandles.dropArguments(NULL_CHECK, 0, int.class), 336 MethodHandles.dropArguments(MethodHandles.constant(int.class, -1), 0, int.class, Object.class), 337 MethodHandles.guardWithTest(MethodHandles.dropArguments(IS_ZERO, 1, Object.class), 338 createRepeatIndexSwitch(lookup, labels), 339 MethodHandles.insertArguments(MAPPED_ENUM_LOOKUP, 1, lookup, enumClass, labels, new EnumMap()))); 340 target = MethodHandles.permuteArguments(body, MethodType.methodType(int.class, Object.class, int.class), 1, 0); 341 } else { 342 target = createMethodHandleSwitch(lookup, labels); 343 } 344 345 target = target.asType(invocationType); 346 target = withIndexCheck(target, labels.length); 347 348 return new ConstantCallSite(target); 349 } 350 convertEnumConstants(MethodHandles.Lookup lookup, Class<?> enumClassTemplate, Object label)351 private static <E extends Enum<E>> Object convertEnumConstants(MethodHandles.Lookup lookup, Class<?> enumClassTemplate, Object label) { 352 if (label == null) { 353 throw new IllegalArgumentException("null label found"); 354 } 355 Class<?> labelClass = label.getClass(); 356 if (labelClass == Class.class) { 357 if (label != enumClassTemplate) { 358 throw new IllegalArgumentException("the Class label: " + label + 359 ", expected the provided enum class: " + enumClassTemplate); 360 } 361 return label; 362 } else if (labelClass == String.class) { 363 return EnumDesc.of(enumClassTemplate.describeConstable().get(), (String) label); 364 } else { 365 throw new IllegalArgumentException("label with illegal type found: " + labelClass + 366 ", expected label of type either String or Class"); 367 } 368 } 369 mappedEnumLookup(T value, MethodHandles.Lookup lookup, Class<T> enumClass, EnumDesc<?>[] labels, EnumMap enumMap)370 private static <T extends Enum<T>> int mappedEnumLookup(T value, MethodHandles.Lookup lookup, Class<T> enumClass, EnumDesc<?>[] labels, EnumMap enumMap) { 371 if (enumMap.map == null) { 372 T[] constants = SharedSecrets.getJavaLangAccess().getEnumConstantsShared(enumClass); 373 int[] map = new int[constants.length]; 374 int ordinal = 0; 375 376 for (T constant : constants) { 377 map[ordinal] = labels.length; 378 379 for (int i = 0; i < labels.length; i++) { 380 if (Objects.equals(labels[i].constantName(), constant.name())) { 381 map[ordinal] = i; 382 break; 383 } 384 } 385 386 ordinal++; 387 } 388 } 389 return enumMap.map[value.ordinal()]; 390 } 391 enumEqCheck(Object value, EnumDesc<?> label, MethodHandles.Lookup lookup, ResolvedEnumLabel resolvedEnum)392 private static boolean enumEqCheck(Object value, EnumDesc<?> label, MethodHandles.Lookup lookup, ResolvedEnumLabel resolvedEnum) { 393 if (resolvedEnum.resolvedEnum == null) { 394 Object resolved; 395 396 try { 397 if (!(value instanceof Enum<?> enumValue)) { 398 return false; 399 } 400 401 Class<?> clazz = label.constantType().resolveConstantDesc(lookup); 402 403 if (enumValue.getDeclaringClass() != clazz) { 404 return false; 405 } 406 407 resolved = label.resolveConstantDesc(lookup); 408 } catch (IllegalArgumentException | ReflectiveOperationException ex) { 409 resolved = SENTINEL; 410 } 411 412 resolvedEnum.resolvedEnum = resolved; 413 } 414 415 return value == resolvedEnum.resolvedEnum; 416 } 417 withIndexCheck(MethodHandle target, int labelsCount)418 private static MethodHandle withIndexCheck(MethodHandle target, int labelsCount) { 419 MethodHandle checkIndex = MethodHandles.insertArguments(CHECK_INDEX, 1, labelsCount + 1); 420 421 return MethodHandles.filterArguments(target, 1, checkIndex); 422 } 423 424 private static final class ResolvedEnumLabel { 425 @Stable 426 public Object resolvedEnum; 427 } 428 429 private static final class EnumMap { 430 @Stable 431 public int[] map; 432 } 433 } 434