Skip to content

Commit 3c85875

Browse files
committed
Intrinsify reflective calls when the argument is a constant.
1 parent 1205804 commit 3c85875

File tree

16 files changed

+605
-91
lines changed

16 files changed

+605
-91
lines changed

compiler/src/org.graalvm.compiler.java/src/org/graalvm/compiler/java/BytecodeParser.java

+8-2
Original file line numberDiff line numberDiff line change
@@ -1929,9 +1929,15 @@ String error(String format, Object... a) {
19291929
}
19301930

19311931
boolean check(boolean pluginResult) {
1932-
if (pluginResult == true) {
1932+
if (pluginResult) {
1933+
/*
1934+
* If lastInstr is null, even if this method has a non-void return type, the method
1935+
* doesn't return a value, it probably throws an exception.
1936+
*/
19331937
int expectedStackSize = beforeStackSize + resultType.getSlotCount();
1934-
assert expectedStackSize == frameState.stackSize() : error("plugin manipulated the stack incorrectly: expected=%d, actual=%d", expectedStackSize, frameState.stackSize());
1938+
assert lastInstr == null || expectedStackSize == frameState.stackSize() : error("plugin manipulated the stack incorrectly: expected=%d, actual=%d", expectedStackSize,
1939+
frameState.stackSize());
1940+
19351941
NodeIterable<Node> newNodes = graph.getNewNodes(mark);
19361942
assert !needsNullCheck || isPointerNonNull(args[0].stamp(NodeView.DEFAULT)) : error("plugin needs to null check the receiver of %s: receiver=%s", targetMethod.format("%H.%n(%p)"),
19371943
args[0]);

substratevm/LIMITATIONS.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ Reflection
3939

4040
What: Calling `Class.forName()`; listing methods and fields of a class; invoking methods and accessing fields reflectively; most classes in the package `java.lang.reflect`.
4141

42-
Individual classes, methods, and fields that should be accessible via reflection must be specified during native image generation in a configuration file via the option `-H:ReflectionConfigurationFiles=`, or by using [`RuntimeReflection`](http://www.graalvm.org/sdk/javadoc/org/graalvm/nativeimage/RuntimeReflection.html) from a [`Feature`](http://www.graalvm.org/sdk/javadoc/org/graalvm/nativeimage/Feature.html). Elements (classes, methods, and fields) that are not included in a configuration cannot be accessed reflectively. For more details, read our [documentation on reflection](REFLECTION.md).
42+
Individual classes, methods, and fields that should be accessible via reflection need to be known ahead-of-time. SubstrateVM tries to resolve these elements through a static analysis that detects calls to the reflection API. Where the analysis fails the program elements reflectively accessed at run time must be specified during native image generation in a configuration file via the option `-H:ReflectionConfigurationFiles=`, or by using [`RuntimeReflection`](http://www.graalvm.org/sdk/javadoc/org/graalvm/nativeimage/RuntimeReflection.html) from a [`Feature`](http://www.graalvm.org/sdk/javadoc/org/graalvm/nativeimage/Feature.html). For more details, read our [documentation on reflection](REFLECTION.md).
4343

4444
During native image generation, reflection can be used without restrictions during native image generation, for example in static initializers.
4545

substratevm/REFLECTION.md

+49-10
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,54 @@
11
# Reflection on Substrate VM
22

3-
Java reflection support (`java.lang.reflect`) enables Java code to examine its own classes, methods and fields and their properties at runtime. One useful application of this feature is the serialization of arbitrary data structures. Substrate VM has partial support for reflection and requires a configuration with those program elements that should be examinable at image runtime.
3+
Java reflection support (the `java.lang.reflect.*` API) enables Java code to examine its own classes, methods and fields and their properties at runtime.
44

5-
## Configuration
5+
Substrate VM has partial support for reflection and it needs to know ahead of time the reflectively accessed program elements. Examining and accessing program elements through `java.lang.reflect.*` or loading classes with `Class.forName(String)` at run time requires preparing additional metadata for those program elements. (Note: We include here loading classes with `Class.forName(String)` since it is closely related to reflection.)
66

7-
Examining and accessing program elements through `java.lang.reflect` at runtime requires preparing additional metadata for those program elements. A configuration that specifies those program elements must be provided during the image build as follows:
7+
SubstrateVM tries to resolve the target elements through a static analysis that detects calls to the reflection API. Where the analysis fails the program elements reflectively accessed at run time must be specified using a manual configuration.
8+
9+
## Automatic detection
10+
11+
The analysis intercepts calls to `Class.forName(String)`, `Class.forName(String, ClassLoader)`, `Class.getDeclaredField(String)`, `Class.getField(String)`, `Class.getDeclaredMethod(String, Class[])`, `Class.getMethod(String, Class[])`, `Class.getDeclaredConstructor(Class[])` and `Class.getConstructor(Class[])`. If the arguments to these calls can be reduced to a constant we try to resolve the target elements. If the target elements can be resolved the calls are removed and instead the target elements are embedded in the code. If the target elements cannot be resolved, e.g., a class is not on the classpath or it doesn't declare a field/method/constructor, then the calls are replaced with a snippet that throws the appropriate exception at run time. The benefits are two fold. First, at run time there are no calls to the reflection API. Second, Graal can employ constant folding and optimize the code further.
12+
13+
The calls are intercepted and processed only when it can be unequivocally determined that the parameters can be reduced to a constant. For example the call `Class.forName(String)` will be replaced with a `Class` literal only if the `String` argument can be constant folded, assuming that the class is actually on the classpath. Additionally a call to `Class.getMethod(String, Class[])` will be processed only if the contents of the `Class[]` argument can be determined with certainty. The last restriction is due to the fact that Java doesn't have immutable arrays. Therefore all the changes to the array between the time it is allocated and the time it is passed as an argument need to be tracked. The analysis follows a simple rule: if all the writes to the array happen in linear sections of code, i.e., no control flow splits, then the array is effectively constant for the purpose of analyzing the call. That is why the analysis doesn't accept `Class[]` arguments coming from static fields since the contents of those can change at any time, even if the fields are final. Although this may seem too restrictive it covers the most commonly used patterns of reflection API calls. The only exception to the constant arguments rule is that the `ClassLoader` argument of `Class.forName(String, ClassLoader)` doesn't need to be a constant; it is ignored and instead a class loader that can load all the classes on the class path is used. The analysis runs to a fix point which means that a chain of calls like `Class.forName(String).getMethod(String, Class[])` will first replace the class constant and then the method effectively reducing this to a `java.lang.reflect.Method`.
14+
15+
Following are examples of calls that can be intercepted and replaced with the corresponding element:
16+
17+
```
18+
Class.forName("java.lang.Integer")
19+
Class.forName("java.lang.Integer", true, ClassLoader.getSystemClassLoader())
20+
Class.forName("java.lang.Integer").getMethod("equals", Object.class)
21+
Integer.class.getDeclaredMethod("bitCount", int.class)
22+
Integer.class.getConstructor(String.class)
23+
Integer.class.getDeclaredConstructor(int.class)
24+
Integer.class.getField("MAX_VALUE")
25+
Integer.class.getDeclaredField("value")
26+
```
27+
28+
The following ways to declare and populate an array are equivalent from the point of view of the analysis:
29+
30+
```
31+
Class<?>[] params0 = new Class<?>[]{String.class, int.class};
32+
Integer.class.getMethod("parseInt", params0);
33+
```
34+
35+
```
36+
Class<?>[] params1 = new Class<?>[2];
37+
params1[0] = Class.forName("java.lang.String");
38+
params1[1] = int.class;
39+
Integer.class.getMethod("parseInt", params1);
40+
```
41+
42+
```
43+
Class<?>[] params2 = {String.class, int.class};
44+
Integer.class.getMethod("parseInt", params2);
45+
```
46+
47+
If a call cannot be processed it is simply skipped. For these situations a manual configuration as described below can be provided.
48+
49+
## Manual configuration
50+
51+
A configuration that specifies the program elements that will be accessed reflectively can be provided during the image build as follows:
852

953
-H:ReflectionConfigurationFiles=/path/to/reflectconfig
1054

@@ -13,8 +57,8 @@ where `reflectconfig` is a JSON file in the following format (use `--expert-opti
1357
[
1458
{
1559
"name" : "java.lang.Class",
16-
"allDeclaredConstructors" : true,
17-
"allPublicConstructors" : true
60+
"allDeclaredConstructors" : true,
61+
"allPublicConstructors" : true
1862
"allDeclaredMethods" : true,
1963
"allPublicMethods" : true
2064
},
@@ -60,10 +104,5 @@ Alternatively, a custom `Feature` implementation can register program elements b
60104
}
61105

62106

63-
## Limitations at Runtime
64-
Dynamic class loading using `Class.forName()` is not available due to the ahead-of-time image generation model of Substrate VM.
65-
66-
See also our [list of general limitations](#LIMITATIONS.md).
67-
68107
## Use during Native Image Generation
69108
Reflection can be used without restrictions during native image generation, for example in static initializers. At this point, code can collect information about methods and fields and store them in own data structures, which are then reflection-free at run time.

substratevm/src/com.oracle.svm.core.graal/src/com/oracle/svm/core/graal/GraalFeature.java

+6-5
Original file line numberDiff line numberDiff line change
@@ -52,30 +52,31 @@ public interface GraalFeature extends Feature {
5252
* @param providers Providers that the lowering can use.
5353
* @param snippetReflection Snippet reflection providers.
5454
* @param foreignCalls The foreign call registry to add to.
55-
* @param hosted True if registering for ahead-of-time compilation, false if registering for
55+
* @param hosted True if registering for ahead-of-time compilation, false otherwise
5656
*/
5757
default void registerForeignCalls(RuntimeConfiguration runtimeConfig, Providers providers, SnippetReflectionProvider snippetReflection,
5858
Map<SubstrateForeignCallDescriptor, SubstrateForeignCallLinkage> foreignCalls, boolean hosted) {
5959
}
6060

6161
/**
6262
* Called to register Graal invocation plugins.
63-
*
63+
*
6464
* @param providers Providers that the lowering can use.
6565
* @param snippetReflection Snippet reflection providers.
6666
* @param invocationPlugins The invocation plugins to add to.
67-
* @param hosted True if registering for ahead-of-time compilation, false if registering for
67+
* @param analysis true if registering for analysis, false if registering for compilation
68+
* @param hosted True if registering for ahead-of-time compilation, false otherwise
6869
*/
69-
default void registerInvocationPlugins(Providers providers, SnippetReflectionProvider snippetReflection, InvocationPlugins invocationPlugins, boolean hosted) {
70+
default void registerInvocationPlugins(Providers providers, SnippetReflectionProvider snippetReflection, InvocationPlugins invocationPlugins, boolean analysis, boolean hosted) {
7071
}
7172

7273
/**
7374
* Called to register Graal node plugins.
7475
*
7576
* @param metaAccess MetaAccessProvider that the node plugins can use.
7677
* @param plugins The Plugins object where node plugins can be added to.
78+
* @param analysis true if registering for analysis, false if registering for compilation
7779
* @param hosted true if registering for ahead-of-time compilation, false if registering for
78-
* @param analysis true if registering for analysis, false if registering for compilaiton
7980
*/
8081
default void registerNodePlugins(MetaAccessProvider metaAccess, Plugins plugins, boolean analysis, boolean hosted) {
8182
}

substratevm/src/com.oracle.svm.core.graal/src/com/oracle/svm/core/graal/jdk/JDKIntrinsicsFeature.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public void registerLowerings(RuntimeConfiguration runtimeConfig, OptionValues o
6666
}
6767

6868
@Override
69-
public void registerInvocationPlugins(Providers providers, SnippetReflectionProvider snippetReflection, InvocationPlugins plugins, boolean hosted) {
69+
public void registerInvocationPlugins(Providers providers, SnippetReflectionProvider snippetReflection, InvocationPlugins plugins, boolean analysis, boolean hosted) {
7070
registerSystemPlugins(plugins);
7171
}
7272

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright (c) 2018, 2018, 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.reflect;
26+
27+
// Checkstyle: allow reflection
28+
29+
import java.lang.reflect.Executable;
30+
import java.lang.reflect.Field;
31+
32+
public class ReflectionPluginExceptions {
33+
34+
public static Class<?> throwClassNotFoundException(String message) throws ClassNotFoundException {
35+
throw new ClassNotFoundException(message);
36+
}
37+
38+
public static Field throwNoSuchFieldException(String message) throws NoSuchFieldException {
39+
throw new NoSuchFieldException(message);
40+
}
41+
42+
public static Executable throwNoSuchMethodException(String message) throws NoSuchMethodException {
43+
throw new NoSuchMethodException(message);
44+
}
45+
}

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -515,9 +515,9 @@ private void doRun(Map<Method, CEntryPointData> entryPoints, Method mainEntryPoi
515515
ImageSingletons.add(AnnotationSubstitutionProcessor.class, annotationSubstitutions);
516516
annotationSubstitutions.init();
517517

518-
UnsafeAutomaticSubstitutionProcessor automaticSubstitutions = new UnsafeAutomaticSubstitutionProcessor(annotationSubstitutions);
518+
UnsafeAutomaticSubstitutionProcessor automaticSubstitutions = new UnsafeAutomaticSubstitutionProcessor(annotationSubstitutions, originalSnippetReflection);
519519
ImageSingletons.add(UnsafeAutomaticSubstitutionProcessor.class, automaticSubstitutions);
520-
automaticSubstitutions.init(originalMetaAccess);
520+
automaticSubstitutions.init(loader, originalMetaAccess);
521521

522522
CEnumCallWrapperSubstitutionProcessor cEnumProcessor = new CEnumCallWrapperSubstitutionProcessor();
523523

@@ -1029,7 +1029,7 @@ public static void registerGraphBuilderPlugins(FeatureHandler featureHandler, Ru
10291029
SubstrateGraphBuilderPlugins.registerInvocationPlugins(pluginsMetaAccess, providers.getConstantReflection(), hostedSnippetReflection, plugins.getInvocationPlugins(),
10301030
replacementBytecodeProvider, analysis);
10311031

1032-
featureHandler.forEachGraalFeature(feature -> feature.registerInvocationPlugins(providers, hostedSnippetReflection, plugins.getInvocationPlugins(), hosted));
1032+
featureHandler.forEachGraalFeature(feature -> feature.registerInvocationPlugins(providers, hostedSnippetReflection, plugins.getInvocationPlugins(), analysis, hosted));
10331033

10341034
providers.setGraphBuilderPlugins(plugins);
10351035
replacements.setGraphBuilderPlugins(plugins);

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/CGlobalDataFeature.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public void afterHeapLayout(AfterHeapLayoutAccess access) {
7676
}
7777

7878
@Override
79-
public void registerInvocationPlugins(Providers providers, SnippetReflectionProvider snippetReflection, InvocationPlugins invocationPlugins, boolean hosted) {
79+
public void registerInvocationPlugins(Providers providers, SnippetReflectionProvider snippetReflection, InvocationPlugins invocationPlugins, boolean analysis, boolean hosted) {
8080
Registration r = new Registration(invocationPlugins, CGlobalData.class);
8181
r.register1("get", Receiver.class, new InvocationPlugin() {
8282
@Override

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/function/CEntryPointSupport.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
@AutomaticFeature
6262
public class CEntryPointSupport implements GraalFeature {
6363
@Override
64-
public void registerInvocationPlugins(Providers providers, SnippetReflectionProvider snippetReflection, InvocationPlugins invocationPlugins, boolean hosted) {
64+
public void registerInvocationPlugins(Providers providers, SnippetReflectionProvider snippetReflection, InvocationPlugins invocationPlugins, boolean analysis, boolean hosted) {
6565
registerEntryPointActionsPlugins(invocationPlugins);
6666
registerEntryPointContextPlugins(invocationPlugins);
6767
}

0 commit comments

Comments
 (0)