Skip to content

Commit c3971a8

Browse files
committed
Add API to control which bootstrap methods are allowed to be executed at build time
1 parent e9cee97 commit c3971a8

File tree

5 files changed

+157
-56
lines changed

5 files changed

+157
-56
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright (c) 2025, 2025, 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+
package com.oracle.svm.core.bootstrap;
26+
27+
import java.util.concurrent.ConcurrentHashMap;
28+
import java.util.concurrent.ConcurrentMap;
29+
30+
import org.graalvm.nativeimage.ImageSingletons;
31+
32+
import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton;
33+
import com.oracle.svm.core.traits.BuiltinTraits;
34+
import com.oracle.svm.core.traits.BuiltinTraits.BuildtimeAccessOnly;
35+
import com.oracle.svm.core.traits.SingletonLayeredInstallationKind.Independent;
36+
import com.oracle.svm.core.traits.SingletonTraits;
37+
38+
import jdk.vm.ci.meta.ResolvedJavaMethod;
39+
40+
/**
41+
* This image singleton is used to cache the {@link BootstrapMethodInfo} computed at run time to
42+
* make sure they are only computed once.
43+
*/
44+
@SingletonTraits(access = BuildtimeAccessOnly.class, layeredCallbacks = BuiltinTraits.NoLayeredCallbacks.class, layeredInstallationKind = Independent.class)
45+
@AutomaticallyRegisteredImageSingleton
46+
public class BootstrapMethodInfoCache {
47+
48+
/**
49+
* Map used to cache the {@link BootstrapMethodInfo} and reuse it for duplicated bytecode,
50+
* avoiding execution of the bootstrap method for the same bci and method pair. This can happen
51+
* during bytecode parsing as some blocks are duplicated, or for methods that are parsed
52+
* multiple times (see MultiMethod).
53+
*/
54+
private final ConcurrentMap<BootstrapMethodRecord, BootstrapMethodInfo> bootstrapMethodInfoCache = new ConcurrentHashMap<>();
55+
56+
/**
57+
* The key of the cache.
58+
*/
59+
public record BootstrapMethodRecord(int bci, int cpi, ResolvedJavaMethod method) {
60+
}
61+
62+
public static BootstrapMethodInfoCache singleton() {
63+
return ImageSingletons.lookup(BootstrapMethodInfoCache.class);
64+
}
65+
66+
public ConcurrentMap<BootstrapMethodRecord, BootstrapMethodInfo> getBootstrapMethodInfoCache() {
67+
return bootstrapMethodInfoCache;
68+
}
69+
}

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
import com.oracle.svm.core.util.VMError;
8989
import com.oracle.svm.hosted.ameta.FieldValueInterceptionSupport;
9090
import com.oracle.svm.hosted.analysis.Inflation;
91+
import com.oracle.svm.hosted.bootstrap.BootstrapMethodConfiguration;
9192
import com.oracle.svm.hosted.c.NativeLibraries;
9293
import com.oracle.svm.hosted.code.CEntryPointData;
9394
import com.oracle.svm.hosted.code.CompileQueue.CompileTask;
@@ -438,6 +439,30 @@ public void registerClassReachabilityListener(BiConsumer<DuringAnalysisAccess, C
438439
getHostVM().registerClassReachabilityListener(listener);
439440
}
440441

442+
/**
443+
* Registers a method that is allowed to be executed at build time if called as the
444+
* bootstrap method for an invokedynamic, in which case each call site outputted will be
445+
* constant-folded. Other bootstrap methods will be executed at run time by default,
446+
* creating the call site at run time.
447+
*
448+
* @since 25.1
449+
*/
450+
public void registerBuildTimeIndyIncludeList(Executable method) {
451+
BootstrapMethodConfiguration.singleton().addBuildTimeIndy(getUniverse().getOriginalMetaAccess().lookupJavaMethod(method));
452+
}
453+
454+
/**
455+
* Registers a method that is allowed to be executed at build time if called as the
456+
* bootstrap method for a constantdynamic, in which case each call site outputted will be
457+
* constant-folded. Other bootstrap methods will be executed at run time by default,
458+
* creating the call site at run time.
459+
*
460+
* @since 25.1
461+
*/
462+
public void registerBuildTimeCondyIncludeList(Executable method) {
463+
BootstrapMethodConfiguration.singleton().addBuildTimeCondy(getUniverse().getOriginalMetaAccess().lookupJavaMethod(method));
464+
}
465+
441466
public SVMHost getHostVM() {
442467
return bb.getHostVM();
443468
}

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/bootstrap/BootstrapMethodConfiguration.java

