Skip to content

Commit 2581935

Browse files
committed
8332528: Generate code in SwitchBootstraps.generateTypeSwitch that require fewer adaptations
Reviewed-by: liach, jlahoda
1 parent b890336 commit 2581935

File tree

4 files changed

+151
-40
lines changed

4 files changed

+151
-40
lines changed

make/jdk/src/classes/build/tools/classlist/HelloClasslist.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,17 @@ public static void main(String ... args) throws Throwable {
151151

152152
LOGGER.log(Level.FINE, "New Date: " + newDate + " - old: " + oldDate);
153153

154+
// Pull SwitchBootstraps and associated classes into the classlist
155+
record A(int a) { }
156+
record B(int b) { }
157+
Object o = new A(4711);
158+
int value = switch (o) {
159+
case A a -> a.a;
160+
case B b -> b.b;
161+
default -> 17;
162+
};
163+
LOGGER.log(Level.FINE, "Value: " + value);
164+
154165
// The Striped64$Cell is loaded rarely only when there's a contention among
155166
// multiple threads performing LongAdder.increment(). This results in
156167
// an inconsistency in the classlist between builds (see JDK-8295951).

src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java

Lines changed: 46 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
package java.lang.runtime;
2727

2828
import java.lang.Enum.EnumDesc;
29+
import java.lang.classfile.ClassBuilder;
2930
import java.lang.classfile.CodeBuilder;
3031
import java.lang.constant.ClassDesc;
3132
import java.lang.constant.ConstantDesc;
@@ -48,6 +49,8 @@
4849
import java.lang.classfile.ClassFile;
4950
import java.lang.classfile.Label;
5051
import java.lang.classfile.instruction.SwitchCase;
52+
53+
import jdk.internal.constant.ReferenceClassDescImpl;
5154
import jdk.internal.misc.PreviewFeatures;
5255
import jdk.internal.vm.annotation.Stable;
5356

@@ -74,28 +77,38 @@ private SwitchBootstraps() {}
7477
private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
7578
private static final boolean previewEnabled = PreviewFeatures.isEnabled();
7679

77-
private static final MethodHandle NULL_CHECK;
78-
private static final MethodHandle IS_ZERO;
79-
private static final MethodHandle CHECK_INDEX;
80-
private static final MethodHandle MAPPED_ENUM_LOOKUP;
80+
81+
private static final MethodType TYPES_SWITCH_TYPE = MethodType.methodType(int.class,
82+
Object.class,
83+
int.class,
84+
BiPredicate.class,
85+
List.class);
8186

8287
private static final MethodTypeDesc TYPES_SWITCH_DESCRIPTOR =
8388
MethodTypeDesc.ofDescriptor("(Ljava/lang/Object;ILjava/util/function/BiPredicate;Ljava/util/List;)I");
84-
85-
static {
86-
try {
87-
NULL_CHECK = LOOKUP.findStatic(Objects.class, "isNull",
88-
MethodType.methodType(boolean.class, Object.class));
89-
IS_ZERO = LOOKUP.findStatic(SwitchBootstraps.class, "isZero",
90-
MethodType.methodType(boolean.class, int.class));
91-
CHECK_INDEX = LOOKUP.findStatic(Objects.class, "checkIndex",
92-
MethodType.methodType(int.class, int.class, int.class));
93-
MAPPED_ENUM_LOOKUP = LOOKUP.findStatic(SwitchBootstraps.class, "mappedEnumLookup",
94-
MethodType.methodType(int.class, Enum.class, MethodHandles.Lookup.class,
95-
Class.class, EnumDesc[].class, EnumMap.class));
96-
}
97-
catch (ReflectiveOperationException e) {
98-
throw new ExceptionInInitializerError(e);
89+
private static final MethodTypeDesc CHECK_INDEX_DESCRIPTOR =
90+
MethodTypeDesc.ofDescriptor("(II)I");
91+
92+
private static final ClassDesc CD_Objects = ReferenceClassDescImpl.ofValidated("Ljava/util/Objects;");
93+
94+
private static class StaticHolders {
95+
private static final MethodHandle NULL_CHECK;
96+
private static final MethodHandle IS_ZERO;
97+
private static final MethodHandle MAPPED_ENUM_LOOKUP;
98+
99+
static {
100+
try {
101+
NULL_CHECK = LOOKUP.findStatic(Objects.class, "isNull",
102+
MethodType.methodType(boolean.class, Object.class));
103+
IS_ZERO = LOOKUP.findStatic(SwitchBootstraps.class, "isZero",
104+
MethodType.methodType(boolean.class, int.class));
105+
MAPPED_ENUM_LOOKUP = LOOKUP.findStatic(SwitchBootstraps.class, "mappedEnumLookup",
106+
MethodType.methodType(int.class, Enum.class, MethodHandles.Lookup.class,
107+
Class.class, EnumDesc[].class, EnumMap.class));
108+
}
109+
catch (ReflectiveOperationException e) {
110+
throw new ExceptionInInitializerError(e);
111+
}
99112
}
100113
}
101114

@@ -163,14 +176,13 @@ public static CallSite typeSwitch(MethodHandles.Lookup lookup,
163176
|| (!invocationType.returnType().equals(int.class))
164177
|| !invocationType.parameterType(1).equals(int.class))
165178
throw new IllegalArgumentException("Illegal invocation type " + invocationType);
166-
requireNonNull(labels);
167179

168-
Stream.of(labels).forEach(l -> verifyLabel(l, selectorType));
180+
for (Object l : labels) { // implicit null-check
181+
verifyLabel(l, selectorType);
182+
}
169183

170184
MethodHandle target = generateTypeSwitch(lookup, selectorType, labels);
171185

172-
target = withIndexCheck(target, labels.length);
173-
174186
return new ConstantCallSite(target);
175187
}
176188

@@ -282,18 +294,17 @@ public static CallSite enumSwitch(MethodHandles.Lookup lookup,
282294
//else if (idx == 0) return mappingArray[selector.ordinal()]; //mapping array created lazily
283295
//else return "typeSwitch(labels)"
284296
MethodHandle body =
285-
MethodHandles.guardWithTest(MethodHandles.dropArguments(NULL_CHECK, 0, int.class),
297+
MethodHandles.guardWithTest(MethodHandles.dropArguments(StaticHolders.NULL_CHECK, 0, int.class),
286298
MethodHandles.dropArguments(MethodHandles.constant(int.class, -1), 0, int.class, Object.class),
287-
MethodHandles.guardWithTest(MethodHandles.dropArguments(IS_ZERO, 1, Object.class),
299+
MethodHandles.guardWithTest(MethodHandles.dropArguments(StaticHolders.IS_ZERO, 1, Object.class),
288300
generateTypeSwitch(lookup, invocationType.parameterType(0), labels),
289-
MethodHandles.insertArguments(MAPPED_ENUM_LOOKUP, 1, lookup, enumClass, labels, new EnumMap())));
301+
MethodHandles.insertArguments(StaticHolders.MAPPED_ENUM_LOOKUP, 1, lookup, enumClass, labels, new EnumMap())));
290302
target = MethodHandles.permuteArguments(body, MethodType.methodType(int.class, Object.class, int.class), 1, 0);
291303
} else {
292304
target = generateTypeSwitch(lookup, invocationType.parameterType(0), labels);
293305
}
294306

295307
target = target.asType(invocationType);
296-
target = withIndexCheck(target, labels.length);
297308

298309
return new ConstantCallSite(target);
299310
}
@@ -339,12 +350,6 @@ private static <T extends Enum<T>> int mappedEnumLookup(T value, MethodHandles.L
339350
return enumMap.map[value.ordinal()];
340351
}
341352

342-
private static MethodHandle withIndexCheck(MethodHandle target, int labelsCount) {
343-
MethodHandle checkIndex = MethodHandles.insertArguments(CHECK_INDEX, 1, labelsCount + 1);
344-
345-
return MethodHandles.filterArguments(target, 1, checkIndex);
346-
}
347-
348353
private static final class ResolvedEnumLabels implements BiPredicate<Integer, Object> {
349354

350355
private final MethodHandles.Lookup lookup;
@@ -407,6 +412,11 @@ private static Consumer<CodeBuilder> generateTypeSwitchSkeleton(Class<?> selecto
407412
int EXTRA_CLASS_LABELS = 3;
408413

409414
return cb -> {
415+
// Objects.checkIndex(RESTART_IDX, labelConstants + 1)
416+
cb.iload(RESTART_IDX);
417+
cb.loadConstant(labelConstants.length + 1);
418+
cb.invokestatic(CD_Objects, "checkIndex", CHECK_INDEX_DESCRIPTOR);
419+
cb.pop();
410420
cb.aload(SELECTOR_OBJ);
411421
Label nonNullLabel = cb.newLabel();
412422
cb.if_nonnull(nonNullLabel);
@@ -621,7 +631,7 @@ private static MethodHandle generateTypeSwitch(MethodHandles.Lookup caller, Clas
621631
List<EnumDesc<?>> enumDescs = new ArrayList<>();
622632
List<Class<?>> extraClassLabels = new ArrayList<>();
623633

624-
byte[] classBytes = ClassFile.of().build(ClassDesc.of(typeSwitchClassName(caller.lookupClass())),
634+
byte[] classBytes = ClassFile.of().build(ReferenceClassDescImpl.ofValidatedBinaryName(typeSwitchClassName(caller.lookupClass())),
625635
clb -> {
626636
clb.withFlags(AccessFlag.FINAL, AccessFlag.SUPER, AccessFlag.SYNTHETIC)
627637
.withMethodBody("typeSwitch",
@@ -636,12 +646,8 @@ private static MethodHandle generateTypeSwitch(MethodHandles.Lookup caller, Clas
636646
lookup = caller.defineHiddenClass(classBytes, true, NESTMATE, STRONG);
637647
MethodHandle typeSwitch = lookup.findStatic(lookup.lookupClass(),
638648
"typeSwitch",
639-
MethodType.methodType(int.class,
640-
Object.class,
641-
int.class,
642-
BiPredicate.class,
643-
List.class));
644-
typeSwitch = MethodHandles.insertArguments(typeSwitch, 2, new ResolvedEnumLabels(caller, enumDescs.toArray(EnumDesc[]::new)),
649+
TYPES_SWITCH_TYPE);
650+
typeSwitch = MethodHandles.insertArguments(typeSwitch, 2, new ResolvedEnumLabels(caller, enumDescs.toArray(new EnumDesc<?>[0])),
645651
List.copyOf(extraClassLabels));
646652
typeSwitch = MethodHandles.explicitCastArguments(typeSwitch,
647653
MethodType.methodType(int.class,

src/java.base/share/classes/jdk/internal/constant/ReferenceClassDescImpl.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,17 @@ public static ReferenceClassDescImpl ofValidated(String descriptor) {
6969
return new ReferenceClassDescImpl(descriptor);
7070
}
7171

72+
/**
73+
* Creates a {@linkplain ClassDesc} from a pre-validated descriptor string
74+
* for a class or interface type or an array type.
75+
*
76+
* @param descriptor a field descriptor string for a class or interface type
77+
* @jvms 4.3.2 Field Descriptors
78+
*/
79+
public static ClassDesc ofValidatedBinaryName(String typeSwitchClassName) {
80+
return ofValidated("L" + binaryToInternal(typeSwitchClassName) + ";");
81+
}
82+
7283
@Override
7384
public String descriptorString() {
7485
return descriptor;
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright (c) 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.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
package org.openjdk.bench.java.lang.runtime;
24+
25+
import org.openjdk.jmh.annotations.Benchmark;
26+
import org.openjdk.jmh.annotations.BenchmarkMode;
27+
import org.openjdk.jmh.annotations.Fork;
28+
import org.openjdk.jmh.annotations.Measurement;
29+
import org.openjdk.jmh.annotations.Mode;
30+
import org.openjdk.jmh.annotations.OutputTimeUnit;
31+
import org.openjdk.jmh.annotations.Scope;
32+
import org.openjdk.jmh.annotations.Setup;
33+
import org.openjdk.jmh.annotations.State;
34+
import org.openjdk.jmh.annotations.Warmup;
35+
36+
import java.util.concurrent.TimeUnit;
37+
38+
@BenchmarkMode(Mode.AverageTime)
39+
@OutputTimeUnit(TimeUnit.NANOSECONDS)
40+
@State(Scope.Thread)
41+
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
42+
@Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
43+
@Fork(3)
44+
public class SwitchSanity {
45+
46+
public record A(int a) { }
47+
public record B(int b) { }
48+
public record C(int c) { }
49+
50+
public Object[] inputs = new Object[10];
51+
@Setup
52+
public void setup() {
53+
for (int i = 0; i < 10; i++) {
54+
if (i % 2 == 0) {
55+
inputs[i] = new A(i + 17);
56+
} else if (i % 3 == 0) {
57+
inputs[i] = new B(i + 4711);
58+
} else {
59+
inputs[i] = new C(i + 174711);
60+
}
61+
}
62+
}
63+
64+
@Benchmark
65+
public int switchSum() {
66+
int sum = 0;
67+
for (Object o : inputs) {
68+
sum += switch (o) {
69+
case A a -> a.a;
70+
case B b -> b.b;
71+
case C c -> c.c;
72+
default -> 17;
73+
};
74+
}
75+
return sum;
76+
}
77+
78+
public static void main(String[] args) {
79+
SwitchSanity s = new SwitchSanity();
80+
s.setup();
81+
System.out.println(s.switchSum());
82+
}
83+
}

0 commit comments

Comments
 (0)