Introduce heuristic for spurious call graph edges

Bug: 133248798
Change-Id: I9c7b272f9c4fa65008892bb5ee92b27f4b708f48
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
index d4ed94b..ca9dae4 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -48,7 +48,11 @@
     }
 
     public void addCallerConcurrently(Node caller) {
-      if (caller != this) {
+      addCallerConcurrently(caller, false);
+    }
+
+    public void addCallerConcurrently(Node caller, boolean likelySpuriousCallEdge) {
+      if (caller != this && !likelySpuriousCallEdge) {
         synchronized (callers) {
           callers.add(caller);
           numberOfCallSites++;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilder.java
index 4606132..ad94ea4 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilder.java
@@ -115,8 +115,8 @@
 
     private void addClassInitializerTarget(DexClass clazz) {
       assert clazz != null;
-      if (clazz.hasClassInitializer() && clazz.isProgramClass()) {
-        addTarget(clazz.getClassInitializer());
+      if (clazz.isProgramClass() && clazz.hasClassInitializer()) {
+        addTarget(clazz.getClassInitializer(), false);
       }
     }
 
@@ -128,10 +128,10 @@
       }
     }
 
-    private void addTarget(DexEncodedMethod callee) {
+    private void addTarget(DexEncodedMethod callee, boolean likelySpuriousCallEdge) {
       if (!callee.accessFlags.isAbstract()) {
         assert callee.isProgramMethod(appView);
-        getOrCreateNode(callee).addCallerConcurrently(caller);
+        getOrCreateNode(callee).addCallerConcurrently(caller, likelySpuriousCallEdge);
       }
     }
 
@@ -158,7 +158,7 @@
             if (type == Type.STATIC) {
               addClassInitializerTarget(clazz);
             }
-            addTarget(singleTarget);
+            addTarget(singleTarget, false);
           }
         }
       }
@@ -187,9 +187,11 @@
                       ? appView.appInfo().lookupInterfaceTargets(method)
                       : appView.appInfo().lookupVirtualTargets(method));
       if (possibleTargets != null) {
+        boolean likelySpuriousCallEdge =
+            possibleTargets.size() >= appView.options().callGraphLikelySpuriousCallEdgeThreshold;
         for (DexEncodedMethod possibleTarget : possibleTargets) {
           if (possibleTarget.isProgramMethod(appView)) {
-            addTarget(possibleTarget);
+            addTarget(possibleTarget, likelySpuriousCallEdge);
           }
         }
       }
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index ecbda8a..8ff922b 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -158,6 +158,7 @@
   // into stack overflows when the call graph contains large call chains. This should have a
   // negligible impact on code size as long as the threshold is large enough.
   public int callGraphCycleEliminatorMaxDepthThreshold = 256;
+  public int callGraphLikelySpuriousCallEdgeThreshold = 50;
 
   public int classInliningInstructionLimit = 50;
   // This defines the limit of instructions in the inlinee