Skip to content

Commit c0ebf7d

Browse files
committed
[GR-58492] Layered NI use open world analysis results as virtual invoke profiles.
PullRequest: graal/18894
2 parents 4aec9c9 + 1c4e16d commit c0ebf7d

File tree

1 file changed

+76
-42
lines changed

1 file changed

+76
-42
lines changed

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

Lines changed: 76 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -131,11 +131,11 @@
131131
/**
132132
* This class applies static analysis results directly to the {@link StructuredGraph Graal IR} used
133133
* to build the type flow graph.
134-
*
134+
*
135135
* It uses a {@link CustomSimplification} for the {@link CanonicalizerPhase}, because that provides
136136
* all the framework for iterative stamp propagation and adding/removing control flow nodes while
137137
* processing the graph.
138-
*
138+
*
139139
* From the single-method view that the compiler has when later compiling the graph, static analysis
140140
* results appear "out of thin air": At some random point in the graph, we suddenly have a more
141141
* precise type (= stamp) for a value. Since many nodes are floating, and even currently fixed nodes
@@ -275,17 +275,17 @@ public final void applyResults(AnalysisMethod method) {
275275
* Returns a type that can replace the original type in stamps as an exact type. When the
276276
* returned type is the original type itself, the original type has no subtype and can be used
277277
* as an exact type.
278-
*
278+
*
279279
* Returns null if there is no single implementor type.
280280
*/
281281
protected abstract AnalysisType getSingleImplementorType(AnalysisType originalType);
282282

283283
/*
284284
* Returns a type that can replace the original type in stamps.
285-
*
285+
*
286286
* Returns null if the original type has no assignable type that is instantiated, i.e., the code
287287
* using the type is unreachable.
288-
*
288+
*
289289
* Returns the original type itself if there is no optimization potential, i.e., if the original
290290
* type itself is instantiated or has more than one instantiated direct subtype.
291291
*/
@@ -388,7 +388,7 @@ class StrengthenSimplifier implements CustomSimplification {
388388
this.toTargetFunction = bb.getHostVM().getStrengthenGraphsToTargetFunction(method.getMultiMethodKey());
389389
}
390390