Lines changed: 58 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -30,27 +30,28 @@
3030
import java.lang.invoke.MethodType;
3131
import java.lang.invoke.StringConcatFactory;
3232
import java.lang.invoke.TypeDescriptor;
33-
import java.lang.reflect.Executable;
34-
import java.lang.reflect.Method;
3533
import java.lang.reflect.Proxy;
3634
import java.lang.runtime.ObjectMethods;
3735
import java.lang.runtime.SwitchBootstraps;
36+
import java.util.HashSet;
3837
import java.util.Set;
39-
import java.util.concurrent.ConcurrentHashMap;
40-
import java.util.concurrent.ConcurrentMap;
4138

4239
import org.graalvm.nativeimage.ImageSingletons;
4340

44-
import com.oracle.svm.core.bootstrap.BootstrapMethodInfo;
41+
import com.oracle.graal.pointsto.meta.AnalysisMethod;
4542
import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
4643
import com.oracle.svm.core.feature.InternalFeature;
4744
import com.oracle.svm.core.traits.BuiltinTraits.BuildtimeAccessOnly;
4845
import com.oracle.svm.core.traits.BuiltinTraits.NoLayeredCallbacks;
4946
import com.oracle.svm.core.traits.SingletonLayeredInstallationKind.Independent;
5047
import com.oracle.svm.core.traits.SingletonTraits;
51-
import com.oracle.svm.util.ReflectionUtil;
48+
import com.oracle.svm.hosted.reflect.proxy.ProxyRenamingSubstitutionProcessor;
49+
import com.oracle.svm.util.GraalAccess;
50+
import com.oracle.svm.util.JVMCIReflectionUtil;
5251

52+
import jdk.vm.ci.meta.MetaAccessProvider;
5353
import jdk.vm.ci.meta.ResolvedJavaMethod;
54+
import jdk.vm.ci.meta.ResolvedJavaType;
5455

5556
/**
5657
* Class storing a list of bootstrap methods that are allowed to be executed at build time. Those
@@ -60,89 +61,96 @@
6061
@SingletonTraits(access = BuildtimeAccessOnly.class, layeredCallbacks = NoLayeredCallbacks.class, layeredInstallationKind = Independent.class)
6162
@AutomaticallyRegisteredFeature
6263
public class BootstrapMethodConfiguration implements InternalFeature {
63-
64-
public record BootstrapMethodRecord(int bci, int cpi, ResolvedJavaMethod method) {
65-
}
66-
67-
/*
68-
* Map used to cache the BootstrapMethodInfo and reuse it for duplicated bytecode, avoiding
69-
* execution of the bootstrap method for the same bci and method pair. This can happen during
70-
* bytecode parsing as some blocks are duplicated, or for methods that are parsed multiple times
71-
* (see MultiMethod).
72-
*/
73-
private final ConcurrentMap<BootstrapMethodRecord, BootstrapMethodInfo> bootstrapMethodInfoCache = new ConcurrentHashMap<>();
74-
private final Set<Executable> indyBuildTimeAllowList;
75-
private final Set<Executable> condyBuildTimeAllowList;
76-
private final Method metafactory;
77-
private final Method altMetafactory;
64+
private final Set<ResolvedJavaMethod> buildTimeIndy = new HashSet<>();
65+
private final Set<ResolvedJavaMethod> buildTimeCondy = new HashSet<>();
7866

7967
public static BootstrapMethodConfiguration singleton() {
8068
return ImageSingletons.lookup(BootstrapMethodConfiguration.class);
8169
}
8270

