Machine Specification for emulated interface

Bug: 184026720
Change-Id: I4b522634dc16cdd35c79645cf971afda60bb2ed9
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
index c5154cd..6eb2424 100644
--- 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
@@ -37,7 +37,6 @@
    * 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;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedInterfaceDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedInterfaceDescriptor.java
new file mode 100644
index 0000000..0f9a520
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedInterfaceDescriptor.java
@@ -0,0 +1,28 @@
+// 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.graph.DexType;
+import java.util.Map;
+
+public class EmulatedInterfaceDescriptor {
+  private final DexType rewrittenType;
+  private final Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedMethods;
+
+  public EmulatedInterfaceDescriptor(
+      DexType rewrittenType, Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedMethods) {
+    this.rewrittenType = rewrittenType;
+    this.emulatedMethods = emulatedMethods;
+  }
+
+  public DexType getRewrittenType() {
+    return rewrittenType;
+  }
+
+  public Map<DexMethod, EmulatedDispatchMethodDescriptor> getEmulatedMethods() {
+    return emulatedMethods;
+  }
+}
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 f73e1c9..db17cf5 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
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification;
 
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
 import com.google.common.collect.ImmutableMap;
 import java.util.Map;
 
@@ -17,10 +18,12 @@
   MachineRewritingFlags(
       Map<DexMethod, DexMethod> staticRetarget,
       Map<DexMethod, DexMethod> nonEmulatedVirtualRetarget,
-      Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedVirtualRetarget) {
+      Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedVirtualRetarget,
+      Map<DexType, EmulatedInterfaceDescriptor> emulatedInterfaces) {
     this.staticRetarget = staticRetarget;
     this.nonEmulatedVirtualRetarget = nonEmulatedVirtualRetarget;
     this.emulatedVirtualRetarget = emulatedVirtualRetarget;
+    this.emulatedInterfaces = emulatedInterfaces;
   }
 
   // Static methods to retarget, duplicated to library boundaries.
@@ -37,6 +40,9 @@
   // Virtual methods to retarget through emulated dispatch.
   private final Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedVirtualRetarget;
 
+  // Emulated interface descriptors.
+  private final Map<DexType, EmulatedInterfaceDescriptor> emulatedInterfaces;
+
   public Map<DexMethod, DexMethod> getStaticRetarget() {
     return staticRetarget;
   }
@@ -49,6 +55,10 @@
     return emulatedVirtualRetarget;
   }
 
+  public Map<DexType, EmulatedInterfaceDescriptor> getEmulatedInterfaces() {
+    return emulatedInterfaces;
+  }
+
   public static class Builder {
 
     Builder() {}
@@ -59,6 +69,8 @@
         ImmutableMap.builder();
     private final ImmutableMap.Builder<DexMethod, EmulatedDispatchMethodDescriptor>
         emulatedVirtualRetarget = ImmutableMap.builder();
+    private final ImmutableMap.Builder<DexType, EmulatedInterfaceDescriptor> emulatedInterfaces =
+        ImmutableMap.builder();
 
     public void putStaticRetarget(DexMethod src, DexMethod dest) {
       staticRetarget.put(src, dest);
@@ -68,6 +80,10 @@
       nonEmulatedVirtualRetarget.put(src, dest);
     }
 
+    public void putEmulatedInterface(DexType src, EmulatedInterfaceDescriptor descriptor) {
+      emulatedInterfaces.put(src, descriptor);
+    }
+
     public void putEmulatedVirtualRetarget(DexMethod src, EmulatedDispatchMethodDescriptor dest) {
       emulatedVirtualRetarget.put(src, dest);
     }
@@ -76,7 +92,8 @@
       return new MachineRewritingFlags(
           staticRetarget.build(),
           nonEmulatedVirtualRetarget.build(),
-          emulatedVirtualRetarget.build());
+          emulatedVirtualRetarget.build(),
+          emulatedInterfaces.build());
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSyntheticHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSyntheticHelper.java
index 5a5768f..8f7d25f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSyntheticHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSyntheticHelper.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.ClasspathOrLibraryClass;
 import com.android.tools.r8.graph.DexClass;
 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.MethodAccessFlags;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.DerivedMethod;
