Skip to content

Commit a84ff87

Browse files
author
Christian Wimmer
committed
[GR-28460] Improve InlineBeforeAnalysis.
PullRequest: graal/9469
2 parents ce41046 + cd9cab2 commit a84ff87

File tree

4 files changed

+100
-16
lines changed

4 files changed

+100
-16
lines changed

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysis.java

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,26 @@ class InlineBeforeAnalysisMethodScope extends PEMethodScope {
128128
super(targetGraph, caller, callerLoopScope, encodedGraph, method, invokeData, inliningDepth, arguments);
129129

130130
if (caller == null) {
131+
/*
132+
* The root method that we are decoding, i.e., inlining into. No policy, because the
133+
* whole method must of course be decoded.
134+
*/
135+
policyScope = null;
136+
} else if (caller.caller == null) {
137+
/*
138+
* The first level of method inlining, i.e., the top scope from the inlining policy
139+
* point of view.
140+
*/
131141
policyScope = policy.createTopScope();
142+
if (graph.getDebug().isLogEnabled()) {
143+
graph.getDebug().logv(repeat(" ", inliningDepth) + "createTopScope for " + method.format("%H.%n(%p)") + ": " + policyScope);
144+
}
132145
} else {
146+
/* Nested inlining. */
133147
policyScope = policy.openCalleeScope((cast(caller)).policyScope);
148+
if (graph.getDebug().isLogEnabled()) {
149+
graph.getDebug().logv(repeat(" ", inliningDepth) + "openCalleeScope for " + method.format("%H.%n(%p)") + ": " + policyScope);
150+
}
134151
}
135152
}
136153
}
@@ -146,6 +163,10 @@ class InlineBeforeAnalysisMethodScope extends PEMethodScope {
146163
new ConcurrentHashMap<>(), new ConcurrentHashMap<>(), true);
147164
this.bb = bb;
148165
this.policy = policy;
166+
167+
if (graph.getDebug().isLogEnabled()) {
168+
graph.getDebug().logv("InlineBeforeAnalysis: decoding " + graph.method().format("%H.%n(%p)"));
169+
}
149170
}
150171