83-
public BootstrapMethodConfiguration() {
71+
@Override
72+
public void duringSetup(DuringSetupAccess access) {
73+
MetaAccessProvider metaAccess = GraalAccess.getOriginalProviders().getMetaAccess();
8474
/*
8575
* Bootstrap method used for Lambdas. Executing this method at run time implies defining
8676
* hidden class at run time, which is unsupported.
8777
*/
88-
metafactory = ReflectionUtil.lookupMethod(LambdaMetafactory.class, "metafactory", MethodHandles.Lookup.class, String.class, MethodType.class, MethodType.class, MethodHandle.class,
89-
MethodType.class);
78+
ResolvedJavaType lambdaMetaFactory = metaAccess.lookupJavaType(LambdaMetafactory.class);
79+
ResolvedJavaMethod metafactory = JVMCIReflectionUtil.getUniqueDeclaredMethod(metaAccess, lambdaMetaFactory, "metafactory", MethodHandles.Lookup.class, String.class, MethodType.class,
80+
MethodType.class, MethodHandle.class, MethodType.class);
9081
/* Alternate version of LambdaMetafactory.metafactory. */
91-
altMetafactory = ReflectionUtil.lookupMethod(LambdaMetafactory.class, "altMetafactory", MethodHandles.Lookup.class, String.class, MethodType.class, Object[].class);
82+
ResolvedJavaMethod altMetafactory = JVMCIReflectionUtil.getUniqueDeclaredMethod(metaAccess, lambdaMetaFactory, "altMetafactory", MethodHandles.Lookup.class, String.class, MethodType.class,
83+
Object[].class);
84+
buildTimeIndy.add(metafactory);
85+
buildTimeIndy.add(altMetafactory);
9286

9387
/*
9488
* Bootstrap method used to optimize String concatenation. Executing it at run time
9589
* currently causes a StackOverFlow error as it infinitely calls itself.
9690
*/
97-
Method makeConcat = ReflectionUtil.lookupMethod(StringConcatFactory.class, "makeConcat", MethodHandles.Lookup.class, String.class, MethodType.class);
91+
ResolvedJavaType stringConcatFactory = metaAccess.lookupJavaType(StringConcatFactory.class);
92+
ResolvedJavaMethod makeConcat = JVMCIReflectionUtil.getUniqueDeclaredMethod(metaAccess, stringConcatFactory, "makeConcat", MethodHandles.Lookup.class, String.class, MethodType.class);
9893
/* Alternate version of StringConcatFactory.makeConcat with constant arguments. */
99-
Method makeConcatWithConstants = ReflectionUtil.lookupMethod(StringConcatFactory.class, "makeConcatWithConstants", MethodHandles.Lookup.class, String.class, MethodType.class, String.class,
100-
Object[].class);
94+
ResolvedJavaMethod makeConcatWithConstants = JVMCIReflectionUtil.getUniqueDeclaredMethod(metaAccess, stringConcatFactory, "makeConcatWithConstants", MethodHandles.Lookup.class, String.class,
95+
MethodType.class, String.class, Object[].class);
96+
buildTimeIndy.add(makeConcat);
97+
buildTimeIndy.add(makeConcatWithConstants);
10198

10299
/* Causes deadlock in Permission feature. */
103-
Method bootstrap = ReflectionUtil.lookupMethod(ObjectMethods.class, "bootstrap", MethodHandles.Lookup.class, String.class, TypeDescriptor.class, Class.class, String.class,
104-
MethodHandle[].class);
100+
ResolvedJavaType objectMethods = metaAccess.lookupJavaType(ObjectMethods.class);
101+
ResolvedJavaMethod bootstrap = JVMCIReflectionUtil.getUniqueDeclaredMethod(metaAccess, objectMethods, "bootstrap", MethodHandles.Lookup.class, String.class, TypeDescriptor.class, Class.class,
102+
String.class, MethodHandle[].class);
103+
buildTimeIndy.add(bootstrap);
105104

106105
/*
107106
* Bootstrap methods used for switch statements. Executing these methods at run time implies
108107
* defining hidden classes at run time, which is unsupported.
109108
*/
110-
Method typeSwitch = ReflectionUtil.lookupMethod(SwitchBootstraps.class, "typeSwitch", MethodHandles.Lookup.class, String.class, MethodType.class, Object[].class);
111-
Method enumSwitch = ReflectionUtil.lookupMethod(SwitchBootstraps.class, "enumSwitch", MethodHandles.Lookup.class, String.class, MethodType.class, Object[].class);
109+
ResolvedJavaType switchBootstraps = metaAccess.lookupJavaType(SwitchBootstraps.class);
110+
ResolvedJavaMethod typeSwitch = JVMCIReflectionUtil.getUniqueDeclaredMethod(metaAccess, switchBootstraps, "typeSwitch", MethodHandles.Lookup.class, String.class, MethodType.class,
111+
Object[].class);
112+
ResolvedJavaMethod enumSwitch = JVMCIReflectionUtil.getUniqueDeclaredMethod(metaAccess, switchBootstraps, "enumSwitch", MethodHandles.Lookup.class, String.class, MethodType.class,
113+
Object[].class);
114+
buildTimeIndy.add(typeSwitch);
115+
buildTimeIndy.add(enumSwitch);
116+
}
112117

113-
/* Bootstrap method used for retrieving the value of static final processors. */
114-
indyBuildTimeAllowList = Set.of(metafactory, altMetafactory, makeConcat, makeConcatWithConstants, bootstrap, typeSwitch, enumSwitch);
118+
public void addBuildTimeIndy(ResolvedJavaMethod method) {
119+
buildTimeIndy.add(method);
120+
}
115121

116-
/* Set of bootstrap methods for constant dynamic allowed at build time is empty for now */
117-
condyBuildTimeAllowList = Set.of();
122+
public void addBuildTimeCondy(ResolvedJavaMethod method) {
123+
buildTimeCondy.add(method);
118124
}
119125

120126
/**
121127
* Check if the provided method is allowed to be executed at build time.
122128
*/
123-
public boolean isIndyAllowedAtBuildTime(Executable method) {
124-
return method != null && indyBuildTimeAllowList.contains(method);
125-
}
126-
127-
public boolean isMetafactory(Executable method) {
128-
return method != null && (method.equals(metafactory) || method.equals(altMetafactory));
129+
public boolean isIndyAllowedAtBuildTime(ResolvedJavaMethod method) {
130+
ResolvedJavaMethod m = getWrapped(method);
131+
return m != null && buildTimeIndy.contains(m);
129132
}
130133

131134
/**
132135
* Check if the provided method is allowed to be executed at build time.
133136
*/
134-
public boolean isCondyAllowedAtBuildTime(Executable method) {
135-
return method != null && (condyBuildTimeAllowList.contains(method) || isProxyCondy(method));
137+
public boolean isCondyAllowedAtBuildTime(ResolvedJavaMethod method) {
138+
ResolvedJavaMethod m = getWrapped(method);
139+
return m != null && (buildTimeCondy.contains(m) || isProxyCondy(m));
140+
}
141+
142+
private static ResolvedJavaMethod getWrapped(ResolvedJavaMethod method) {
143+
if (method instanceof AnalysisMethod analysisMethod) {
144+
return analysisMethod.getWrapped();
145+
} else {
146+
return method;
147+
}
136148
}
137149

138150
/**
139151
* Every {@link Proxy} class has its own bootstrap method that is used for a constant dynamic.
140152
*/
141-
private static boolean isProxyCondy(Executable method) {
142-
return Proxy.isProxyClass(method.getDeclaringClass()) && method.getName().equals("$getMethod");
143-
}
144-
145-
public ConcurrentMap<BootstrapMethodRecord, BootstrapMethodInfo> getBootstrapMethodInfoCache() {
146-
return bootstrapMethodInfoCache;
153+
private static boolean isProxyCondy(ResolvedJavaMethod method) {
154+
return ProxyRenamingSubstitutionProcessor.isProxyType(method.getDeclaringClass()) && method.getName().equals("$getMethod");
147155
}
148156
}

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalysisGraphBuilderPhase.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
import com.oracle.svm.hosted.code.SubstrateCompilationDirectives;
3737
import com.oracle.svm.hosted.dynamicaccessinference.ConstantExpressionRegistry;
3838
import com.oracle.svm.util.ModuleSupport;
39-
import com.oracle.svm.util.OriginalMethodProvider;
4039

4140
import jdk.graal.compiler.core.common.type.StampFactory;
4241
import jdk.graal.compiler.core.common.type.TypeReference;
@@ -169,8 +168,7 @@ protected void genInvokeDynamic(int cpi, int opcode) {
169168
}
170169
JavaMethod calleeMethod = lookupMethodInPool(cpi, opcode);
171170

172-
if (bootstrap == null || calleeMethod instanceof ResolvedJavaMethod ||
173-
BootstrapMethodConfiguration.singleton().isIndyAllowedAtBuildTime(OriginalMethodProvider.getJavaMethod(bootstrap.getMethod()))) {
171+
if (bootstrap == null || calleeMethod instanceof ResolvedJavaMethod || BootstrapMethodConfiguration.singleton().isIndyAllowedAtBuildTime(bootstrap.getMethod())) {
174172
super.genInvokeDynamic(cpi, opcode);
175173
return;
176174
}

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SharedGraphBuilderPhase.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@
5454
import com.oracle.svm.core.ForeignSupport;
5555
import com.oracle.svm.core.bootstrap.BootstrapMethodInfo;
5656
import com.oracle.svm.core.bootstrap.BootstrapMethodInfo.ExceptionWrapper;
57+
import com.oracle.svm.core.bootstrap.BootstrapMethodInfoCache;
58+
import com.oracle.svm.core.bootstrap.BootstrapMethodInfoCache.BootstrapMethodRecord;
5759
import com.oracle.svm.core.deopt.DeoptimizationSupport;
5860
import com.oracle.svm.core.graal.nodes.DeoptEntryBeginNode;
5961
import com.oracle.svm.core.graal.nodes.DeoptEntryNode;
@@ -71,7 +73,6 @@
7173
import com.oracle.svm.hosted.LinkAtBuildTimeSupport;
7274
import com.oracle.svm.hosted.SharedArenaSupport;
7375
import com.oracle.svm.hosted.bootstrap.BootstrapMethodConfiguration;
74-
import com.oracle.svm.hosted.bootstrap.BootstrapMethodConfiguration.BootstrapMethodRecord;
7576
import com.oracle.svm.hosted.code.FactoryMethodSupport;
7677
import com.oracle.svm.hosted.code.SubstrateCompilationDirectives;
7778
import com.oracle.svm.hosted.nodes.DeoptProxyNode;
@@ -1442,7 +1443,7 @@ private Object loadConstantDynamic(int cpi, int opcode) {
14421443
}
14431444
}
14441445

1445-
if (!BootstrapMethodConfiguration.singleton().isCondyAllowedAtBuildTime(bootstrapMethod)) {
1446+
if (!BootstrapMethodConfiguration.singleton().isCondyAllowedAtBuildTime(bootstrap.getMethod())) {
14461447
int parameterLength = bootstrap.getMethod().getParameters().length;
14471448
List<JavaConstant> staticArguments = bootstrap.getStaticArguments();
14481449
boolean isVarargs = bootstrap.getMethod().isVarArgs();
@@ -1513,7 +1514,7 @@ protected Object resolveLinkedObject(int bci, int cpi, int opcode, BootstrapMeth
15131514
/* Step 1: Initialize the BootstrapMethodInfo. */
15141515

15151516
BootstrapMethodRecord bootstrapMethodRecord = new BootstrapMethodRecord(bci, cpi, ((AnalysisMethod) method).getMultiMethod(MultiMethod.ORIGINAL_METHOD));
1516-
BootstrapMethodInfo bootstrapMethodInfo = BootstrapMethodConfiguration.singleton().getBootstrapMethodInfoCache().computeIfAbsent(bootstrapMethodRecord,
1517+
BootstrapMethodInfo bootstrapMethodInfo = BootstrapMethodInfoCache.singleton().getBootstrapMethodInfoCache().computeIfAbsent(bootstrapMethodRecord,
15171518
_ -> new BootstrapMethodInfo());
15181519
ConstantNode bootstrapMethodInfoNode = ConstantNode.forConstant(getSnippetReflection().forObject(bootstrapMethodInfo), getMetaAccess(), getGraph());
15191520

0 commit comments

Comments
 (0)