• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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