Skip to content

Commit 8385334

Browse files
committed
[GR-55073] Fix open-type world analysis.
PullRequest: graal/18580
2 parents 67d1339 + 5f0d956 commit 8385334

File tree

27 files changed

+204
-71
lines changed

27 files changed

+204
-71
lines changed

compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/phases/PhaseSuite.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,12 @@
3535
import java.util.function.Supplier;
3636

3737
import jdk.graal.compiler.core.common.util.PhasePlan;
38+
import jdk.graal.compiler.debug.GraalError;
3839
import jdk.graal.compiler.debug.TTY;
3940
import jdk.graal.compiler.nodes.GraphState;
4041
import jdk.graal.compiler.nodes.StructuredGraph;
4142
import jdk.graal.compiler.options.Option;
4243
import jdk.graal.compiler.options.OptionKey;
43-
4444
import jdk.graal.compiler.serviceprovider.GraalServices;
4545

4646
/**
@@ -102,6 +102,21 @@ public final void addBeforeLast(BasePhase<? super C> phase) {
102102
last.add(phase);
103103
}
104104

105+
/** The new phase is inserted before the first position the target phase is found at. */
106+
public final void insertBeforePhase(Class<? extends BasePhase<? super C>> phaseClass, BasePhase<? super C> newPhase) {
107+
ListIterator<BasePhase<? super C>> position = findPhase(phaseClass);
108+
GraalError.guarantee(position != null, "Phase %s not found in suite %s.", phaseClass.getName(), this.getName());
109+
position.previous();
110+
position.add(newPhase);
111+
}
112+
113+
/** The new phase is inserted after the first position the target phase is found at. */
114+
public final void insertAfterPhase(Class<? extends BasePhase<? super C>> phaseClass, BasePhase<? super C> newPhase) {
115+
ListIterator<BasePhase<? super C>> position = findPhase(phaseClass);
116+
GraalError.guarantee(position != null, "Phase %s not found in suite %s.", phaseClass.getName(), this.getName());
117+
position.add(newPhase);
118+
}
119+
105120
/**
106121
* Insert a new phase at the specified index and shifts the element currently at that position
107122
* and any subsequent elements to the right.

compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/virtual/phases/ea/PartialEscapeClosure.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
import jdk.graal.compiler.nodes.ValuePhiNode;
7070
import jdk.graal.compiler.nodes.ValueProxyNode;
7171
import jdk.graal.compiler.nodes.VirtualState;
72+
import jdk.graal.compiler.nodes.WithExceptionNode;
7273
import jdk.graal.compiler.nodes.cfg.HIRBlock;
7374
import jdk.graal.compiler.nodes.java.AbstractNewObjectNode;
7475
import jdk.graal.compiler.nodes.java.AccessMonitorNode;
@@ -311,7 +312,11 @@ private boolean processVirtualizable(ValueNode node, FixedNode insertBefore, Blo
311312
if (state.hasObjectState(id)) {
312313
FixedNode materializeBefore = insertBefore;
313314
if (insertBefore == node && tool.isDeleted()) {
314-
materializeBefore = ((FixedWithNextNode) insertBefore).next();
315+
if (insertBefore instanceof FixedWithNextNode withNextNode) {
316+
materializeBefore = withNextNode.next();
317+
} else {
318+
materializeBefore = ((WithExceptionNode) insertBefore).next();
319+
}
315320
}
316321
ensureMaterialized(state, id, materializeBefore, effects, COUNTER_MATERIALIZATIONS);
317322
}

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/HostVM.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,10 @@ public boolean isFieldIncluded(BigBang bb, Field field) {
332332
return true;
333333
}
334334

335+
public boolean isClosedTypeWorld() {
336+
return true;
337+
}
338+
335339
/**
336340
* Helpers to determine what analysis actions should be taken for a given Multi-Method version.
337341
*/

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMethod.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -911,6 +911,10 @@ public ResolvedJavaMethod unwrapTowardsOriginalMethod() {
911911
}
912912

913913
public Executable getJavaMethod() {
914+
if (wrapped instanceof BaseLayerMethod) {
915+
/* We don't know the corresponding Java method. */
916+
return null;
917+
}
914918
return OriginalMethodProvider.getJavaMethod(this);
915919
}
916920

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

Lines changed: 65 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import java.util.stream.Stream;
3737

3838
import org.graalvm.collections.EconomicSet;
39+
import org.graalvm.nativeimage.AnnotationAccess;
3940

