Skip to content

Commit 5f0d956

Browse files
committed
Fix open type world analysis.
1 parent c03216d commit 5f0d956

File tree

10 files changed

+137
-32
lines changed

10 files changed

+137
-32
lines changed

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: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1264,14 +1264,19 @@ 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+
};
12681273

1269-
@Option(help = "Use the closed type world dynamichub representation. This is only allowed when the option ClosedTypeWorld is also set to true.", type = OptionType.Expert) //
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) //
12701275
public static final HostedOptionKey<Boolean> ClosedTypeWorldHubLayout = new HostedOptionKey<>(true);
12711276

12721277
@Fold
12731278
public static boolean useClosedTypeWorldHubLayout() {
1274-
return useClosedTypeWorld() && ClosedTypeWorldHubLayout.getValue();
1279+
return ClosedTypeWorldHubLayout.getValue();
12751280
}
12761281

12771282
@Fold

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

Lines changed: 5 additions & 2 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

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ public enum UsageKind {
201201
private Set<Field> excludedFields;
202202

203203
private final Boolean optionAllowUnsafeAllocationOfAllInstantiatedTypes = SubstrateOptions.AllowUnsafeAllocationOfAllInstantiatedTypes.getValue();
204+
private final boolean isClosedTypeWorld = SubstrateOptions.useClosedTypeWorld();
204205

205206
@SuppressWarnings("this-escape")
206207
public SVMHost(OptionValues options, ImageClassLoader loader, ClassInitializationSupport classInitializationSupport, AnnotationSubstitutionProcessor annotationSubstitutions,
@@ -936,6 +937,11 @@ public boolean isFieldIncluded(BigBang bb, Field field) {
936937
return super.isFieldIncluded(bb, field);
937938
}
938939

940+
@Override
941+
public boolean isClosedTypeWorld() {
942+
return isClosedTypeWorld;
943+
}
944+
939945
private final List<BiPredicate<AnalysisMethod, AnalysisMethod>> neverInlineTrivialHandlers = new CopyOnWriteArrayList<>();
940946

941947
public void registerNeverInlineTrivialHandler(BiPredicate<AnalysisMethod, AnalysisMethod> handler) {

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedInstanceClass.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@
2626

2727
import com.oracle.graal.pointsto.meta.AnalysisType;
2828

29+
import jdk.vm.ci.meta.Assumptions.AssumptionResult;
2930
import jdk.vm.ci.meta.JavaKind;
3031
import jdk.vm.ci.meta.ResolvedJavaField;
32+
import jdk.vm.ci.meta.ResolvedJavaMethod;
3133

3234
public class HostedInstanceClass extends HostedClass {
3335

@@ -138,4 +140,16 @@ public void setIdentityHashOffset(int offset) {
138140
assert offset > 0;
139141
this.identityHashOffset = offset;
140142
}
143+
144+
@Override
145+
public AssumptionResult<ResolvedJavaMethod> findUniqueConcreteMethod(ResolvedJavaMethod m) {
146+
if (m.canBeStaticallyBound() || universe.hostVM().isClosedTypeWorld()) {
147+
return super.findUniqueConcreteMethod(m);
148+
}
149+
/*
150+
* With an open type world analysis we cannot make assumptions for methods that cannot be
151+
* trivially statically bound.
152+
*/
153+
return null;
154+
}
141155
}

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedMethod.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,10 @@ public final class HostedMethod extends HostedElement implements SharedMethod, W
111111
/**
112112
* All concrete methods that can actually be called when calling this method. This includes all
113113
* overridden methods in subclasses, as well as this method if it is non-abstract.
114+
* <p>
115+
* With an open type world analysis the list of implementations is incomplete, i.e., no
116+
* aggressive optimizations should be performed based on the contents of this list as one must
117+
* assume that additional implementations can be discovered later.
114118
*/
115119
HostedMethod[] implementations;
116120

@@ -340,7 +344,7 @@ public boolean hasVTableIndex() {
340344

341345
@Override
342346
public int getVTableIndex() {
343-
assert vtableIndex != -1;
347+
assert vtableIndex != -1 : "Missing vtable index for method " + this.format("%H.%n(%p)");
344348
return vtableIndex;
345349
}
346350

@@ -451,7 +455,14 @@ public boolean isConstructor() {
451455

452456
@Override
453457
public boolean canBeStaticallyBound() {
454-
return implementations.length == 1 && implementations[0].equals(this);
458+
if (holder.universe.hostVM().isClosedTypeWorld()) {
459+
return implementations.length == 1 && implementations[0].equals(this);
460+
}
461+
/*
462+
* In open type world analysis we cannot make assumptions based on discovered
463+
* implementations.
464+
*/
465+
return wrapped.canBeStaticallyBound();
455466
}
456467

457468
@Override

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/TypeCheckBuilder.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,10 @@ public final class TypeCheckBuilder {
153153
VMError.guarantee(result != 0, "Unexpected match of types %s %s", o1, o2);
154154
return result;
155155
};
156+
private final boolean isClosedTypeWorld;
156157

157158
public static int buildTypeMetadata(HostedUniverse hUniverse, Collection<HostedType> types, HostedType objectType, HostedType cloneableType, HostedType serializableType) {
158-
var builder = new TypeCheckBuilder(types, objectType, cloneableType, serializableType);
159+
var builder = new TypeCheckBuilder(types, objectType, cloneableType, serializableType, hUniverse.hostVM().isClosedTypeWorld());
159160
if (SubstrateOptions.useClosedTypeWorldHubLayout()) {
160161
builder.buildTypeInformation(hUniverse, 0);
161162
builder.calculateClosedTypeWorldTypeMetadata();
@@ -169,7 +170,7 @@ public static int buildTypeMetadata(HostedUniverse hUniverse, Collection<HostedT
169170
}
170171
}
171172

172-
private TypeCheckBuilder(Collection<HostedType> types, HostedType objectType, HostedType cloneableType, HostedType serializableType) {
173+
private TypeCheckBuilder(Collection<HostedType> types, HostedType objectType, HostedType cloneableType, HostedType serializableType, boolean isClosedTypeWorld) {
173174
this.allTypes = types;
174175
this.objectType = objectType;
175176
this.cloneableType = cloneableType;
@@ -187,6 +188,7 @@ private TypeCheckBuilder(Collection<HostedType> types, HostedType objectType, Ho
187188
allIncludedRoots = allIncludedTypes.stream().filter(t -> !hasParent.contains(t)).toList();
188189

189190
heightOrderedTypes = generateHeightOrder(allIncludedRoots, subtypeMap);
191+
this.isClosedTypeWorld = isClosedTypeWorld;
190192
}
191193

192194
private int getNumTypeCheckSlots() {
@@ -472,6 +474,16 @@ public void buildTypeInformation(HostedUniverse hUniverse, int startingTypeID) {
472474
for (int i = heightOrderedTypes.size() - 1; i >= 0; i--) {
473475
HostedType type = heightOrderedTypes.get(i);
474476

477+
if (!type.isLeaf() && !isClosedTypeWorld) {
478+
/*
479+
* With an open type world analysis we have to assume that a non-final type can have
480+
* multiple instantiated subtypes.
481+
*/
482+
type.strengthenStampType = type;
483+
type.uniqueConcreteImplementation = null;
484+
continue;
485+
}
486+
475487
HostedType subtypeStampType = null;
476488
for (HostedType child : subtypeMap.get(type)) {
477489
if (child.strengthenStampType != null) {

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/VTableBuilder.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939
import com.oracle.svm.core.InvalidMethodPointerHandler;
4040
import com.oracle.svm.core.SubstrateOptions;
4141
import com.oracle.svm.core.SubstrateUtil;
42-
import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport;
4342
import com.oracle.svm.hosted.OpenTypeWorldFeature;
4443

4544
import jdk.graal.compiler.debug.Assertions;
@@ -119,12 +118,12 @@ private List<HostedMethod> generateITable(HostedType type) {
119118

120119
private List<HostedMethod> generateDispatchTable(HostedType type, int startingIndex) {
121120
Predicate<HostedMethod> includeMethod;
122-
if (ImageLayerBuildingSupport.buildingImageLayer()) {
123-
// include all methods
124-
includeMethod = m -> true;
125-
} else {
121+
if (hUniverse.hostVM().isClosedTypeWorld()) {
126122
// include only methods which will be indirect calls
127123
includeMethod = m -> m.implementations.length > 1 || m.wrapped.isVirtualRootMethod();
124+
} else {
125+
// include all methods
126+
includeMethod = m -> true;
128127
}
129128
var table = type.getWrapped().getOpenTypeWorldDispatchTableMethods().stream().map(hUniverse::lookup).filter(includeMethod).sorted(HostedUniverse.METHOD_COMPARATOR).toList();
130129

0 commit comments

Comments
 (0)