@@ -37,7 +38,8 @@
 
   private DexMethod emulatedHolderDispatchMethod(DexType holder, DerivedMethod method) {
     assert method.getHolderKind() == SyntheticKind.RETARGET_CLASS;
-    return appView.dexItemFactory().createMethod(holder, method.getProto(), method.getName());
+    DexProto newProto = appView.dexItemFactory().prependHolderToProto(method.getMethod());
+    return appView.dexItemFactory().createMethod(holder, newProto, method.getName());
   }
 
   DexMethod emulatedInterfaceDispatchMethod(DexType holder, DerivedMethod method) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/RetargetingInfo.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/RetargetingInfo.java
index eb38e9e..9e1f5e3 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/RetargetingInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/RetargetingInfo.java
@@ -115,12 +115,8 @@
                 DerivedMethod forwardingMethod = new DerivedMethod(forwardingDexMethod);
                 DerivedMethod interfaceMethod =
                     new DerivedMethod(methodReference, SyntheticKind.RETARGET_INTERFACE);
-                DexMethod dispatchDexMethod =
-                    appView
-                        .dexItemFactory()
-                        .createMethod(methodReference.getHolderType(), newProto, methodName);
                 DerivedMethod dispatchMethod =
-                    new DerivedMethod(dispatchDexMethod, SyntheticKind.RETARGET_CLASS);
+                    new DerivedMethod(methodReference, SyntheticKind.RETARGET_CLASS);
                 emulatedVirtualRetarget.put(
                     methodReference,
                     new EmulatedDispatchMethodDescriptor(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineEmulatedInterfaceConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineEmulatedInterfaceConverter.java
new file mode 100644
index 0000000..2411ba0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineEmulatedInterfaceConverter.java
@@ -0,0 +1,183 @@
+// 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.specificationconversion;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+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.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+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.EmulatedInterfaceDescriptor;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineRewritingFlags;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.utils.WorkList;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class HumanToMachineEmulatedInterfaceConverter {
+
+  private final AppInfoWithClassHierarchy appInfo;
+  private Map<DexType, List<DexType>> emulatedInterfaceHierarchy;
+
+  public HumanToMachineEmulatedInterfaceConverter(AppInfoWithClassHierarchy appInfo) {
+    this.appInfo = appInfo;
+  }
+
+  public void convertEmulatedInterfaces(
+      HumanRewritingFlags rewritingFlags,
+      AppInfoWithClassHierarchy appInfo,
+      MachineRewritingFlags.Builder builder) {
+    Map<DexType, DexType> emulateInterfaces = rewritingFlags.getEmulateLibraryInterface();
+    Set<DexMethod> dontRewriteInvocation = rewritingFlags.getDontRewriteInvocation();
+    emulatedInterfaceHierarchy = processEmulatedInterfaceHierarchy(appInfo, emulateInterfaces);
+    for (DexType itf : emulateInterfaces.keySet()) {
+      DexProgramClass itfClass = appInfo.contextIndependentDefinitionFor(itf).asProgramClass();
+      assert itfClass != null;
+      Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedMethods = new IdentityHashMap<>();
+      itfClass.forEachProgramVirtualMethodMatching(
+          m -> m.isDefaultMethod() && !dontRewriteInvocation.contains(m.getReference()),
+          method ->
+              emulatedMethods.put(
+                  method.getReference(),
+                  computeEmulatedDispatchDescriptor(
+                      method.getReference(), rewritingFlags, appInfo)));
+      builder.putEmulatedInterface(
+          itf, new EmulatedInterfaceDescriptor(emulateInterfaces.get(itf), emulatedMethods));
+    }
+  }
+
+  private EmulatedDispatchMethodDescriptor computeEmulatedDispatchDescriptor(
+      DexMethod method, HumanRewritingFlags rewritingFlags, AppInfoWithClassHierarchy appInfo) {
+    DerivedMethod forwardingMethod = new DerivedMethod(method, SyntheticKind.COMPANION_CLASS);
+    DexMethod itfDexMethod =
+        appInfo
+            .dexItemFactory()
+            .createMethod(
+                rewritingFlags.getEmulateLibraryInterface().get(method.getHolderType()),
+                method.getProto(),
+                method.getName());
+    DerivedMethod interfaceMethod = new DerivedMethod(itfDexMethod);
+    DerivedMethod dispatchMethod =
+        new DerivedMethod(method, SyntheticKind.EMULATED_INTERFACE_CLASS);
+    LinkedHashMap<DexType, DerivedMethod> dispatchCases = getDispatchCases(rewritingFlags, method);
+    return new EmulatedDispatchMethodDescriptor(
+        interfaceMethod, dispatchMethod, forwardingMethod, dispatchCases);
+  }
+
+  private LinkedHashMap<DexType, DerivedMethod> getDispatchCases(
+      HumanRewritingFlags rewritingFlags, DexMethod method) {
+    // To properly emulate the library interface call, we need to compute the interfaces
+    // inheriting from the interface and manually implement the dispatch with instance of.
+    // The list guarantees that an interface is always after interfaces it extends,
+    // hence reverse iteration.
+    List<DexType> subInterfaces = emulatedInterfaceHierarchy.get(method.getHolderType());
+    LinkedHashMap<DexType, DerivedMethod> extraDispatchCases = new LinkedHashMap<>();
+    // Retarget core lib emulated dispatch handled as part of emulated interface dispatch.
+    Map<DexMethod, DexType> retargetCoreLibMember = rewritingFlags.getRetargetCoreLibMember();
+    for (DexMethod retarget : retargetCoreLibMember.keySet()) {
+      if (retarget.match(method)) {
+        DexClass inClass = appInfo.definitionFor(retarget.getHolderType());
+        if (inClass != null && implementsInterface(inClass, method.getHolderType())) {
+          DexProto newProto = appInfo.dexItemFactory().prependHolderToProto(retarget);
+          DexMethod forwardingDexMethod =
+              appInfo
+                  .dexItemFactory()
+                  .createMethod(retargetCoreLibMember.get(retarget), newProto, retarget.getName());
+          extraDispatchCases.put(retarget.getHolderType(), new DerivedMethod(forwardingDexMethod));
+        }
+      }
+    }
+    if (subInterfaces != null) {
+      for (int i = subInterfaces.size() - 1; i >= 0; i--) {
+        DexClass subInterfaceClass = appInfo.definitionFor(subInterfaces.get(i));
+        assert subInterfaceClass != null;
+        assert subInterfaceClass.isProgramClass();
+        // Else computation of subInterface would have failed.
+        // if the method is implemented, extra dispatch is required.
+        DexEncodedMethod result = subInterfaceClass.lookupVirtualMethod(method);
+        if (result != null && !result.isAbstract()) {
+          assert result.isDefaultMethod();
+          DexMethod reference = result.getReference();
+          extraDispatchCases.put(
+              subInterfaceClass.type, new DerivedMethod(reference, SyntheticKind.COMPANION_CLASS));
+        }
+      }
+    } else {
+      assert extraDispatchCases.size() <= 1;
+    }
+    return extraDispatchCases;
+  }
+
+  private boolean implementsInterface(DexClass clazz, DexType interfaceType) {
+    WorkList<DexType> workList =
+        WorkList.newIdentityWorkList(Arrays.asList(clazz.interfaces.values));
+    while (!workList.isEmpty()) {
+      DexType next = workList.next();
+      if (interfaceType == next) {
+        return true;
+      }
+      DexClass nextClass = appInfo.definitionFor(next);
+      if (nextClass != null) {
+        workList.addIfNotSeen(nextClass.interfaces.values);
+      }
+    }
+    return false;
+  }
+
+  private Map<DexType, List<DexType>> processEmulatedInterfaceHierarchy(
+      AppInfoWithClassHierarchy appInfo, Map<DexType, DexType> emulateInterfaces) {
+    Map<DexType, List<DexType>> emulatedInterfacesHierarchy = new IdentityHashMap<>();
+    Set<DexType> processed = Sets.newIdentityHashSet();
+    ArrayList<DexType> emulatedInterfacesSorted = new ArrayList<>(emulateInterfaces.keySet());
+    emulatedInterfacesSorted.sort(DexType::compareTo);
+    for (DexType interfaceType : emulatedInterfacesSorted) {
+      processEmulatedInterfaceHierarchy(
+          appInfo, emulateInterfaces, interfaceType, processed, emulatedInterfacesHierarchy);
+    }
+    return emulatedInterfacesHierarchy;
+  }
+
+  private void processEmulatedInterfaceHierarchy(
+      AppInfoWithClassHierarchy appInfo,
+      Map<DexType, DexType> emulateInterfaces,
+      DexType interfaceType,
+      Set<DexType> processed,
+      Map<DexType, List<DexType>> emulatedInterfacesHierarchy) {
+    if (processed.contains(interfaceType)) {
+      return;
+    }
+    emulatedInterfacesHierarchy.put(interfaceType, new ArrayList<>());
+    processed.add(interfaceType);
+    DexClass theInterface = appInfo.definitionFor(interfaceType);
+    if (theInterface == null) {
+      return;
+    }
+    WorkList<DexType> workList =
+        WorkList.newIdentityWorkList(Arrays.asList(theInterface.interfaces.values));
+    while (!workList.isEmpty()) {
+      DexType next = workList.next();
+      if (emulateInterfaces.containsKey(next)) {
+        processEmulatedInterfaceHierarchy(
+            appInfo, emulateInterfaces, next, processed, emulatedInterfacesHierarchy);
+        emulatedInterfacesHierarchy.get(next).add(interfaceType);
+        DexClass nextClass = appInfo.definitionFor(next);
+        if (nextClass != null) {
+          workList.addIfNotSeen(nextClass.interfaces.values);
+        }
+      }
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineRetargetConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineRetargetConverter.java
new file mode 100644
index 0000000..fae5b31
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineRetargetConverter.java
@@ -0,0 +1,168 @@
+// 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.specificationconversion;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+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.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.MachineRewritingFlags;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.utils.TraversalContinuation;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.function.BiConsumer;
+
+public class HumanToMachineRetargetConverter {
+
+  private final AppInfoWithClassHierarchy appInfo;
+  private SubtypingInfo subtypingInfo;
+
+  public HumanToMachineRetargetConverter(AppInfoWithClassHierarchy appInfo) {
+    this.appInfo = appInfo;
+  }
+
+  public void convertRetargetFlags(
+      HumanRewritingFlags rewritingFlags, MachineRewritingFlags.Builder builder) {
+    subtypingInfo = new SubtypingInfo(appInfo);
+    rewritingFlags
+        .getRetargetCoreLibMember()
+        .forEach(
+            (method, type) ->
+                convertRetargetCoreLibMemberFlag(builder, rewritingFlags, method, type));
+  }
+
+  private void convertRetargetCoreLibMemberFlag(
+      MachineRewritingFlags.Builder builder,
+      HumanRewritingFlags rewritingFlags,
+      DexMethod method,
+      DexType type) {
+    DexClass holder = appInfo.definitionFor(method.holder);
+    DexEncodedMethod foundMethod = holder.lookupMethod(method);
+    assert foundMethod != null;
+    if (foundMethod.isStatic()) {
+      convertStaticRetarget(builder, foundMethod, type);
+      return;
+    }
+    if (holder.isFinal() || foundMethod.isFinal()) {
+      convertNonEmulatedVirtualRetarget(builder, foundMethod, type);
+      return;
+    }
+    convertEmulatedVirtualRetarget(builder, rewritingFlags, foundMethod, type);
+  }
+
+  private void convertEmulatedVirtualRetarget(
+      MachineRewritingFlags.Builder builder,
+      HumanRewritingFlags rewritingFlags,
+      DexEncodedMethod src,
+      DexType type) {
+    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);
+    DerivedMethod dispatchMethod =
+        new DerivedMethod(src.getReference(), 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(
+      DexEncodedMethod foundMethod,
+      DexType type,
+      AppInfoWithClassHierarchy appInfo,
+      SubtypingInfo subtypingInfo,
+      BiConsumer<DexMethod, DexMethod> consumer) {
+    DexMethod src = foundMethod.getReference();
+    DexMethod dest = src.withHolder(type, appInfo.dexItemFactory());
+    consumer.accept(src, dest);
+    for (DexType subtype : subtypingInfo.subtypes(foundMethod.getHolderType())) {
+      DexClass subclass = appInfo.definitionFor(subtype);
+      MethodResolutionResult resolutionResult = appInfo.resolveMethodOn(subclass, src);
+      if (resolutionResult.isSuccessfulMemberResolutionResult()
+          && resolutionResult.getResolvedMethod().getReference() == src) {
+        consumer.accept(src.withHolder(subtype, appInfo.dexItemFactory()), dest);
+      }
+    }
+  }
+
+  private void convertNonEmulatedVirtualRetarget(
+      MachineRewritingFlags.Builder builder, DexEncodedMethod foundMethod, DexType type) {
+    convertNonEmulatedRetarget(
+        foundMethod,
+        type,
+        appInfo,
+        subtypingInfo,
+        (src, dest) ->
+            builder.putNonEmulatedVirtualRetarget(
+                src,
+                dest.withExtraArgumentPrepended(
+                    foundMethod.getHolderType(), appInfo.dexItemFactory())));
+  }
+
+  private void convertStaticRetarget(
+      MachineRewritingFlags.Builder builder, DexEncodedMethod foundMethod, DexType type) {
+    convertNonEmulatedRetarget(
+        foundMethod, type, appInfo, subtypingInfo, builder::putStaticRetarget);
+  }
+}
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 d2227cb..69ae2e2 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
@@ -9,31 +9,17 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
-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;
 
 public class HumanToMachineSpecificationConverter {
 
@@ -51,156 +37,12 @@
   private MachineRewritingFlags convertRewritingFlags(
       HumanRewritingFlags rewritingFlags, AppInfoWithClassHierarchy appInfo) {
     MachineRewritingFlags.Builder builder = MachineRewritingFlags.builder();
-    SubtypingInfo subtypingInfo = new SubtypingInfo(appInfo);
-    rewritingFlags
-        .getRetargetCoreLibMember()
-        .forEach(
-            (method, type) ->
-                convertRetargetCoreLibMemberFlag(
-                    builder, rewritingFlags, method, type, appInfo, subtypingInfo));
+    new HumanToMachineRetargetConverter(appInfo).convertRetargetFlags(rewritingFlags, builder);
+    new HumanToMachineEmulatedInterfaceConverter(appInfo)
+        .convertEmulatedInterfaces(rewritingFlags, appInfo, builder);
     return builder.build();
   }
 
-  private void convertRetargetCoreLibMemberFlag(
-      MachineRewritingFlags.Builder builder,
-      HumanRewritingFlags rewritingFlags,
-      DexMethod method,
-      DexType type,
-      AppInfoWithClassHierarchy appInfo,
-      SubtypingInfo subtypingInfo) {
-    DexClass holder = appInfo.definitionFor(method.holder);
-    DexEncodedMethod foundMethod = holder.lookupMethod(method);
-    assert foundMethod != null;
-    if (foundMethod.isStatic()) {
-      convertStaticRetarget(builder, foundMethod, type, appInfo, subtypingInfo);
-      return;
-    }
-    if (holder.isFinal() || foundMethod.isFinal()) {
-      convertNonEmulatedVirtualRetarget(builder, foundMethod, type, appInfo, subtypingInfo);
-      return;
-    }
-    convertEmulatedVirtualRetarget(
-        builder, rewritingFlags, foundMethod, type, appInfo, subtypingInfo);
-  }
-
-  private void convertEmulatedVirtualRetarget(
-      MachineRewritingFlags.Builder builder,
-      HumanRewritingFlags rewritingFlags,
-      DexEncodedMethod src,
-      DexType type,
-      AppInfoWithClassHierarchy appInfo,
-      SubtypingInfo subtypingInfo) {
-    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(
-      DexEncodedMethod foundMethod,
-      DexType type,
-      AppInfoWithClassHierarchy appInfo,
-      SubtypingInfo subtypingInfo,
-      BiConsumer<DexMethod, DexMethod> consumer) {
-    DexMethod src = foundMethod.getReference();
-    DexMethod dest = src.withHolder(type, appInfo.dexItemFactory());
-    consumer.accept(src, dest);
-    for (DexType subtype : subtypingInfo.subtypes(foundMethod.getHolderType())) {
-      DexClass subclass = appInfo.definitionFor(subtype);
-      MethodResolutionResult resolutionResult = appInfo.resolveMethodOn(subclass, src);
-      if (resolutionResult.isSuccessfulMemberResolutionResult()
-          && resolutionResult.getResolvedMethod().getReference() == src) {
-        consumer.accept(src.withHolder(subtype, appInfo.dexItemFactory()), dest);
-      }
-    }
-  }
-
-  private void convertNonEmulatedVirtualRetarget(
-      MachineRewritingFlags.Builder builder,
-      DexEncodedMethod foundMethod,
-      DexType type,
-      AppInfoWithClassHierarchy appInfo,
-      SubtypingInfo subtypingInfo) {
-    convertNonEmulatedRetarget(
-        foundMethod,
-        type,
-        appInfo,
-        subtypingInfo,
-        (src, dest) ->
-            builder.putNonEmulatedVirtualRetarget(
-                src,
-                dest.withExtraArgumentPrepended(
-                    foundMethod.getHolderType(), appInfo.dexItemFactory())));
-  }
-
-  private void convertStaticRetarget(
-      MachineRewritingFlags.Builder builder,
-      DexEncodedMethod foundMethod,
-      DexType type,
-      AppInfoWithClassHierarchy appInfo,
-      SubtypingInfo subtypingInfo) {
-    convertNonEmulatedRetarget(
-        foundMethod, type, appInfo, subtypingInfo, builder::putStaticRetarget);
-  }
-
   private DexApplication readApp(Path androidLib, InternalOptions options) throws IOException {
     AndroidApp androidApp = AndroidApp.builder().addProgramFile(androidLib).build();
     ApplicationReader applicationReader =
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/ProgramEmulatedInterfaceSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/ProgramEmulatedInterfaceSynthesizer.java
index 55cf273..3e8b5cb 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/ProgramEmulatedInterfaceSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/ProgramEmulatedInterfaceSynthesizer.java
@@ -4,21 +4,25 @@
 package com.android.tools.r8.ir.desugar.itf;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 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.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaring;
 import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaringEventConsumer;
+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.EmulatedInterfaceDescriptor;
 import com.android.tools.r8.ir.desugar.itf.EmulatedInterfaceSynthesizerEventConsumer.L8ProgramEmulatedInterfaceSynthesizerEventConsumer;
 import com.android.tools.r8.ir.synthetic.EmulateDispatchSyntheticCfCodeProvider;
 import com.android.tools.r8.synthesis.SyntheticMethodBuilder;
 import com.android.tools.r8.synthesis.SyntheticNaming;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.synthesis.SyntheticProgramClassBuilder;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.Iterables;
@@ -116,28 +120,34 @@
         method ->
             builder.addMethod(
                 methodBuilder ->
-                    synthesizeEmulatedInterfaceMethod(method, emulatedInterface, methodBuilder)));
-    assert builder.getType()
-        == InterfaceDesugaringSyntheticHelper.getEmulateLibraryInterfaceClassType(
-            emulatedInterface.type, appView.dexItemFactory());
+                    synthesizeEmulatedInterfaceMethod(
+                        method, emulatedInterface, builder.getType(), methodBuilder)));
+  }
+
+  private DexMethod emulatedMethod(DerivedMethod method, DexType holder) {
+    assert method.getHolderKind() == SyntheticKind.EMULATED_INTERFACE_CLASS;
+    DexProto newProto = appView.dexItemFactory().prependHolderToProto(method.getMethod());
+    return appView.dexItemFactory().createMethod(holder, newProto, method.getName());
+  }
+
+  private DexMethod interfaceMethod(DerivedMethod method) {
+    assert method.getHolderKind() == null;
+    return method.getMethod();
   }
 
   private void synthesizeEmulatedInterfaceMethod(
-      ProgramMethod method, DexProgramClass theInterface, SyntheticMethodBuilder methodBuilder) {
+      ProgramMethod method,
+      DexProgramClass theInterface,
+      DexType dispatchType,
+      SyntheticMethodBuilder methodBuilder) {
     assert !method.getDefinition().isStatic();
+    if (appView.options().testing.machineDesugaredLibrarySpecification != null) {
+      synthesizeEmulatedInterfaceMethodFromMachineSpecification(
+          method, theInterface, dispatchType, methodBuilder);
+      return;
+    }
     DexMethod emulatedMethod = helper.emulateInterfaceLibraryMethod(method);
-    methodBuilder
-        .setName(emulatedMethod.getName())
-        .setProto(emulatedMethod.getProto())
-        .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
-        .setCode(
-            emulatedInterfaceMethod ->
-                synthesizeCfCode(method.asProgramMethod(), theInterface, emulatedInterfaceMethod));
-  }
-
-  private CfCode synthesizeCfCode(
-      ProgramMethod method, DexProgramClass theInterface, DexMethod emulatedInterfaceMethod) {
-    DexMethod libraryMethod =
+    DexMethod itfMethod =
         method
             .getReference()
             .withHolder(helper.getEmulatedInterface(theInterface.type), appView.dexItemFactory());
@@ -145,13 +155,84 @@
         helper.ensureDefaultAsMethodOfProgramCompanionClassStub(method).getReference();
     LinkedHashMap<DexType, DexMethod> extraDispatchCases =
         getDispatchCases(method, theInterface, companionMethod);
-    return new EmulateDispatchSyntheticCfCodeProvider(
-            emulatedInterfaceMethod.getHolderType(),
-            companionMethod,
-            libraryMethod,
-            extraDispatchCases,
-            appView)
-        .generateCfCode();
+    methodBuilder
+        .setName(emulatedMethod.getName())
+        .setProto(emulatedMethod.getProto())
+        .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+        .setCode(
+            emulatedInterfaceMethod ->
+                new EmulateDispatchSyntheticCfCodeProvider(
+                        emulatedMethod.getHolderType(),
+                        companionMethod,
+                        itfMethod,
+                        extraDispatchCases,
+                        appView)
+                    .generateCfCode());
+  }
+
+  private void synthesizeEmulatedInterfaceMethodFromMachineSpecification(
+      ProgramMethod method,
+      DexProgramClass theInterface,
+      DexType dispatchType,
+      SyntheticMethodBuilder methodBuilder) {
+    EmulatedInterfaceDescriptor emulatedInterfaceDescriptor =
+        appView
+            .options()
+            .testing
+            .machineDesugaredLibrarySpecification
+            .getRewritingFlags()
+            .getEmulatedInterfaces()
+            .get(theInterface.type);
+    EmulatedDispatchMethodDescriptor descriptor =
+        emulatedInterfaceDescriptor.getEmulatedMethods().get(method.getReference());
+    DexMethod emulatedMethod = emulatedMethod(descriptor.getEmulatedDispatchMethod(), dispatchType);
+    DexMethod itfMethod = interfaceMethod(descriptor.getInterfaceMethod());
+    // TODO(b/184026720): Adapt to use the forwarding method.
+    DerivedMethod forwardingMethod = descriptor.getForwardingMethod();
+    assert forwardingMethod.getHolderKind() == SyntheticKind.COMPANION_CLASS;
+    assert forwardingMethod.getMethod() == method.getReference();
+    DexMethod companionMethod =
+        helper.ensureDefaultAsMethodOfProgramCompanionClassStub(method).getReference();
+    LinkedHashMap<DexType, DexMethod> extraDispatchCases = resolveDispatchCases(descriptor);
+    methodBuilder
+        .setName(descriptor.getEmulatedDispatchMethod().getName())
+        .setProto(descriptor.getEmulatedDispatchMethod().getProto())
+        .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+        .setCode(
+            emulatedInterfaceMethod ->
+                new EmulateDispatchSyntheticCfCodeProvider(
+                        emulatedMethod.getHolderType(),
+                        companionMethod,
+                        itfMethod,
+                        extraDispatchCases,
+                        appView)
+                    .generateCfCode());
+  }
+
+  private LinkedHashMap<DexType, DexMethod> resolveDispatchCases(
+      EmulatedDispatchMethodDescriptor descriptor) {
+    LinkedHashMap<DexType, DexMethod> extraDispatchCases = new LinkedHashMap<>();
+    descriptor
+        .getDispatchCases()
+        .forEach(
+            (type, derivedMethod) -> {
+              DexMethod caseMethod;
+              if (derivedMethod.getHolderKind() == null) {
+                caseMethod = derivedMethod.getMethod();
+              } else {
+                assert derivedMethod.getHolderKind() == SyntheticKind.COMPANION_CLASS;
+                ProgramMethod resolvedProgramMethod =
+                    appView
+                        .appInfoForDesugaring()
+                        .resolveMethod(derivedMethod.getMethod(), true)
+                        .getResolvedProgramMethod();
+                caseMethod =
+                    InterfaceDesugaringSyntheticHelper.defaultAsMethodOfCompanionClass(
+                        resolvedProgramMethod.getReference(), appView.dexItemFactory());
+              }
+              extraDispatchCases.put(type, caseMethod);
+            });
+    return extraDispatchCases;
   }
 
   private LinkedHashMap<DexType, DexMethod> getDispatchCases(
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/EmulateDispatchSyntheticCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/EmulateDispatchSyntheticCfCodeProvider.java
index e356796..a82579e 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/EmulateDispatchSyntheticCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/EmulateDispatchSyntheticCfCodeProvider.java
@@ -32,7 +32,6 @@
 public class EmulateDispatchSyntheticCfCodeProvider extends SyntheticCfCodeProvider {
 
   private final DexMethod forwardingMethod;
-  private final DexType receiverType;
   private final DexMethod interfaceMethod;
   private final LinkedHashMap<DexType, DexMethod> extraDispatchCases;
 
@@ -44,13 +43,13 @@
       AppView<?> appView) {
     super(appView, holder);
     this.forwardingMethod = forwardingMethod;
-    this.receiverType = forwardingMethod.getParameter(0);
     this.interfaceMethod = interfaceMethod;
     this.extraDispatchCases = extraDispatchCases;
   }
 
   @Override
   public CfCode generateCfCode() {
+    DexType receiverType = forwardingMethod.getParameter(0);
     List<CfInstruction> instructions = new ArrayList<>();
     CfLabel[] labels = new CfLabel[extraDispatchCases.size() + 1];
     for (int i = 0; i < labels.length; i++) {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java
index b3eb86b..d1e9746 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java
@@ -10,11 +10,17 @@
 import com.android.tools.r8.D8TestRunResult;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecification;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion.HumanToMachineSpecificationConverter;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion.LegacyToHumanSpecificationConverter;
 import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.io.IOException;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -34,14 +40,19 @@
 
   private final TestParameters parameters;
   private final boolean shrinkDesugaredLibrary;
+  private final boolean machineSpec;
 
-  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  @Parameters(name = "machine: {0}, {2}, shrink: {1}")
   public static List<Object[]> data() {
     return buildParameters(
-        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+        BooleanUtils.values(),
+        BooleanUtils.values(),
+        getTestParameters().withDexRuntimes().withAllApiLevels().build());
   }
 
-  public CustomCollectionTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+  public CustomCollectionTest(
+      boolean machineSpec, boolean shrinkDesugaredLibrary, TestParameters parameters) {
+    this.machineSpec = machineSpec;
     this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
     this.parameters = parameters;
   }
@@ -49,6 +60,22 @@
   private final String EXECUTOR =
       "com.android.tools.r8.desugar.desugaredlibrary.CustomCollectionTest$Executor";
 
+  private void setMachineSpec(InternalOptions opt) {
+    if (!machineSpec) {
+      return;
+    }
+    try {
+      HumanDesugaredLibrarySpecification human =
+          new LegacyToHumanSpecificationConverter()
+              .convert(opt.desugaredLibrarySpecification, getLibraryFile(), opt);
+      MachineDesugaredLibrarySpecification machine =
+          new HumanToMachineSpecificationConverter().convert(human, getLibraryFile(), opt);
+      opt.testing.machineDesugaredLibrarySpecification = machine;
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
   @Test
   public void testCustomCollectionD8() throws Exception {
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
@@ -56,6 +83,7 @@
         testForD8()
             .addLibraryFiles(getLibraryFile())
             .addInnerClasses(CustomCollectionTest.class)
+            .addOptionsModification(this::setMachineSpec)
             .setMinApi(parameters.getApiLevel())
             .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
             .compile()
@@ -78,6 +106,7 @@
     Path jar =
         testForD8(Backend.CF)
             .addInnerClasses(CustomCollectionTest.class)
+            .addOptionsModification(this::setMachineSpec)
             .setMinApi(parameters.getApiLevel())
             .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
             .compile()
@@ -133,6 +162,7 @@
         testForR8(Backend.DEX)
             .addLibraryFiles(getLibraryFile())
             .addInnerClasses(CustomCollectionTest.class)
+            .addOptionsModification(this::setMachineSpec)
             .setMinApi(parameters.getApiLevel())
             .addKeepClassAndMembersRules(Executor.class)
             .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)