3636import java .util .stream .Stream ;
3737
3838import org .graalvm .collections .EconomicSet ;
39+ import org .graalvm .nativeimage .AnnotationAccess ;
3940
4041import com .oracle .graal .pointsto .BigBang ;
4142import com .oracle .graal .pointsto .PointsToAnalysis ;
5354import com .oracle .graal .pointsto .meta .PointsToAnalysisMethod ;
5455import com .oracle .graal .pointsto .typestate .PrimitiveConstantTypeState ;
5556import com .oracle .graal .pointsto .typestate .TypeState ;
57+ import com .oracle .graal .pointsto .util .AnalysisError ;
58+ import com .oracle .svm .core .annotate .Delete ;
5659import com .oracle .svm .util .ImageBuildStatistics ;
5760
5861import 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 ) {
0 commit comments