Add emulated dispatch in machine specifications

Bug: 184026720
Change-Id: Ie0afa5afe0d0aa3bbb9a02118a74992475523b42
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/DerivedMethod.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/DerivedMethod.java
new file mode 100644
index 0000000..abe876f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/DerivedMethod.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification;
+
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+
+/**
+ * A derived method is: - if the holderKind is null, a normal dexMethod; - if the holderKind is
+ * non-null, a method derived from the dexMethod In that case the method holder is used as the
+ * context to generate the holder type. The method may however differ (for example the method name
+ * may be different).
+ */
+public class DerivedMethod {
+
+  private final DexMethod method;
+  private final SyntheticKind holderKind;
+
+  public DerivedMethod(DexMethod method) {
+    this(method, null);
+  }
+
+  public DerivedMethod(DexMethod method, SyntheticKind holderKind) {
+    this.holderKind = holderKind;
+    this.method = method;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedDispatchMethodDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedDispatchMethodDescriptor.java
new file mode 100644
index 0000000..7b368fc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedDispatchMethodDescriptor.java
@@ -0,0 +1,55 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification;
+
+import com.android.tools.r8.graph.DexType;
+import java.util.LinkedHashMap;
+
+public class EmulatedDispatchMethodDescriptor {
+
+  /**
+   * When resolving into the descriptor, if the resolution is used for a super-invoke or to generate
+   * a forwarding method, then the forwarding method should be used. If the resolution is used to
+   * rewrite an invoke, then it should be rewritten to an invoke-static to the emulated dispatch
+   * method.
+   *
+   * <p>Emulated dispatch method are generated as follows: <code>emulatedDispatchMethod {
+   *   if (rcvr instanceof itf) {
+   *     invoke-interface itfMethod
+   *   }
+   *   if (rcvr instanceof dispatchCases0) {
+   *     invoke-static dispatchCases0
+   *   }
+   *   ...
+   *   if (rcvr instanceof dispatchCasesN) {
+   *     invoke-static dispatchCasesN
+   *   }
+   *   invoke-static forwardingMethod }</code>
+   *
+   * <p>For emulatedVirtualRetarget instances, the itfMethod holder and emulatedDispatchMethod
+   * holder are the itf and emulated dispatch holder types to synthesize. The forwardingMethod is
+   * the method to retarget to.
+   *
+   * <p>For emulated interface, itfMethod holder is the rewritten emulated interface type and the
+   * emulated dispatch method is on the $-EI class holding the dispatch methods. The forwarding
+   * method is the method on the companion class.
+   */
+  private final DerivedMethod interfaceMethod;
+
+  private final DerivedMethod emulatedDispatchMethod;
+  private final DerivedMethod forwardingMethod;
+  private final LinkedHashMap<DexType, DerivedMethod> dispatchCases;
+
+  public EmulatedDispatchMethodDescriptor(
+      DerivedMethod interfaceMethod,
+      DerivedMethod emulatedDispatchMethod,
+      DerivedMethod forwardingMethod,
+      LinkedHashMap<DexType, DerivedMethod> dispatchCases) {
+    this.interfaceMethod = interfaceMethod;
+    this.emulatedDispatchMethod = emulatedDispatchMethod;
+    this.forwardingMethod = forwardingMethod;
+    this.dispatchCases = dispatchCases;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
index 4dd87f6..4fd73d7 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
@@ -16,9 +16,11 @@
 
   MachineRewritingFlags(
       Map<DexMethod, DexMethod> staticRetarget,
-      Map<DexMethod, DexMethod> nonEmulatedVirtualRetarget) {
+      Map<DexMethod, DexMethod> nonEmulatedVirtualRetarget,
+      Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedVirtualRetarget) {
     this.staticRetarget = staticRetarget;
     this.nonEmulatedVirtualRetarget = nonEmulatedVirtualRetarget;
+    this.emulatedVirtualRetarget = emulatedVirtualRetarget;
   }
 
   // Static methods to retarget, duplicated to library boundaries.
@@ -32,6 +34,9 @@
   // code.
   private final Map<DexMethod, DexMethod> nonEmulatedVirtualRetarget;
 
+  // Virtual methods to retarget through emulated dispatch.
+  private final Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedVirtualRetarget;
+
   public static class Builder {
 
     Builder() {}
@@ -40,6 +45,8 @@
         ImmutableMap.builder();
     private final ImmutableMap.Builder<DexMethod, DexMethod> nonEmulatedVirtualRetarget =
         ImmutableMap.builder();
+    private final ImmutableMap.Builder<DexMethod, EmulatedDispatchMethodDescriptor>
+        emulatedVirtualRetarget = ImmutableMap.builder();
 
     public void putStaticRetarget(DexMethod src, DexMethod dest) {
       staticRetarget.put(src, dest);
@@ -49,8 +56,15 @@
       nonEmulatedVirtualRetarget.put(src, dest);
     }
 
+    public void putEmulatedVirtualRetarget(DexMethod src, EmulatedDispatchMethodDescriptor dest) {
+      emulatedVirtualRetarget.put(src, dest);
+    }
+
     public MachineRewritingFlags build() {
-      return new MachineRewritingFlags(staticRetarget.build(), nonEmulatedVirtualRetarget.build());
+      return new MachineRewritingFlags(
+          staticRetarget.build(),
+          nonEmulatedVirtualRetarget.build(),
+          emulatedVirtualRetarget.build());
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
index 7796c19..d2227cb 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
@@ -12,19 +12,26 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanRewritingFlags;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.DerivedMethod;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.EmulatedDispatchMethodDescriptor;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineRewritingFlags;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.TraversalContinuation;
 import java.io.IOException;
 import java.nio.file.Path;
+import java.util.LinkedHashMap;
+import java.util.Map;
 import java.util.concurrent.ExecutorService;
 import java.util.function.BiConsumer;
 
@@ -49,12 +56,14 @@
         .getRetargetCoreLibMember()
         .forEach(
             (method, type) ->
-                convertRetargetCoreLibMemberFlag(builder, method, type, appInfo, subtypingInfo));
+                convertRetargetCoreLibMemberFlag(
+                    builder, rewritingFlags, method, type, appInfo, subtypingInfo));
     return builder.build();
   }
 
   private void convertRetargetCoreLibMemberFlag(
       MachineRewritingFlags.Builder builder,
+      HumanRewritingFlags rewritingFlags,
       DexMethod method,
       DexType type,
       AppInfoWithClassHierarchy appInfo,
@@ -70,16 +79,79 @@
       convertNonEmulatedVirtualRetarget(builder, foundMethod, type, appInfo, subtypingInfo);
       return;
     }
-    convertEmulatedVirtualRetarget(builder, foundMethod, type, appInfo, subtypingInfo);
+    convertEmulatedVirtualRetarget(
+        builder, rewritingFlags, foundMethod, type, appInfo, subtypingInfo);
   }
 
   private void convertEmulatedVirtualRetarget(
       MachineRewritingFlags.Builder builder,
-      DexEncodedMethod foundMethod,
+      HumanRewritingFlags rewritingFlags,
+      DexEncodedMethod src,
       DexType type,
       AppInfoWithClassHierarchy appInfo,
       SubtypingInfo subtypingInfo) {
-    // TODO(b/184026720): To implement.
+    if (isEmulatedInterfaceDispatch(src, appInfo, rewritingFlags)) {
+      // Handled by emulated interface dispatch.
+      return;
+    }
+    // TODO(b/184026720): Implement library boundaries.
+    DexProto newProto = appInfo.dexItemFactory().prependHolderToProto(src.getReference());
+    DexMethod forwardingDexMethod =
+        appInfo.dexItemFactory().createMethod(type, newProto, src.getName());
+    DerivedMethod forwardingMethod = new DerivedMethod(forwardingDexMethod);
+    DerivedMethod interfaceMethod =
+        new DerivedMethod(src.getReference(), SyntheticKind.RETARGET_INTERFACE);
+    DexMethod dispatchDexMethod =
+        appInfo.dexItemFactory().createMethod(src.getHolderType(), newProto, src.getName());
+    DerivedMethod dispatchMethod =
+        new DerivedMethod(dispatchDexMethod, SyntheticKind.RETARGET_CLASS);
+    LinkedHashMap<DexType, DerivedMethod> dispatchCases = new LinkedHashMap<>();
+    assert validateNoOverride(src, appInfo, subtypingInfo);
+    builder.putEmulatedVirtualRetarget(
+        src.getReference(),
+        new EmulatedDispatchMethodDescriptor(
+            interfaceMethod, dispatchMethod, forwardingMethod, dispatchCases));
+  }
+
+  private boolean validateNoOverride(
+      DexEncodedMethod src, AppInfoWithClassHierarchy appInfo, SubtypingInfo subtypingInfo) {
+    for (DexType subtype : subtypingInfo.subtypes(src.getHolderType())) {
+      DexClass subclass = appInfo.definitionFor(subtype);
+      MethodResolutionResult resolutionResult =
+          appInfo.resolveMethodOn(subclass, src.getReference());
+      if (resolutionResult.isSuccessfulMemberResolutionResult()
+          && resolutionResult.getResolvedMethod().getReference() != src.getReference()) {
+        assert false; // Unsupported.
+      }
+    }
+    return true;
+  }
+
+  private boolean isEmulatedInterfaceDispatch(
+      DexEncodedMethod method,
+      AppInfoWithClassHierarchy appInfo,
+      HumanRewritingFlags humanRewritingFlags) {
+    // Answers true if this method is already managed through emulated interface dispatch.
+    Map<DexType, DexType> emulateLibraryInterface =
+        humanRewritingFlags.getEmulateLibraryInterface();
+    if (emulateLibraryInterface.isEmpty()) {
+      return false;
+    }
+    DexMethod methodToFind = method.getReference();
+    // Look-up all superclass and interfaces, if an emulated interface is found,
+    // and it implements the method, answers true.
+    DexClass dexClass = appInfo.definitionFor(method.getHolderType());
+    // Cannot retarget a method on a virtual method on an emulated interface.
+    assert !emulateLibraryInterface.containsKey(dexClass.getType());
+    return appInfo
+        .traverseSuperTypes(
+            dexClass,
+            (supertype, subclass, isSupertypeAnInterface) ->
+                TraversalContinuation.breakIf(
+                    subclass.isInterface()
+                        && emulateLibraryInterface.containsKey(subclass.getType())
+                        && subclass.lookupMethod(methodToFind) != null))
+        .shouldBreak();
   }
 
   private void convertNonEmulatedRetarget(