Merge "Per-tree method pool for publicizing private instance methods."
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index feef8f7..91b1dc7 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -729,8 +729,10 @@
   }
 
   public DexCallSite createCallSite(
-      DexString methodName, DexProto methodProto,
-      DexMethodHandle bootstrapMethod, List<DexValue> bootstrapArgs) {
+      DexString methodName,
+      DexProto methodProto,
+      DexMethodHandle bootstrapMethod,
+      List<DexValue> bootstrapArgs) {
     // Call sites are never equal and therefore we do not canonicalize.
     assert !sorted;
     DexCallSite callSite = new DexCallSite(methodName, methodProto, bootstrapMethod, bootstrapArgs);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
index b7653fa..94bf56c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
@@ -98,9 +98,10 @@
         if (target == null) {
           target = appInfo.lookupDirectTarget(method);
         }
-        assert target == null ||
-            (implHandle.type.isInvokeInstance() && isInstanceMethod(target)) ||
-            (implHandle.type.isInvokeDirect() && isPrivateInstanceMethod(target));
+        assert target == null
+            || (implHandle.type.isInvokeInstance() && isInstanceMethod(target))
+            || (implHandle.type.isInvokeDirect() && isPrivateInstanceMethod(target))
+            || (implHandle.type.isInvokeDirect() && isPublicizedInstanceMethod(target));
         return target;
       }
 
@@ -129,12 +130,17 @@
 
   private boolean isInstanceMethod(DexEncodedMethod encodedMethod) {
     assert encodedMethod != null;
-    return !encodedMethod.accessFlags.isConstructor() && !encodedMethod.accessFlags.isStatic();
+    return !encodedMethod.accessFlags.isConstructor() && !encodedMethod.isStaticMethod();
   }
 
   private boolean isPrivateInstanceMethod(DexEncodedMethod encodedMethod) {
     assert encodedMethod != null;
-    return encodedMethod.accessFlags.isPrivate() && isInstanceMethod(encodedMethod);
+    return encodedMethod.isPrivateMethod() && isInstanceMethod(encodedMethod);
+  }
+
+  private boolean isPublicizedInstanceMethod(DexEncodedMethod encodedMethod) {
+    assert encodedMethod != null;
+    return encodedMethod.isPublicized() && isInstanceMethod(encodedMethod);
   }
 
   final MethodAccessFlags getAccessibility() {
diff --git a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
index eaeb1ee..04f8a3b 100644
--- a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
@@ -18,11 +18,13 @@
 import com.android.tools.r8.utils.Timing;
 import com.google.common.base.Equivalence;
 import com.google.common.base.Equivalence.Wrapper;
-import com.google.common.collect.Sets;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
@@ -31,12 +33,11 @@
   private final DexApplication application;
   private final AppInfo appInfo;
   private final RootSet rootSet;
-  private final GraphLense previuosLense;
+  private final GraphLense previousLense;
   private final PublicizedLenseBuilder lenseBuilder;
 
   private final Equivalence<DexMethod> equivalence = MethodSignatureEquivalence.get();
-  // TODO(b/72109068): finer-grained naming spaces, e.g., per-tree.
-  private final Set<Wrapper<DexMethod>> methodPool = Sets.newConcurrentHashSet();
+  private final Map<DexClass, MethodPool> methodPools = new ConcurrentHashMap<>();
 
   private ClassAndMemberPublicizer(
       DexApplication application,
@@ -46,7 +47,7 @@
     this.application = application;
     this.appInfo = appInfo;
     this.rootSet = rootSet;
-    this.previuosLense = previousLense;
+    this.previousLense = previousLense;
     lenseBuilder = PublicizerLense.createBuilder();
   }
 
@@ -74,9 +75,8 @@
     timing.begin("Phase 1: collectMethods");
     try {
       List<Future<?>> futures = new ArrayList<>();
-      // TODO(b/72109068): finer-grained naming spaces will need a different class visiting.
       application.classes().forEach(clazz ->
-          futures.add(executorService.submit(collectMethodPerClass(clazz))));
+          futures.add(executorService.submit(computeMethodPoolPerClass(clazz))));
       ThreadUtils.awaitFutures(futures);
     } finally {
       timing.end();
@@ -88,17 +88,35 @@
     publicizeType(appInfo.dexItemFactory.objectType);
     timing.end();
 
-    return lenseBuilder.build(appInfo, previuosLense);
+    return lenseBuilder.build(appInfo, previousLense);
   }
 