391-
private TypeFlow<?> getNodeFlow(Node node) {
391+
protected TypeFlow<?> getNodeFlow(Node node) {
392392
return nodeFlows == null || nodeFlows.isNew(node) ? null : nodeFlows.get(node);
393393
}
394394

@@ -563,6 +563,7 @@ private AnalysisType asConstantNonReachableType(ValueNode value, CoreProviders p
563563
}
564564

565565
private void handleInvoke(Invoke invoke, SimplifierTool tool) {
566+
566567
FixedNode node = invoke.asFixedNode();
567568
MethodCallTargetNode callTarget = (MethodCallTargetNode) invoke.callTarget();
568569

@@ -668,32 +669,25 @@ private void handleInvoke(Invoke invoke, SimplifierTool tool) {
668669
if (invokeFlow.getTargetMethod().hasReceiver() && !methodFlow.isSaturated((PointsToAnalysis) bb, invokeFlow.getReceiver())) {
669670
receiverTypeState = methodFlow.foldTypeFlow((PointsToAnalysis) bb, invokeFlow.getReceiver());
670671
}
671-
672-
/*
673-
* In an open type world we cannot trust the type state of the receiver for
674-
* virtual calls as new subtypes could be added later.
675-
*
676-
* Note: MethodFlowsGraph.saturateAllParameters() does saturate the receiver in
677-
* many cases, so the check above would also lead to a null typeProfile, but we
678-
* cannot guarantee that we cover all cases.
679-
*/
680-
JavaTypeProfile typeProfile = makeTypeProfile(receiverTypeState);
681-
/*
682-
* In a closed type world analysis the method profile of an invoke is complete
683-
* and contains all the callees reachable at that invocation location. Even if
684-
* that invoke is saturated it is still correct as it contains all the reachable
685-
* implementations of the target method. However, in an open type world the
686-
* method profile of an invoke, saturated or not, is incomplete, as there can be
687-
* implementations that we haven't yet seen.
688-
*/
689-
JavaMethodProfile methodProfile = makeMethodProfile(callees);
690-
691-
assert typeProfile == null || typeProfile.getTypes().length > 1 : "Should devirtualize with typeProfile=" + typeProfile + " and methodProfile=" + methodProfile + " and callees" +
692-
callees + " invoke " + invokeFlow + " " + invokeFlow.getReceiver() + " in method " + getQualifiedName(graph);
693-
assert methodProfile == null || methodProfile.getMethods().length > 1 : "Should devirtualize with typeProfile=" + typeProfile + " and methodProfile=" + methodProfile +
694-
" and callees" + callees + " invoke " + invokeFlow + " " + invokeFlow.getReceiver() + " in method " + getQualifiedName(graph);
695-
696-
setInvokeProfiles(invoke, typeProfile, methodProfile);
672+
assignInvokeProfiles(invoke, invokeFlow, callees, receiverTypeState, false);
673+
}
674+
} else {
675+
/* Last resort, try to inject profiles optimistically. */
676+
TypeState receiverTypeState = null;
677+
if (invokeFlow.getTargetMethod().hasReceiver()) {
678+
if (invokeFlow.isSaturated()) {
679+
/*
680+
* For saturated invokes use all seen instantiated subtypes of target method
681+
* declaring class. In an open world this is incomplete as new types may be
682+
* seen later, but it is an optimistic approximation.
683+
*/
684+
receiverTypeState = targetMethod.getDeclaringClass().getTypeFlow(bb, false).getState();
685+
} else {
686+
receiverTypeState = methodFlow.foldTypeFlow((PointsToAnalysis) bb, invokeFlow.getReceiver());
687+
}
688+
}
689+
if (receiverTypeState != null && receiverTypeState.typesCount() <= MAX_TYPES_OPTIMISTIC_PROFILES) {
690+
assignInvokeProfiles(invoke, invokeFlow, callees, receiverTypeState, true);
697691
}
698692
}
699693

@@ -718,6 +712,45 @@ private void handleInvoke(Invoke invoke, SimplifierTool tool) {
718712
updateStampUsingPiNode(node, newStampOrConstant, anchorPointAfterInvoke, tool);
719713
}
720714

715+
/**
716+
* Maximum number of types seen in a {@link TypeState} for a virtual {@link Invoke} to
717+
* consider optimistic profile injection. See {@link #handleInvoke(Invoke, SimplifierTool)}
718+
* for more details. Note that this is a footprint consideration - we do not want to carry
719+
* around gargantuan {@link JavaTypeProfile} in {@link MethodCallTargetNode} that cannot be
720+
* used anyway.
721+
*/
722+
private static final int MAX_TYPES_OPTIMISTIC_PROFILES = 100;
723+
724+
private void assignInvokeProfiles(Invoke invoke, InvokeTypeFlow invokeFlow, Collection<AnalysisMethod> callees, TypeState receiverTypeState, boolean assumeNotRecorded) {
725+
/*
726+
* In an open type world we cannot trust the type state of the receiver for virtual
727+
* calls as new subtypes could be added later.
728+
*
729+
* Note: assumeNotRecorded specifies if profiles are injected for a closed or open
730+
* world. For a closed world with precise analysis results we never have a
731+
* notRecordedProbabiltiy in any profile. For the open world we always assume that there
732+
* is a not recorded probability in the profile. Such a not recorded probability will be
733+
* injected if assumeNotRecorded==true.
734+
*/
735+
JavaTypeProfile typeProfile = makeTypeProfile(receiverTypeState, assumeNotRecorded);
736+
/*
737+
* In a closed type world analysis the method profile of an invoke is complete and
738+
* contains all the callees reachable at that invocation location. Even if that invoke
739+
* is saturated it is still correct as it contains all the reachable implementations of
740+
* the target method. However, in an open type world the method profile of an invoke,
741+
* saturated or not, is incomplete, as there can be implementations that we haven't yet
742+
* seen.
743+
*/
744+
JavaMethodProfile methodProfile = makeMethodProfile(callees, assumeNotRecorded);
745+
746+
assert typeProfile == null || typeProfile.getTypes().length > 1 || assumeNotRecorded : "Should devirtualize with typeProfile=" + typeProfile + " and methodProfile=" + methodProfile +
747+
" and callees" + callees + " invoke " + invokeFlow + " " + invokeFlow.getReceiver() + " in method " + getQualifiedName(graph);
748+
assert methodProfile == null || methodProfile.getMethods().length > 1 || assumeNotRecorded : "Should devirtualize with typeProfile=" + typeProfile + " and methodProfile=" + methodProfile +
749+
" and callees" + callees + " invoke " + invokeFlow + " " + invokeFlow.getReceiver() + " in method " + getQualifiedName(graph);
750+
751+
setInvokeProfiles(invoke, typeProfile, methodProfile);
752+
}
753+
721754
/**
722755
* If all possible callees return the same parameter, then we can replace the invoke with
723756
* that parameter at all usages. This is the same that would happen when the callees are
@@ -1101,17 +1134,17 @@ private static String getQualifiedName(StructuredGraph graph) {
11011134
return ((AnalysisMethod) graph.method()).getQualifiedName();
11021135
}
11031136

1104-
protected JavaTypeProfile makeTypeProfile(TypeState typeState) {
1137+
protected JavaTypeProfile makeTypeProfile(TypeState typeState, boolean injectNotRecordedProbability) {
11051138
if (typeState == null || analysisSizeCutoff != -1 && typeState.typesCount() > analysisSizeCutoff) {
11061139
return null;
11071140
}
1108-
var created = createTypeProfile(typeState);
1141+
var created = createTypeProfile(typeState, injectNotRecordedProbability);
11091142
var existing = cachedTypeProfiles.putIfAbsent(created, created);
11101143
return existing != null ? existing : created;
11111144
}
11121145

1113-
private JavaTypeProfile createTypeProfile(TypeState typeState) {
1114-
double probability = 1d / typeState.typesCount();
1146+
private JavaTypeProfile createTypeProfile(TypeState typeState, boolean injectNotRecordedProbability) {
1147+
double probability = 1d / (typeState.typesCount() + (injectNotRecordedProbability ? 1 : 0));
11151148

11161149
Stream<? extends ResolvedJavaType> stream = typeState.typesStream(bb);
11171150
if (converter != null) {
@@ -1121,31 +1154,32 @@ private JavaTypeProfile createTypeProfile(TypeState typeState) {
11211154
.map(type -> new JavaTypeProfile.ProfiledType(type, probability))
11221155
.toArray(JavaTypeProfile.ProfiledType[]::new);
11231156

1124-
return new JavaTypeProfile(TriState.get(typeState.canBeNull()), 0, pitems);
1157+
return new JavaTypeProfile(TriState.get(typeState.canBeNull()), injectNotRecordedProbability ? probability : 0, pitems);
11251158
}
11261159

1127-
protected JavaMethodProfile makeMethodProfile(Collection<AnalysisMethod> callees) {
1160+
protected JavaMethodProfile makeMethodProfile(Collection<AnalysisMethod> callees, boolean injectNotRecordedProbability) {
11281161
if (analysisSizeCutoff != -1 && callees.size() > analysisSizeCutoff) {
11291162
return null;
11301163
}
1131-
var created = createMethodProfile(callees);
1164+
var created = createMethodProfile(callees, injectNotRecordedProbability);
11321165
var existing = cachedMethodProfiles.putIfAbsent(created, created);
11331166
return existing != null ? existing.profile : created.profile;
11341167
}
11351168

1136-
private CachedJavaMethodProfile createMethodProfile(Collection<AnalysisMethod> callees) {
1169+
private CachedJavaMethodProfile createMethodProfile(Collection<AnalysisMethod> callees, boolean injectNotRecordedProbability) {
11371170
JavaMethodProfile.ProfiledMethod[] pitems = new JavaMethodProfile.ProfiledMethod[callees.size()];
11381171
int hashCode = 0;
1139-
double probability = 1d / pitems.length;
1172+
double probability = 1d / (pitems.length + (injectNotRecordedProbability ? 1 : 0));
11401173

11411174
int idx = 0;
11421175
for (AnalysisMethod aMethod : callees) {
11431176
ResolvedJavaMethod convertedMethod = converter == null ? aMethod : converter.lookup(aMethod);
11441177
pitems[idx++] = new JavaMethodProfile.ProfiledMethod(convertedMethod, probability);
11451178
hashCode = hashCode * 31 + convertedMethod.hashCode();
11461179
}
1147-
return new CachedJavaMethodProfile(new JavaMethodProfile(0, pitems), hashCode);
1180+
return new CachedJavaMethodProfile(new JavaMethodProfile(injectNotRecordedProbability ? probability : 0, pitems), hashCode);
11481181
}
1182+
11491183
}
11501184

11511185
/**

0 commit comments

Comments
 (0)