4041
import com.oracle.graal.pointsto.BigBang;
4142
import com.oracle.graal.pointsto.PointsToAnalysis;
@@ -53,6 +54,8 @@
5354
import com.oracle.graal.pointsto.meta.PointsToAnalysisMethod;
5455
import com.oracle.graal.pointsto.typestate.PrimitiveConstantTypeState;
5556
import com.oracle.graal.pointsto.typestate.TypeState;
57+
import com.oracle.graal.pointsto.util.AnalysisError;
58+
import com.oracle.svm.core.annotate.Delete;
5659
import com.oracle.svm.util.ImageBuildStatistics;
5760

5861
import jdk.graal.compiler.core.common.type.AbstractObjectStamp;
@@ -182,6 +185,9 @@ public boolean equals(Object obj) {
182185
private final StrengthenGraphsCounters beforeCounters;
183186
private final StrengthenGraphsCounters afterCounters;
184187

188+
/** Used to avoid aggressive optimizations for open type world analysis. */
189+
private final boolean isClosedTypeWorld;
190+
185191
public StrengthenGraphs(BigBang bb, Universe converter) {
186192
this.bb = bb;
187193
this.converter = converter;
@@ -197,6 +203,7 @@ public StrengthenGraphs(BigBang bb, Universe converter) {
197203
beforeCounters = null;
198204
afterCounters = null;
199205
}
206+
this.isClosedTypeWorld = converter.hostVM().isClosedTypeWorld();
200207
}
201208

202209
private static void reportNeverNullInstanceFields(BigBang bb) {
@@ -291,7 +298,7 @@ public final void applyResults(AnalysisMethod method) {
291298
protected abstract boolean simplifyDelegate(Node n, SimplifierTool tool);
292299

293300
/* Wrapper to clearly identify phase in IGV graph dumps. */
294-
class AnalysisStrengthenGraphsPhase extends BasePhase<CoreProviders> {
301+
public class AnalysisStrengthenGraphsPhase extends BasePhase<CoreProviders> {
295302
final CanonicalizerPhase phase;
296303

297304
AnalysisStrengthenGraphsPhase(AnalysisMethod method, StructuredGraph graph) {
@@ -554,7 +561,8 @@ private void handleInvoke(Invoke invoke, SimplifierTool tool) {
554561
FixedNode node = invoke.asFixedNode();
555562
MethodCallTargetNode callTarget = (MethodCallTargetNode) invoke.callTarget();
556563

557-
if (callTarget.invokeKind().isDirect() && !((AnalysisMethod) callTarget.targetMethod()).isSimplyImplementationInvoked()) {
564+
AnalysisMethod targetMethod = (AnalysisMethod) callTarget.targetMethod();
565+
if (callTarget.invokeKind().isDirect() && !targetMethod.isSimplyImplementationInvoked()) {
558566
/*
559567
* This is a direct call to a method that the static analysis did not see as
560568
* invoked. This can happen when the receiver is always null. In most cases, the
@@ -619,27 +627,66 @@ private void handleInvoke(Invoke invoke, SimplifierTool tool) {
619627
}
620628
}
621629

622-
if (callees.size() == 1) {
630+
if (callTarget.invokeKind().isDirect()) {
631+
/*
632+
* Note: A direct invoke doesn't necessarily imply that the analysis should have
633+
* discovered a single callee. When dealing with interfaces it is in fact possible
634+
* that the Graal stamps are more accurate than the analysis results. So an
635+
* interface call may have already been optimized to a special call by stamp
636+
* strengthening of the receiver object, hence the invoke kind is direct, whereas
637+
* the points-to analysis inaccurately concluded there can be more than one callee.
638+
*
639+
* Below we just check that if there is a direct invoke *and* the analysis
640+
* discovered a single callee, then the callee should match the target method.
641+
*/
642+
if (callees.size() == 1) {
643+
AnalysisMethod singleCallee = callees.iterator().next();
644+
assert targetMethod.equals(singleCallee) : "Direct invoke target mismatch: " + targetMethod + " != " + singleCallee + ". Called from " + graph.method().format("%H.%n");
645+
}
646+
} else if (AnnotationAccess.isAnnotationPresent(targetMethod, Delete.class)) {
647+
/* We de-virtualize invokes to deleted methods since the callee must be unique. */
648+
AnalysisError.guarantee(callees.size() == 1, "@Delete methods should have a single callee.");
623649
AnalysisMethod singleCallee = callees.iterator().next();
624-
if (callTarget.invokeKind().isDirect()) {
625-
assert callTarget.targetMethod().equals(singleCallee) : "Direct invoke target mismatch: " + callTarget.targetMethod() + " != " + singleCallee;
626-
} else {
650+
devirtualizeInvoke(singleCallee, invoke);
651+
} else if (targetMethod.canBeStaticallyBound() || isClosedTypeWorld) {
652+
/*
653+
* We only de-virtualize invokes if we run a closed type world analysis or the
654+
* target method can be trivially statically bound.
655+
*/
656+
if (callees.size() == 1) {
657+
AnalysisMethod singleCallee = callees.iterator().next();
627658
devirtualizeInvoke(singleCallee, invoke);
628-
}
659+
} else {
660+
TypeState receiverTypeState = null;
661+
/* If the receiver flow is saturated, its exact type state does not matter. */
662+
if (invokeFlow.getTargetMethod().hasReceiver() && !methodFlow.isSaturated((PointsToAnalysis) bb, invokeFlow.getReceiver())) {
663+
receiverTypeState = methodFlow.foldTypeFlow((PointsToAnalysis) bb, invokeFlow.getReceiver());
664+
}
629665

630-
} else {
631-
TypeState receiverTypeState = null;
632-
/* If the receiver flow is saturated, its exact type state does not matter. */
633-
if (invokeFlow.getTargetMethod().hasReceiver() && !methodFlow.isSaturated((PointsToAnalysis) bb, invokeFlow.getReceiver())) {
634-
receiverTypeState = methodFlow.foldTypeFlow((PointsToAnalysis) bb, invokeFlow.getReceiver());
635-
}
666+
/*
667+
* In an open type world we cannot trust the type state of the receiver for
668+
* virtual calls as new subtypes could be added later.
669+
*
670+
* Note: MethodFlowsGraph.saturateAllParameters() does saturate the receiver in
671+
* many cases, so the check above would also lead to a null typeProfile, but we
672+
* cannot guarantee that we cover all cases.
673+
*/
674+
JavaTypeProfile typeProfile = makeTypeProfile(receiverTypeState);
675+
/*
676+
* In a closed type world analysis the method profile of an invoke is complete
677+
* and contains all the callees reachable at that invocation location. Even if
678+
* that invoke is saturated it is still correct as it contains all the reachable
679+
* implementations of the target method. However, in an open type world the
680+
* method profile of an invoke, saturated or not, is incomplete, as there can be
681+
* implementations that we haven't yet seen.
682+
*/
683+
JavaMethodProfile methodProfile = makeMethodProfile(callees);
636684

637-
JavaTypeProfile typeProfile = makeTypeProfile(receiverTypeState);
638-
JavaMethodProfile methodProfile = makeMethodProfile(callees);
639-
assert typeProfile == null || typeProfile.getTypes().length > 1 : "Should devirtualize with typeProfile=" + typeProfile + " and methodProfile=" + methodProfile;
640-
assert methodProfile == null || methodProfile.getMethods().length > 1 : "Should devirtualize with typeProfile=" + typeProfile + " and methodProfile=" + methodProfile;
685+
assert typeProfile == null || typeProfile.getTypes().length > 1 : "Should devirtualize with typeProfile=" + typeProfile + " and methodProfile=" + methodProfile;
686+
assert methodProfile == null || methodProfile.getMethods().length > 1 : "Should devirtualize with typeProfile=" + typeProfile + " and methodProfile=" + methodProfile;
641687

642-
setInvokeProfiles(invoke, typeProfile, methodProfile);
688+
setInvokeProfiles(invoke, typeProfile, methodProfile);
689+
}
643690
}
644691

645692
if (allowOptimizeReturnParameter) {

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1264,10 +1264,23 @@ public static boolean includeAll() {
12641264
public static final HostedOptionKey<Boolean> ForeignAPISupport = new HostedOptionKey<>(false);
12651265

12661266
@Option(help = "Assume new types cannot be added after analysis", type = OptionType.Expert) //
1267-
public static final HostedOptionKey<Boolean> ClosedTypeWorld = new HostedOptionKey<>(true);
1267+
public static final HostedOptionKey<Boolean> ClosedTypeWorld = new HostedOptionKey<>(true) {
1268+
@Override
1269+
protected void onValueUpdate(EconomicMap<OptionKey<?>, Object> values, Boolean oldValue, Boolean newValue) {
1270+
ClosedTypeWorldHubLayout.update(values, newValue);
1271+
}
1272+
};
1273+
1274+
@Option(help = "Use the closed type world dynamic hub representation. This is only allowed when the option ClosedTypeWorld is also set to true.", type = OptionType.Expert) //
1275+
public static final HostedOptionKey<Boolean> ClosedTypeWorldHubLayout = new HostedOptionKey<>(true);
1276+
1277+
@Fold
1278+
public static boolean useClosedTypeWorldHubLayout() {
1279+
return ClosedTypeWorldHubLayout.getValue();
1280+
}
12681281

12691282
@Fold
1270-
public static boolean closedTypeWorld() {
1283+
public static boolean useClosedTypeWorld() {
12711284
return ClosedTypeWorld.getValue();
12721285
}
12731286

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/meta/KnownOffsets.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ public int getVTableOffset(int vTableIndex, boolean fromDynamicHubStart) {
125125
}
126126

127127
public int getTypeIDSlotsOffset() {
128-
assert isFullyInitialized() && SubstrateOptions.closedTypeWorld();
128+
assert isFullyInitialized() && SubstrateOptions.useClosedTypeWorldHubLayout();
129129
return typeIDSlotsOffset;
130130
}
131131

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/meta/SubstrateBasicLoweringProvider.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ private void lowerLoadMethodNode(LoadMethodNode loadMethodNode, LoweringTool too
162162
SharedMethod method = (SharedMethod) loadMethodNode.getMethod();
163163
ValueNode hub = loadMethodNode.getHub();
164164

165-
if (SubstrateOptions.closedTypeWorld()) {
165+
if (SubstrateOptions.useClosedTypeWorldHubLayout()) {
166166

167167
int vtableEntryOffset = knownOffsets.getVTableOffset(method.getVTableIndex(), true);
168168
assert vtableEntryOffset > 0;

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/NonSnippetLowerings.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -293,11 +293,13 @@ public static class InvokeLowering implements NodeLoweringProvider<FixedNode> {
293293
protected final RuntimeConfiguration runtimeConfig;
294294
protected final boolean verifyTypes;
295295
protected final KnownOffsets knownOffsets;
296+
private final boolean isClosedTypeWorld;
296297

297298
public InvokeLowering(RuntimeConfiguration runtimeConfig, boolean verifyTypes, KnownOffsets knownOffsets) {
298299
this.runtimeConfig = runtimeConfig;
299300
this.verifyTypes = verifyTypes;
300301
this.knownOffsets = knownOffsets;
302+
isClosedTypeWorld = SubstrateOptions.useClosedTypeWorld();
301303
}
302304

303305
@Override
@@ -366,14 +368,15 @@ public void lower(FixedNode node, LoweringTool tool) {
366368
}
367369

368370
CallTargetNode loweredCallTarget;
369-
if (invokeKind.isDirect() || implementations.length == 1) {
371+
if (invokeKind.isDirect() || (implementations.length == 1 && (isClosedTypeWorld || method.canBeStaticallyBound()))) {
370372
SharedMethod targetMethod = method;
371373
if (!invokeKind.isDirect()) {
372374
/*
373375
* We only have one possible implementation for an indirect call, so we can
374376
* emit a direct call to the unique implementation.
375377
*/
376378
targetMethod = implementations[0];
379+
assert targetMethod != null : "Expecting a unique callee for target method " + method;
377380
}
378381

379382
if (SubstrateUtil.HOSTED && targetMethod.forceIndirectCall()) {
@@ -421,7 +424,7 @@ public void lower(FixedNode node, LoweringTool tool) {
421424
address, parameters.toArray(new ValueNode[parameters.size()]), callTarget.returnStamp(), signature, targetMethod, callType, invokeKind));
422425
graph.addBeforeFixed(node, codeStart);
423426
}
424-
} else if (implementations.length == 0) {
427+
} else if (implementations.length == 0 && isClosedTypeWorld) {
425428
/*
426429
* We are calling an abstract method with no implementation, i.e., the
427430
* closed-world analysis showed that there is no concrete receiver ever
@@ -437,7 +440,7 @@ public void lower(FixedNode node, LoweringTool tool) {
437440
LoadHubNode hub = graph.unique(new LoadHubNode(runtimeConfig.getProviders().getStampProvider(), graph.addOrUnique(PiNode.create(receiver, nullCheck))));
438441
nodesToLower.add(hub);
439442

440-
if (SubstrateOptions.closedTypeWorld()) {
443+
if (SubstrateOptions.useClosedTypeWorldHubLayout()) {
441444
int vtableEntryOffset = knownOffsets.getVTableOffset(method.getVTableIndex(), true);
442445

443446
AddressNode address = graph.unique(new OffsetAddressNode(hub, ConstantNode.forIntegerKind(ConfigurationValues.getWordKind(), vtableEntryOffset, graph)));

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNIAccessibleMethod.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ CodePointer getCallWrapperAddress() {
128128
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
129129
CodePointer getJavaCallAddress(Object instance, boolean nonVirtual) {
130130
if (!nonVirtual) {
131-
if (SubstrateOptions.closedTypeWorld()) {
131+
if (SubstrateOptions.useClosedTypeWorldHubLayout()) {
132132
assert vtableOffset != JNIAccessibleMethod.VTABLE_OFFSET_NOT_YET_COMPUTED;
133133
if (vtableOffset != JNIAccessibleMethod.STATICALLY_BOUND_METHOD) {
134134
return BarrieredAccess.readWord(instance.getClass(), vtableOffset, NamedLocationIdentity.FINAL_LOCATION);

0 commit comments

Comments
 (0)