-  private Runnable collectMethodPerClass(DexClass clazz) {
+  private Runnable computeMethodPoolPerClass(DexClass clazz) {
     return () -> {
+      MethodPool methodPool = methodPools.computeIfAbsent(clazz, k -> new MethodPool());
       clazz.forEachMethod(encodedMethod -> {
         // We will add private instance methods when we promote them.
         if (!encodedMethod.isPrivateMethod() || encodedMethod.isStaticMethod()) {
-          methodPool.add(equivalence.wrap(encodedMethod.method));
+          methodPool.seen(equivalence.wrap(encodedMethod.method));
         }
       });
+      if (clazz.superType != null) {
+        DexClass superClazz = application.definitionFor(clazz.superType);
+        if (superClazz != null) {
+          MethodPool superPool = methodPools.computeIfAbsent(superClazz, k -> new MethodPool());
+          superPool.linkSubtype(methodPool);
+          methodPool.linkSupertype(superPool);
+        }
+      }
+      if (clazz.isInterface()) {
+        clazz.type.forAllImplementsSubtypes(implementer -> {
+          DexClass subClazz = application.definitionFor(implementer);
+          if (subClazz != null) {
+            MethodPool childPool = methodPools.computeIfAbsent(subClazz, k -> new MethodPool());
+            childPool.linkInterface(methodPool);
+          }
+        });
+      }
     };
   }
 
@@ -118,7 +136,6 @@
       }
     }
 
-    // TODO(b/72109068): Can process sub types in parallel.
     type.forAllExtendsSubtypes(this::publicizeType);
   }
 
@@ -152,21 +169,22 @@
 
       // We can't publicize private instance methods in interfaces or methods that are copied from
       // interfaces to lambda-desugared classes because this will be added as a new default method.
-      // TODO(b/72109068): It might be possible to transform it into static methods, though.
+      // TODO(b/111118390): It might be possible to transform it into static methods, though.
       if (holder.isInterface() || accessFlags.isSynthetic()) {
         return false;
       }
 
+      MethodPool methodPool = methodPools.get(holder);
       Wrapper<DexMethod> key = equivalence.wrap(encodedMethod.method);
-      if (methodPool.contains(key)) {
+      if (methodPool.hasSeen(key)) {
         // We can't do anything further because even renaming is not allowed due to the keep rule.
         if (rootSet.noObfuscation.contains(encodedMethod)) {
           return false;
         }
-        // TODO(b/72109068): Renaming will enable more private instance methods to be publicized.
+        // TODO(b/111118390): Renaming will enable more private instance methods to be publicized.
         return false;
       }
-      methodPool.add(key);
+      methodPool.seen(key);
       lenseBuilder.add(encodedMethod.method);
       accessFlags.unsetPrivate();
       accessFlags.setFinal();
@@ -186,4 +204,51 @@
     accessFlags.setPublic();
     return false;
   }