151172
@Override
@@ -192,8 +213,16 @@ protected void handleNonInlinedInvoke(MethodScope methodScope, LoopScope loopSco
192213

193214
private void maybeAbortInlining(MethodScope ms, Node node) {
194215
InlineBeforeAnalysisMethodScope methodScope = cast(ms);
195-
if (!methodScope.inliningAborted && methodScope.isInlinedMethod() && !policy.processNode(methodScope.policyScope, node)) {
196-
methodScope.inliningAborted = true;
216+
if (!methodScope.inliningAborted && methodScope.isInlinedMethod()) {
217+
if (graph.getDebug().isLogEnabled()) {
218+
graph.getDebug().logv(repeat(" ", methodScope.inliningDepth) + " node " + node + ": " + methodScope.policyScope);
219+
}
220+
if (!policy.processNode(methodScope.policyScope, node)) {
221+
if (graph.getDebug().isLogEnabled()) {
222+
graph.getDebug().logv(repeat(" ", methodScope.inliningDepth) + " abort!");
223+
}
224+
methodScope.inliningAborted = true;
225+
}
197226
}
198227
}
199228

@@ -221,8 +250,12 @@ protected void finishInlining(MethodScope is) {
221250
InvokeData invokeData = inlineScope.invokeData;
222251

223252
if (inlineScope.inliningAborted) {
224-
policy.abortCalleeScope(callerScope.policyScope, inlineScope.policyScope);
225-
253+
if (graph.getDebug().isLogEnabled()) {
254+
graph.getDebug().logv(repeat(" ", callerScope.inliningDepth) + " aborted " + invokeData.callTarget.targetMethod().format("%H.%n(%p)") + ": " + inlineScope.policyScope);
255+
}
256+
if (callerScope.policyScope != null) {
257+
policy.abortCalleeScope(callerScope.policyScope, inlineScope.policyScope);
258+
}
226259
killControlFlowNodes(inlineScope, invokeData.invokePredecessor.next());
227260
assert invokeData.invokePredecessor.next() == null : "Successor must have been a fixed node created in the aborted scope, which is deleted now";
228261
invokeData.invokePredecessor.setNext(invokeData.invoke.asNode());
@@ -239,12 +272,26 @@ protected void finishInlining(MethodScope is) {
239272
return;
240273
}
241274

242-
policy.commitCalleeScope(callerScope.policyScope, inlineScope.policyScope);
275+
if (graph.getDebug().isLogEnabled()) {
276+
graph.getDebug().logv(repeat(" ", callerScope.inliningDepth) + " committed " + invokeData.callTarget.targetMethod().format("%H.%n(%p)") + ": " + inlineScope.policyScope);
277+
}
278+
if (callerScope.policyScope != null) {
279+
policy.commitCalleeScope(callerScope.policyScope, inlineScope.policyScope);
280+
}
243281
((AnalysisMethod) invokeData.callTarget.targetMethod()).registerAsInlined();
244282

245283
super.finishInlining(inlineScope);
246284
}
247285

286+
/* String.repeat is only available in JDK 11 and later, so need to do our own. */
287+
private static String repeat(String s, int count) {
288+
StringBuilder result = new StringBuilder();
289+
for (int i = 0; i < count; i++) {
290+
result.append(s);
291+
}
292+
return result.toString();
293+
}
294+
248295
/**
249296
* Kill fixed nodes of structured control flow. Not as generic, but faster, than
250297
* {@link GraphUtil#killCFG}.

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,6 @@ public static Class<?> forNameOrNull(String className, ClassLoader classLoader)
5858
// Note: for non-predefined classes, we (currently) don't need to check the provided loader
5959
return result;
6060
}
61-
62-
/** Whether a call to {@link Class#forName} for the given class can be folded to a constant. */
63-
public static boolean canBeFolded(Class<?> clazz) {
64-
return !PredefinedClassesSupport.isPredefined(clazz);
65-
}
6661
}
6762

6863
@AutomaticFeature

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

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,12 @@
2525
package com.oracle.svm.hosted.phases;
2626

2727
import org.graalvm.compiler.graph.Node;
28-
import org.graalvm.compiler.nodeinfo.NodeSize;
28+
import org.graalvm.compiler.nodes.AbstractBeginNode;
29+
import org.graalvm.compiler.nodes.AbstractEndNode;
2930
import org.graalvm.compiler.nodes.CallTargetNode;
3031
import org.graalvm.compiler.nodes.ConstantNode;
3132
import org.graalvm.compiler.nodes.FrameState;
33+
import org.graalvm.compiler.nodes.FullInfopointNode;
3234
import org.graalvm.compiler.nodes.Invoke;
3335
import org.graalvm.compiler.nodes.LogicConstantNode;
3436
import org.graalvm.compiler.nodes.ParameterNode;
@@ -39,6 +41,7 @@
3941
import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderContext;
4042
import org.graalvm.compiler.nodes.java.AbstractNewObjectNode;
4143
import org.graalvm.compiler.nodes.java.NewArrayNode;
44+
import org.graalvm.compiler.nodes.spi.ValueProxy;
4245
import org.graalvm.compiler.options.Option;
4346
import org.graalvm.util.GuardedAnnotationAccess;
4447

@@ -60,6 +63,12 @@
6063
* method above the limit. On the other hand, the inlining depth is generous because we do do not
6164
* need to limit it. Note that more experimentation is necessary to come up with the optimal
6265
* configuration.
66+
*
67+
* Important: the implementation details of this class are publicly observable API. Since
68+
* {@link java.lang.reflect.Method} constants can be produced by inlining lookup methods with
69+
* constant arguments, reducing inlining can break customer code. This means we can never reduce the
70+
* amount of inlining in a future version without breaking compatibility. This also means that we
71+
* must be conservative and only inline what is necessary for known use cases.
6372
*/
6473
public final class InlineBeforeAnalysisPolicyImpl extends InlineBeforeAnalysisPolicy<InlineBeforeAnalysisPolicyImpl.CountersScope> {
6574

@@ -87,6 +96,11 @@ static final class CountersScope implements InlineBeforeAnalysisPolicy.Scope {
8796
CountersScope(CountersScope accumulated) {
8897
this.accumulated = accumulated;
8998
}
99+
100+
@Override
101+
public String toString() {
102+
return numNodes + "/" + numInvokes + " (" + accumulated.numNodes + "/" + accumulated.numInvokes + ")";
103+
}
90104
}
91105

92106
@Override
@@ -152,8 +166,13 @@ protected boolean processNode(CountersScope scope, Node node) {
152166
/* Infrastructure nodes that are not even visible to the policy. */
153167
throw VMError.shouldNotReachHere("Node must not be visible to policy: " + node.getClass().getTypeName());
154168
}
155-
if (node.getNodeClass().size() == NodeSize.SIZE_0 || node instanceof FrameState) {
156-
/* Infrastructure nodes that are never counted. */
169+
if (node instanceof FullInfopointNode || node instanceof ValueProxy || node instanceof FrameState || node instanceof AbstractBeginNode || node instanceof AbstractEndNode) {
170+
/*
171+
* Infrastructure nodes that are never counted. We could look at the NodeSize annotation
172+
* of a node, but that is a bit unreliable. For example, FrameState and
173+
* ExceptionObjectNode have size != 0 but we do not want to count them; CallTargetNode
174+
* has size 0 but we need to count it.
175+
*/
157176
return true;
158177
}
159178

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/ReflectionPlugins.java

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
import com.oracle.svm.core.SubstrateOptions;
6464
import com.oracle.svm.core.TypeResult;
6565
import com.oracle.svm.core.annotate.Delete;
66-
import com.oracle.svm.core.hub.ClassForNameSupport;
66+
import com.oracle.svm.core.hub.PredefinedClassesSupport;
6767
import com.oracle.svm.core.option.HostedOptionKey;
6868
import com.oracle.svm.core.util.VMError;
6969
import com.oracle.svm.hosted.ExceptionSynthesizer;
@@ -204,7 +204,6 @@ public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Rec
204204

205205
private void registerClassPlugins(InvocationPlugins plugins) {
206206
registerFoldInvocationPlugins(plugins, Class.class,
207-
"getClassLoader",
208207
"isInterface", "isPrimitive",
209208
"getField", "getMethod", "getConstructor",
210209
"getDeclaredField", "getDeclaredMethod", "getDeclaredConstructor");
@@ -228,6 +227,12 @@ public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Rec
228227
return processClassForName(b, targetMethod, nameNode, initializeNode);
229228
}
230229
});
230+
r.register1("getClassLoader", Receiver.class, new InvocationPlugin() {
231+
@Override
232+
public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver) {
233+
return processClassGetClassLoader(b, targetMethod, receiver);
234+
}
235+
});
231236
}
232237

