Skip to content

Commit 78faa3d

Browse files
committed
[GR-70633] Ensure invokes in core methods can be devirtualized in open type world
PullRequest: graal/22326
2 parents ba71c4b + c48750a commit 78faa3d

File tree

3 files changed

+64
-43
lines changed

3 files changed

+64
-43
lines changed

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/results/TypeFlowSimplifier.java

Lines changed: 41 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,28 @@ private void handleInvoke(Invoke invoke, SimplifierTool tool) {
338338
}
339339
}
340340

341+
boolean hasReceiver = invokeFlow.getTargetMethod().hasReceiver();
342+
/*
343+
* The receiver's analysis results are complete when either:
344+
*
345+
* 1. We are in the closed world.
346+
*
347+
* 2. The receiver TypeFlow's type is a closed type, so it may be not extended in a later
348+
* layer.
349+
*
350+
* 3. The receiver TypeFlow's type is a core type, so it may be not extended in a later
351+
* layer. (GR-70846: This check condition will be merged with the previous one once core
352+
* types are fully considered as closed.)
353+
*
354+
* 4. The receiver TypeFlow is not saturated.
355+
*
356+
* Otherwise, when the receiver's analysis results are incomplete, then it is possible for
357+
* more types to be observed in subsequent layers.
358+
*/
359+
boolean receiverAnalysisResultsComplete = strengthenGraphs.isClosedTypeWorld ||
360+
(hasReceiver && (analysis.isClosed(invokeFlow.getReceiverType()) || analysis.getHostVM().isCoreType(invokeFlow.getReceiverType()) ||
361+
!methodFlow.isSaturated(analysis, invokeFlow.getReceiver())));
362+
341363
if (callTarget.invokeKind().isDirect()) {
342364
/*
343365
* Note: A direct invoke doesn't necessarily imply that the analysis should have
@@ -359,40 +381,38 @@ private void handleInvoke(Invoke invoke, SimplifierTool tool) {
359381
AnalysisError.guarantee(callees.size() == 1, "@Delete methods should have a single callee.");
360382
AnalysisMethod singleCallee = callees.iterator().next();
361383
devirtualizeInvoke(singleCallee, invoke);
362-
} else if (targetMethod.canBeStaticallyBound() || strengthenGraphs.isClosedTypeWorld) {
384+
} else if (targetMethod.canBeStaticallyBound() || (receiverAnalysisResultsComplete && callees.size() == 1)) {
363385
/*
364-
* We only de-virtualize invokes if we run a closed type world analysis or the target
365-
* method can be trivially statically bound.
386+
* A method can be devirtualized if there is only one possible callee. This can be
387+
* determined by the following ways:
388+
*
389+
* 1. The method can be trivially statically bound, as determined independently of
390+
* analysis results.
391+
*
392+
* 2. Analysis results indicate there is only one callee. The analysis results are
393+
* required to be complete, as there could be more than only one callee in subsequent
394+
* layers.
366395
*/
367-
if (callees.size() == 1) {
368-
AnalysisMethod singleCallee = callees.iterator().next();
369-
devirtualizeInvoke(singleCallee, invoke);
370-
} else {
371-
TypeState receiverTypeState = null;
372-
/* If the receiver flow is saturated, its exact type state does not matter. */
373-
if (invokeFlow.getTargetMethod().hasReceiver() && !methodFlow.isSaturated(analysis, invokeFlow.getReceiver())) {
374-
receiverTypeState = methodFlow.foldTypeFlow(analysis, invokeFlow.getReceiver());
375-
}
376-
assignInvokeProfiles(invoke, invokeFlow, callees, receiverTypeState, false);
377-
}
396+
assert callees.size() == 1;
397+
AnalysisMethod singleCallee = callees.iterator().next();
398+
devirtualizeInvoke(singleCallee, invoke);
378399
} else {
379-
/* Last resort, try to inject profiles optimistically. */
380400
TypeState receiverTypeState = null;
381-
if (invokeFlow.getTargetMethod().hasReceiver()) {
401+
if (hasReceiver) {
382402
if (methodFlow.isSaturated(analysis, invokeFlow)) {
383403
/*
384404
* For saturated invokes use all seen instantiated subtypes of target method
385-
* declaring class. In an open world this is incomplete as new types may be seen
386-
* later, but it is an optimistic approximation.
405+
* declaring class. Note if this analysis results are not complete this is
406+
* incomplete as new types may be seen later, but it is an optimistic
407+
* approximation.
387408
*/
388409
receiverTypeState = targetMethod.getDeclaringClass().getTypeFlow(analysis, false).getState();
389410
} else {
411+
assert receiverAnalysisResultsComplete;
390412
receiverTypeState = methodFlow.foldTypeFlow(analysis, invokeFlow.getReceiver());
391413
}
392414
}
393-
if (receiverTypeState != null && receiverTypeState.typesCount() <= MAX_TYPES_OPTIMISTIC_PROFILES) {
394-
assignInvokeProfiles(invoke, invokeFlow, callees, receiverTypeState, true);
395-
}
415+
assignInvokeProfiles(invoke, invokeFlow, callees, receiverTypeState, !receiverAnalysisResultsComplete);
396416
}
397417

398418
if (allowOptimizeReturnParameter && (strengthenGraphs.isClosedTypeWorld || callTarget.invokeKind().isDirect() || targetMethod.canBeStaticallyBound())) {
@@ -445,15 +465,6 @@ private void devirtualizeInvoke(AnalysisMethod singleCallee, Invoke invoke) {
445465
invoke.callTarget().setTargetMethod(singleCallee);
446466
}
447467

448-
/**
449-
* Maximum number of types seen in a {@link TypeState} for a virtual {@link Invoke} to consider
450-
* optimistic profile injection. See {@link #handleInvoke(Invoke, SimplifierTool)} for more
451-
* details. Note that this is a footprint consideration - we do not want to carry around
452-
* gargantuan {@link JavaTypeProfile} in {@link MethodCallTargetNode} that cannot be used
453-
* anyway.
454-
*/
455-
private static final int MAX_TYPES_OPTIMISTIC_PROFILES = 100;
456-
457468
private void assignInvokeProfiles(Invoke invoke, InvokeTypeFlow invokeFlow, Collection<AnalysisMethod> callees, TypeState receiverTypeState, boolean assumeNotRecorded) {
458469
/*
459470
* In an open type world we cannot trust the type state of the receiver for virtual calls as

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
import com.oracle.svm.core.config.ConfigurationValues;
5555
import com.oracle.svm.core.graal.RuntimeCompilation;
5656
import com.oracle.svm.core.heap.ReferenceHandler;
57+
import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport;
5758
import com.oracle.svm.core.jdk.VectorAPIEnabled;
5859
import com.oracle.svm.core.option.APIOption;
5960
import com.oracle.svm.core.option.APIOptionGroup;
@@ -1197,7 +1198,12 @@ public Boolean getValueOrDefault(UnmodifiableEconomicMap<OptionKey<?>, Object> v
11971198
if (values.containsKey(this)) {
11981199
return (Boolean) values.get(this);
11991200
}
1200-
return ImageInfo.isExecutable();
1201+
/*
1202+
* GR-70850: ImageInfo.isExecutable is inconsistent across layers. Since only an
1203+
* executable application layer is currently supported on Layered Images, the current
1204+
* solution is to enable this by default.
1205+
*/
1206+
return ImageInfo.isExecutable() || ImageLayerBuildingSupport.buildingImageLayer();
12011207
}
12021208

12031209
@Override
@@ -1342,7 +1348,12 @@ public Boolean getValueOrDefault(UnmodifiableEconomicMap<OptionKey<?>, Object> v
13421348
if (values.containsKey(this)) {
13431349
return (Boolean) values.get(this);
13441350
}
1345-
return ImageInfo.isExecutable();
1351+
/*
1352+
* GR-70850: ImageInfo.isExecutable is inconsistent across layers. Since only an
1353+
* executable application layer is currently supported on Layered Images, the current
1354+
* solution is to enable this by default.
1355+
*/
1356+
return ImageInfo.isExecutable() || ImageLayerBuildingSupport.buildingImageLayer();
13461357
}
13471358

13481359
@Override

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/HostedImageLayerBuildingSupport.java

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -232,28 +232,27 @@ public static void processLayerOptions(EconomicMap<OptionKey<?>, Object> values,
232232
SubstrateOptions.imageLayerCreateEnabledHandler.onOptionEnabled(values);
233233
}
234234
SubstrateOptions.UseContainerSupport.update(values, false);
235-
enableConservativeUnsafeAccess(values);
236-
/*
237-
* Module needs to be initialized in the application layer because of ALL_UNNAMED_MODULE
238-
* and EVERYONE_MODULE. This allows to have a consistent hash code for those modules at
239-
* run time and build time.
240-
*/
241-
SubstrateOptions.ApplicationLayerInitializedClasses.update(values, Module.class.getName());
242-
243-
setOptionIfHasNotBeenSet(values, SubstrateOptions.ConcealedOptions.RelativeCodePointers, true);
244235
}
245236

246237
if (isLayerUseOptionEnabled(hostedOptions)) {
247238
SubstrateOptions.ClosedTypeWorldHubLayout.update(values, false);
248239
if (SubstrateOptions.imageLayerEnabledHandler != null) {
249240
SubstrateOptions.imageLayerEnabledHandler.onOptionEnabled(values);
250241
}
242+
}
243+
244+
if (isLayerCreateOptionEnabled(hostedOptions) || isLayerUseOptionEnabled(hostedOptions)) {
251245
enableConservativeUnsafeAccess(values);
246+
247+
/*
248+
* Module needs to be initialized in the application layer because of ALL_UNNAMED_MODULE
249+
* and EVERYONE_MODULE. This allows to have a consistent hash code for those modules at
250+
* run time and build time.
251+
*/
252252
SubstrateOptions.ApplicationLayerInitializedClasses.update(values, Module.class.getName());
253+
253254
setOptionIfHasNotBeenSet(values, SubstrateOptions.ConcealedOptions.RelativeCodePointers, true);
254-
}
255255

256-
if (isLayerCreateOptionEnabled(hostedOptions) || isLayerUseOptionEnabled(hostedOptions)) {
257256
classLoaderSupport.initializePathDigests(digestIgnorePath);
258257
}
259258
}

0 commit comments

Comments
 (0)