+
+  // Per-class collection of method signatures, which will be used to determine if a certain method
+  // can be publicized or not.
+  static class MethodPool {
+    private MethodPool superType;
+    private final Set<MethodPool> interfaces = new HashSet<>();
+    private final Set<MethodPool> subTypes = new HashSet<>();
+    private final Set<Wrapper<DexMethod>> methodPool = new HashSet<>();
+
+    MethodPool() {
+    }
+
+    synchronized void linkSupertype(MethodPool superType) {
+      assert this.superType == null;
+      this.superType = superType;
+    }
+
+    synchronized void linkSubtype(MethodPool subType) {
+      boolean added = subTypes.add(subType);
+      assert added;
+    }
+
+    synchronized void linkInterface(MethodPool itf) {
+      boolean added = interfaces.add(itf);
+      assert added;
+    }
+
+    synchronized void seen(Wrapper<DexMethod> method) {
+      boolean added = methodPool.add(method);
+      assert added;
+    }
+
+    boolean hasSeen(Wrapper<DexMethod> method) {
+      return hasSeenUpwardRecursive(method) || hasSeenDownwardRecursive(method);
+    }
+
+    private boolean hasSeenUpwardRecursive(Wrapper<DexMethod> method) {
+      return methodPool.contains(method)
+          || (superType != null && superType.hasSeenUpwardRecursive(method))
+          || interfaces.stream().anyMatch(itf -> itf.hasSeenUpwardRecursive(method));
+    }
+
+    private boolean hasSeenDownwardRecursive(Wrapper<DexMethod> method) {
+      return methodPool.contains(method)
+          || subTypes.stream().anyMatch(subType -> subType.hasSeenDownwardRecursive(method));
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTest.java
index 027b7e8..53a8c31 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTest.java
@@ -171,10 +171,13 @@
     assertNotPublic(dexInspector, Base.class,
         new MethodSignature("foo2", STRING, ImmutableList.of()));
 
-    // Sub1#bar1(int) can't be publicized due to Base#bar1(int).
-    assertNotPublic(dexInspector, Sub1.class,
+    // Sub?#bar1(int) can be publicized as they don't bother each other.
+    assertPublic(dexInspector, Sub1.class,
+        new MethodSignature("bar1", STRING, ImmutableList.of("int")));
+    assertPublic(dexInspector, Sub2.class,
         new MethodSignature("bar1", STRING, ImmutableList.of("int")));
 
+    // Sub2#bar2(int) is unique throughout the hierarchy, hence publicized.
     assertPublic(dexInspector, Sub2.class,
         new MethodSignature("bar2", STRING, ImmutableList.of("int")));
   }
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Base.java b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Base.java
index fd1fd1c..6325c78 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Base.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Base.java
@@ -30,19 +30,10 @@
     return foo2();
   }
 
-  private synchronized String bar1(int i) {
-    throw new AssertionError("Sub1#bar1(int) will not use this signature.");
-  }
-
   public void dump() {
     System.out.println(foo());
     System.out.println(foo1());
     System.out.println(foo2());
-    try {
-      bar1(0);
-    } catch (AssertionError e) {
-      // expected
-    }
   }
 
 }
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub1.java b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub1.java
index 4cbbf11..317aaff 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub1.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub1.java
@@ -22,11 +22,7 @@
   public void dump() {
     System.out.println(foo1());
     System.out.println(foo1(0));
-    try {
-      System.out.println(bar1(0));
-    } catch (Throwable e) {
-      System.out.println(e.getClass().getName());
-    }
+    System.out.println(bar1(0));
   }
 
 }
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub2.java b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub2.java
index 8c4e01e..039e3ac 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub2.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub2.java
@@ -10,6 +10,14 @@
     return "Sub2::foo2()";
   }
 
+  private synchronized String bar1(int i) {
+    return "Sub2::bar1(" + i + ")";
+  }
+
+  public String pBar1() {
+    return bar1(1);
+  }
+
   private synchronized String bar2(int i) {
     return "Sub2::bar2(" + i + ")";
   }
@@ -23,6 +31,11 @@
     System.out.println(foo2());
     System.out.println(foo2(0));
     System.out.println(bar2(0));
+    try {
+      bar1(0);
+    } catch (AssertionError e) {
+      // expected
+    }
   }
 
 }