233238
private static final Constructor<MethodHandles.Lookup> LOOKUP_CONSTRUCTOR = ReflectionUtil.lookupConstructor(MethodHandles.Lookup.class, Class.class);
@@ -275,7 +280,7 @@ private boolean processClassForName(GraphBuilderContext b, ResolvedJavaMethod ta
275280
return throwException(b, targetMethod, targetParameters, e.getClass(), e.getMessage());
276281
}
277282
Class<?> clazz = typeResult.get();
278-
if (!ClassForNameSupport.canBeFolded(clazz)) {
283+
if (PredefinedClassesSupport.isPredefined(clazz)) {
279284
return false;
280285
}
281286

@@ -290,6 +295,24 @@ private boolean processClassForName(GraphBuilderContext b, ResolvedJavaMethod ta
290295
return true;
291296
}
292297

298+
/**
299+
* For {@link PredefinedClassesSupport predefined classes}, the class loader is not known yet at
300+
* image build time. So we must not constant fold Class.getClassLoader for such classes. But for
301+
* "normal" classes, it is important to fold it because it unlocks further constant folding of,
302+
* e.g., Class.forName calls.
303+
*/
304+
private boolean processClassGetClassLoader(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver) {
305+
Object classValue = unbox(b, receiver.get(false), JavaKind.Object);
306+
if (!(classValue instanceof Class)) {
307+
return false;
308+
}
309+
Class<?> clazz = (Class<?>) classValue;
310+
if (PredefinedClassesSupport.isPredefined(clazz)) {
311+
return false;
312+
}
313+
return pushConstant(b, targetMethod, () -> clazz.getName(), JavaKind.Object, clazz.getClassLoader()) != null;
314+
}
315+
293316
/**
294317
* Helper to register all declared methods by name only, to avoid listing all the complete
295318
* parameter types. It also simplifies handling of different JDK versions, because methods not

0 commit comments

Comments
 (0)