|
56 | 56 |
|
57 | 57 | import static java.lang.invoke.MethodHandles.Lookup.ClassOption.NESTMATE;
|
58 | 58 | import static java.lang.invoke.MethodHandles.Lookup.ClassOption.STRONG;
|
| 59 | +import java.util.Arrays; |
59 | 60 | import java.util.HashMap;
|
60 | 61 | import java.util.Map;
|
61 | 62 | import static java.util.Objects.requireNonNull;
|
@@ -95,19 +96,13 @@ private SwitchBootstraps() {}
|
95 | 96 | private static final ClassDesc CD_Objects = ReferenceClassDescImpl.ofValidated("Ljava/util/Objects;");
|
96 | 97 |
|
97 | 98 | private static class StaticHolders {
|
98 |
| - private static final MethodHandle NULL_CHECK; |
99 |
| - private static final MethodHandle IS_ZERO; |
100 |
| - private static final MethodHandle MAPPED_ENUM_LOOKUP; |
| 99 | + private static final MethodHandle MAPPED_ENUM_SWITCH; |
101 | 100 |
|
102 | 101 | static {
|
103 | 102 | try {
|
104 |
| - NULL_CHECK = LOOKUP.findStatic(Objects.class, "isNull", |
105 |
| - MethodType.methodType(boolean.class, Object.class)); |
106 |
| - IS_ZERO = LOOKUP.findStatic(SwitchBootstraps.class, "isZero", |
107 |
| - MethodType.methodType(boolean.class, int.class)); |
108 |
| - MAPPED_ENUM_LOOKUP = LOOKUP.findStatic(SwitchBootstraps.class, "mappedEnumLookup", |
109 |
| - MethodType.methodType(int.class, Enum.class, MethodHandles.Lookup.class, |
110 |
| - Class.class, EnumDesc[].class, EnumMap.class)); |
| 103 | + MAPPED_ENUM_SWITCH = LOOKUP.findStatic(SwitchBootstraps.class, "mappedEnumSwitch", |
| 104 | + MethodType.methodType(int.class, Enum.class, int.class, MethodHandles.Lookup.class, |
| 105 | + Class.class, EnumDesc[].class, MappedEnumCache.class)); |
111 | 106 | }
|
112 | 107 | catch (ReflectiveOperationException e) {
|
113 | 108 | throw new ExceptionInInitializerError(e);
|
@@ -211,10 +206,6 @@ private static void verifyLabel(Object label, Class<?> selectorType) {
|
211 | 206 | }
|
212 | 207 | }
|
213 | 208 |
|
214 |
| - private static boolean isZero(int value) { |
215 |
| - return value == 0; |
216 |
| - } |
217 |
| - |
218 | 209 | /**
|
219 | 210 | * Bootstrap method for linking an {@code invokedynamic} call site that
|
220 | 211 | * implements a {@code switch} on a target of an enum type. The static
|
@@ -286,23 +277,27 @@ public static CallSite enumSwitch(MethodHandles.Lookup lookup,
|
286 | 277 | labels = labels.clone();
|
287 | 278 |
|
288 | 279 | Class<?> enumClass = invocationType.parameterType(0);
|
289 |
| - labels = Stream.of(labels).map(l -> convertEnumConstants(lookup, enumClass, l)).toArray(); |
| 280 | + boolean constantsOnly = true; |
| 281 | + int len = labels.length; |
| 282 | + |
| 283 | + for (int i = 0; i < len; i++) { |
| 284 | + Object convertedLabel = |
| 285 | + convertEnumConstants(lookup, enumClass, labels[i]); |
| 286 | + labels[i] = convertedLabel; |
| 287 | + if (constantsOnly) |
| 288 | + constantsOnly = convertedLabel instanceof EnumDesc; |
| 289 | + } |
290 | 290 |
|
291 | 291 | MethodHandle target;
|
292 |
| - boolean constantsOnly = Stream.of(labels).allMatch(l -> enumClass.isAssignableFrom(EnumDesc.class)); |
293 | 292 |
|
294 | 293 | if (labels.length > 0 && constantsOnly) {
|
295 | 294 | //If all labels are enum constants, construct an optimized handle for repeat index 0:
|
296 | 295 | //if (selector == null) return -1
|
297 | 296 | //else if (idx == 0) return mappingArray[selector.ordinal()]; //mapping array created lazily
|
298 | 297 | //else return "typeSwitch(labels)"
|
299 |
| - MethodHandle body = |
300 |
| - MethodHandles.guardWithTest(MethodHandles.dropArguments(StaticHolders.NULL_CHECK, 0, int.class), |
301 |
| - MethodHandles.dropArguments(MethodHandles.constant(int.class, -1), 0, int.class, Object.class), |
302 |
| - MethodHandles.guardWithTest(MethodHandles.dropArguments(StaticHolders.IS_ZERO, 1, Object.class), |
303 |
| - generateTypeSwitch(lookup, invocationType.parameterType(0), labels), |
304 |
| - MethodHandles.insertArguments(StaticHolders.MAPPED_ENUM_LOOKUP, 1, lookup, enumClass, labels, new EnumMap()))); |
305 |
| - target = MethodHandles.permuteArguments(body, MethodType.methodType(int.class, Object.class, int.class), 1, 0); |
| 298 | + EnumDesc<?>[] enumDescLabels = |
| 299 | + Arrays.copyOf(labels, labels.length, EnumDesc[].class); |
| 300 | + target = MethodHandles.insertArguments(StaticHolders.MAPPED_ENUM_SWITCH, 2, lookup, enumClass, enumDescLabels, new MappedEnumCache()); |
306 | 301 | } else {
|
307 | 302 | target = generateTypeSwitch(lookup, invocationType.parameterType(0), labels);
|
308 | 303 | }
|
@@ -331,26 +326,63 @@ private static <E extends Enum<E>> Object convertEnumConstants(MethodHandles.Loo
|
331 | 326 | }
|
332 | 327 | }
|
333 | 328 |
|
334 |
| - private static <T extends Enum<T>> int mappedEnumLookup(T value, MethodHandles.Lookup lookup, Class<T> enumClass, EnumDesc<?>[] labels, EnumMap enumMap) { |
335 |
| - if (enumMap.map == null) { |
336 |
| - T[] constants = SharedSecrets.getJavaLangAccess().getEnumConstantsShared(enumClass); |
337 |
| - int[] map = new int[constants.length]; |
338 |
| - int ordinal = 0; |
339 |
| - |
340 |
| - for (T constant : constants) { |
341 |
| - map[ordinal] = labels.length; |
| 329 | + private static <T extends Enum<T>> int mappedEnumSwitch(T value, int restartIndex, MethodHandles.Lookup lookup, Class<T> enumClass, EnumDesc<?>[] labels, MappedEnumCache enumCache) throws Throwable { |
| 330 | + if (value == null) { |
| 331 | + return -1; |
| 332 | + } |
342 | 333 |
|
343 |
| - for (int i = 0; i < labels.length; i++) { |
344 |
| - if (Objects.equals(labels[i].constantName(), constant.name())) { |
345 |
| - map[ordinal] = i; |
346 |
| - break; |
| 334 | + if (restartIndex != 0) { |
| 335 | + MethodHandle generatedSwitch = enumCache.generatedSwitch; |
| 336 | + if (generatedSwitch == null) { |
| 337 | + synchronized (enumCache) { |
| 338 | + generatedSwitch = enumCache.generatedSwitch; |
| 339 | + |
| 340 | + if (generatedSwitch == null) { |
| 341 | + generatedSwitch = |
| 342 | + generateTypeSwitch(lookup, enumClass, labels) |
| 343 | + .asType(MethodType.methodType(int.class, |
| 344 | + Enum.class, |
| 345 | + int.class)); |
| 346 | + enumCache.generatedSwitch = generatedSwitch; |
347 | 347 | }
|
348 | 348 | }
|
| 349 | + } |
| 350 | + |
| 351 | + return (int) generatedSwitch.invokeExact(value, restartIndex); |
| 352 | + } |
| 353 | + |
| 354 | + int[] constantsMap = enumCache.constantsMap; |
| 355 | + |
| 356 | + if (constantsMap == null) { |
| 357 | + synchronized (enumCache) { |
| 358 | + constantsMap = enumCache.constantsMap; |
349 | 359 |
|
350 |
| - ordinal++; |
| 360 | + if (constantsMap == null) { |
| 361 | + T[] constants = SharedSecrets.getJavaLangAccess() |
| 362 | + .getEnumConstantsShared(enumClass); |
| 363 | + constantsMap = new int[constants.length]; |
| 364 | + int ordinal = 0; |
| 365 | + |
| 366 | + for (T constant : constants) { |
| 367 | + constantsMap[ordinal] = labels.length; |
| 368 | + |
| 369 | + for (int i = 0; i < labels.length; i++) { |
| 370 | + if (Objects.equals(labels[i].constantName(), |
| 371 | + constant.name())) { |
| 372 | + constantsMap[ordinal] = i; |
| 373 | + break; |
| 374 | + } |
| 375 | + } |
| 376 | + |
| 377 | + ordinal++; |
| 378 | + } |
| 379 | + |
| 380 | + enumCache.constantsMap = constantsMap; |
| 381 | + } |
351 | 382 | }
|
352 | 383 | }
|
353 |
| - return enumMap.map[value.ordinal()]; |
| 384 | + |
| 385 | + return constantsMap[value.ordinal()]; |
354 | 386 | }
|
355 | 387 |
|
356 | 388 | private static final class ResolvedEnumLabels implements BiPredicate<Integer, Object> {
|
@@ -395,9 +427,11 @@ public boolean test(Integer labelIndex, Object value) {
|
395 | 427 | }
|
396 | 428 | }
|
397 | 429 |
|
398 |
| - private static final class EnumMap { |
| 430 | + private static final class MappedEnumCache { |
| 431 | + @Stable |
| 432 | + public int[] constantsMap; |
399 | 433 | @Stable
|
400 |
| - public int[] map; |
| 434 | + public MethodHandle generatedSwitch; |
401 | 435 | }
|
402 | 436 |
|
403 | 437 | /*
|
|
0 commit comments