Emulated library class in new synthetic infrastructure

Bug: 183998768
Change-Id: I5bc2252b506aa1a22a5d19fe873e07433a10c14e
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index d387d77..3fd5ce0 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -1245,30 +1245,6 @@
     return builder.build();
   }
 
-  public static DexEncodedMethod toEmulateDispatchLibraryMethod(
-      DexType interfaceType,
-      DexMethod newMethod,
-      DexMethod companionMethod,
-      DexMethod libraryMethod,
-      List<Pair<DexType, DexMethod>> extraDispatchCases,
-      AppView<?> appView) {
-    MethodAccessFlags accessFlags =
-        MethodAccessFlags.fromSharedAccessFlags(
-            Constants.ACC_SYNTHETIC | Constants.ACC_STATIC | Constants.ACC_PUBLIC, false);
-    CfCode code =
-        new EmulateInterfaceSyntheticCfCodeProvider(
-                interfaceType, companionMethod, libraryMethod, extraDispatchCases, appView)
-            .generateCfCode();
-    return new DexEncodedMethod(
-        newMethod,
-        accessFlags,
-        MethodTypeSignature.noSignature(),
-        DexAnnotationSet.empty(),
-        ParameterAnnotationsList.empty(),
-        code,
-        true);
-  }
-
   public ProgramMethod toStaticForwardingBridge(
       DexProgramClass holder, DexMethod newMethod, DexItemFactory dexItemFactory) {
     assert isPrivate()
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
index 4f4673a..a6a5984 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexApplication;
@@ -42,6 +43,7 @@
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.synthetic.EmulateInterfaceSyntheticCfCodeProvider;
 import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringDiagnostic;
@@ -677,13 +679,25 @@
           appView
               .dexItemFactory()
               .createMethod(dispatchHolder, desugarMethod.proto, emulatedDispatchMethod.getName());
-      return DexEncodedMethod.toEmulateDispatchLibraryMethod(
-          emulatedDispatchMethod.getHolderType(),
+      MethodAccessFlags accessFlags =
+          MethodAccessFlags.fromSharedAccessFlags(
+              Constants.ACC_SYNTHETIC | Constants.ACC_STATIC | Constants.ACC_PUBLIC, false);
+      CfCode code =
+          new EmulateInterfaceSyntheticCfCodeProvider(
+                  emulatedDispatchMethod.getHolderType(),
+                  desugarMethod,
+                  itfMethod,
+                  Collections.emptyList(),
+                  appView)
+              .generateCfCode();
+      return new DexEncodedMethod(
           newMethod,
-          desugarMethod,
-          itfMethod,
-          Collections.emptyList(),
-          appView);
+          accessFlags,
+          MethodTypeSignature.noSignature(),
+          DexAnnotationSet.empty(),
+          ParameterAnnotationsList.empty(),
+          code,
+          true);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceProcessor.java
index 9efdc49..eac8a2b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceProcessor.java
@@ -3,21 +3,22 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.desugar.itf;
 
+import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.ClassAccessFlags;
-import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexApplication.Builder;
 import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedField;
 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.DexString;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.GenericSignature;
 import com.android.tools.r8.graph.GenericSignature.ClassSignature;
-import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.synthetic.EmulateInterfaceSyntheticCfCodeProvider;
+import com.android.tools.r8.synthesis.SyntheticMethodBuilder;
+import com.android.tools.r8.synthesis.SyntheticNaming;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.Pair;
@@ -39,8 +40,6 @@
   private final InterfaceMethodRewriter rewriter;
   private final Map<DexType, DexType> emulatedInterfaces;
   private final Map<DexType, List<DexType>> emulatedInterfacesHierarchy;
-  // All created emulated interface classes indexed by emulated interface type.
-  final Map<DexProgramClass, DexProgramClass> syntheticClasses = new IdentityHashMap<>();
 
   EmulatedInterfaceProcessor(AppView<?> appView, InterfaceMethodRewriter rewriter) {
     this.appView = appView;
@@ -135,125 +134,118 @@
     return newMethods;
   }
 
-  void generateEmulateInterfaceLibrary(DexProgramClass emulatedInterface) {
+  DexProgramClass ensureEmulateInterfaceLibrary(
+      DexProgramClass emulatedInterface, ProgramMethodSet synthesizedMethods) {
     assert rewriter.isEmulatedInterface(emulatedInterface.type);
-    DexProgramClass theProgramInterface = emulatedInterface.asProgramClass();
-    DexProgramClass synthesizedClass = synthesizeEmulateInterfaceLibraryClass(theProgramInterface);
-    assert synthesizedClass != null;
-    syntheticClasses.put(emulatedInterface, synthesizedClass);
-  }
-
-  private DexProgramClass synthesizeEmulateInterfaceLibraryClass(DexProgramClass theInterface) {
-    List<DexEncodedMethod> emulationMethods = new ArrayList<>();
-    theInterface.forEachProgramMethodMatching(
-        DexEncodedMethod::isDefaultMethod,
-        method -> {
-          DexMethod libraryMethod =
-              method
-                  .getReference()
-                  .withHolder(emulatedInterfaces.get(theInterface.type), appView.dexItemFactory());
-          DexMethod companionMethod =
-              method.getAccessFlags().isStatic()
-                  ? rewriter.staticAsMethodOfCompanionClass(method)
-                  : rewriter.defaultAsMethodOfCompanionClass(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 = emulatedInterfacesHierarchy.get(theInterface.type);
-          List<Pair<DexType, DexMethod>> extraDispatchCases = new ArrayList<>();
-          // In practice, there is usually a single case (except for tests),
-          // so we do not bother to make the following loop more clever.
-          Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
-              appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember();
-          for (DexString methodName : retargetCoreLibMember.keySet()) {
-            if (method.getName() == methodName) {
-              for (DexType inType : retargetCoreLibMember.get(methodName).keySet()) {
-                DexClass inClass = appView.definitionFor(inType);
-                if (inClass != null && implementsInterface(inClass, theInterface.type)) {
-                  extraDispatchCases.add(
-                      new Pair<>(
-                          inType,
-                          appView
-                              .dexItemFactory()
-                              .createMethod(
-                                  retargetCoreLibMember.get(methodName).get(inType),
-                                  appView
-                                      .dexItemFactory()
-                                      .protoWithDifferentFirstParameter(
-                                          companionMethod.proto, inType),
-                                  method.getName())));
-                }
-              }
-            }
-          }
-          if (subInterfaces != null) {
-            for (int i = subInterfaces.size() - 1; i >= 0; i--) {
-              DexClass subInterfaceClass = appView.definitionFor(subInterfaces.get(i));
-              assert subInterfaceClass != null;
-              // Else computation of subInterface would have failed.
-              // if the method is implemented, extra dispatch is required.
-              DexEncodedMethod result =
-                  subInterfaceClass.lookupVirtualMethod(method.getReference());
-              if (result != null && !result.isAbstract()) {
-                extraDispatchCases.add(
-                    new Pair<>(
-                        subInterfaceClass.type,
-                        appView
-                            .dexItemFactory()
-                            .createMethod(
-                                rewriter.getCompanionClassType(subInterfaceClass.type),
-                                appView
-                                    .dexItemFactory()
-                                    .protoWithDifferentFirstParameter(
-                                        companionMethod.proto, subInterfaceClass.type),
-                                companionMethod.name)));
-              }
-            }
-          }
-          emulationMethods.add(
-              DexEncodedMethod.toEmulateDispatchLibraryMethod(
-                  method.getHolderType(),
-                  rewriter.emulateInterfaceLibraryMethod(method),
-                  companionMethod,
-                  libraryMethod,
-                  extraDispatchCases,
-                  appView));
-        });
-    assert !emulationMethods.isEmpty();
     DexType emulateLibraryClassType =
         InterfaceMethodRewriter.getEmulateLibraryInterfaceClassType(
-            theInterface.type, appView.dexItemFactory());
-    ClassAccessFlags emulateLibraryClassFlags = theInterface.accessFlags.copy();
-    emulateLibraryClassFlags.unsetAbstract();
-    emulateLibraryClassFlags.unsetInterface();
-    emulateLibraryClassFlags.unsetAnnotation();
-    emulateLibraryClassFlags.setFinal();
-    emulateLibraryClassFlags.setSynthetic();
-    emulateLibraryClassFlags.setPublic();
-    DexProgramClass clazz =
-        new DexProgramClass(
-            emulateLibraryClassType,
-            null,
-            new SynthesizedOrigin("interface desugaring (libs)", getClass()),
-            emulateLibraryClassFlags,
-            appView.dexItemFactory().objectType,
-            DexTypeList.empty(),
-            theInterface.sourceFile,
-            null,
-            Collections.emptyList(),
-            null,
-            Collections.emptyList(),
-            ClassSignature.noSignature(),
-            DexAnnotationSet.empty(),
-            DexEncodedField.EMPTY_ARRAY,
-            DexEncodedField.EMPTY_ARRAY,
-            // All synthesized methods are static in this case.
-            emulationMethods.toArray(DexEncodedMethod.EMPTY_ARRAY),
-            DexEncodedMethod.EMPTY_ARRAY,
-            appView.dexItemFactory().getSkipNameValidationForTesting(),
-            DexProgramClass::checksumFromType);
-    return clazz;
+            emulatedInterface.type, appView.dexItemFactory());
+    DexProgramClass emulateInterfaceClass =
+        appView
+            .getSyntheticItems()
+            .ensureFixedClassWhileMigrating(
+                SyntheticNaming.SyntheticKind.EMULATED_INTERFACE_CLASS,
+                emulateLibraryClassType,
+                emulatedInterface,
+                appView,
+                builder ->
+                    emulatedInterface.forEachProgramMethodMatching(
+                        DexEncodedMethod::isDefaultMethod,
+                        method ->
+                            builder.addMethod(
+                                methodBuilder ->
+                                    synthesizeEmulatedInterfaceMethod(
+                                        method, emulatedInterface, methodBuilder))));
+    emulateInterfaceClass.forEachProgramMethod(synthesizedMethods::add);
+    return emulateInterfaceClass;
+  }
+
+  private void synthesizeEmulatedInterfaceMethod(
+      ProgramMethod method, DexProgramClass theInterface, SyntheticMethodBuilder methodBuilder) {
+    DexMethod libraryMethod =
+        method
+            .getReference()
+            .withHolder(emulatedInterfaces.get(theInterface.type), appView.dexItemFactory());
+    DexMethod companionMethod =
+        method.getAccessFlags().isStatic()
+            ? rewriter.staticAsMethodOfCompanionClass(method)
+            : rewriter.defaultAsMethodOfCompanionClass(method);
+    List<Pair<DexType, DexMethod>> extraDispatchCases =
+        getDispatchCases(method, theInterface, companionMethod);
+    DexMethod emulatedMethod = rewriter.emulateInterfaceLibraryMethod(method);
+    methodBuilder
+        .setName(emulatedMethod.getName())
+        .setProto(emulatedMethod.getProto())
+        .setCode(
+            theMethod ->
+                new EmulateInterfaceSyntheticCfCodeProvider(
+                        theMethod.getHolderType(),
+                        companionMethod,
+                        libraryMethod,
+                        extraDispatchCases,
+                        appView)
+                    .generateCfCode())
+        .setAccessFlags(
+            MethodAccessFlags.fromSharedAccessFlags(
+                Constants.ACC_SYNTHETIC | Constants.ACC_STATIC | Constants.ACC_PUBLIC, false));
+  }
+
+  private List<Pair<DexType, DexMethod>> getDispatchCases(
+      ProgramMethod method, DexProgramClass theInterface, DexMethod companionMethod) {
+    // 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 = emulatedInterfacesHierarchy.get(theInterface.type);
+    List<Pair<DexType, DexMethod>> extraDispatchCases = new ArrayList<>();
+    // In practice, there is usually a single case (except for tests),
+    // so we do not bother to make the following loop more clever.
+    Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
+        appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember();
+    for (DexString methodName : retargetCoreLibMember.keySet()) {
+      if (method.getName() == methodName) {
+        for (DexType inType : retargetCoreLibMember.get(methodName).keySet()) {
+          DexClass inClass = appView.definitionFor(inType);
+          if (inClass != null && implementsInterface(inClass, theInterface.type)) {
+            extraDispatchCases.add(
+                new Pair<>(
+                    inType,
+                    appView
+                        .dexItemFactory()
+                        .createMethod(
+                            retargetCoreLibMember.get(methodName).get(inType),
+                            appView
+                                .dexItemFactory()
+                                .protoWithDifferentFirstParameter(companionMethod.proto, inType),
+                            method.getName())));
+          }
+        }
+      }
+    }
+    if (subInterfaces != null) {
+      for (int i = subInterfaces.size() - 1; i >= 0; i--) {
+        DexClass subInterfaceClass = appView.definitionFor(subInterfaces.get(i));
+        assert subInterfaceClass != null;
+        // Else computation of subInterface would have failed.
+        // if the method is implemented, extra dispatch is required.
+        DexEncodedMethod result = subInterfaceClass.lookupVirtualMethod(method.getReference());
+        if (result != null && !result.isAbstract()) {
+          extraDispatchCases.add(
+              new Pair<>(
+                  subInterfaceClass.type,
+                  appView
+                      .dexItemFactory()
+                      .createMethod(
+                          rewriter.getCompanionClassType(subInterfaceClass.type),
+                          appView
+                              .dexItemFactory()
+                              .protoWithDifferentFirstParameter(
+                                  companionMethod.proto, subInterfaceClass.type),
+                          companionMethod.name)));
+        }
+      }
+    }
+    return extraDispatchCases;
   }
 
   private boolean implementsInterface(DexClass clazz, DexType interfaceType) {
@@ -279,7 +271,7 @@
       return;
     }
     if (needsEmulateInterfaceLibrary(emulatedInterface)) {
-      generateEmulateInterfaceLibrary(emulatedInterface);
+      ensureEmulateInterfaceLibrary(emulatedInterface, synthesizedMethods);
     }
   }
 
@@ -291,7 +283,6 @@
   public void finalizeProcessing(Builder<?> builder, ProgramMethodSet synthesizedMethods) {
     warnMissingEmulatedInterfaces();
     if (!appView.options().isDesugaredLibraryCompilation()) {
-      assert syntheticClasses.isEmpty();
       return;
     }
     for (DexType interfaceType : emulatedInterfaces.keySet()) {
@@ -304,12 +295,6 @@
         }
       }
     }
-    syntheticClasses.forEach(
-        (interfaceClass, synthesizedClass) -> {
-          builder.addSynthesizedClass(synthesizedClass);
-          appView.appInfo().addSynthesizedClass(synthesizedClass, interfaceClass);
-          synthesizedClass.forEachProgramMethod(synthesizedMethods::add);
-        });
     // TODO(b/183918843): Investigate what to do for the filtering, the minimum would be to make
     // the rewriting rule explicit instead of using the synthesized class prefix.
     filterEmulatedInterfaceSubInterfaces(builder);
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index 7bac324..8b19177 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -25,6 +25,7 @@
     // Class synthetics.
     RECORD_TAG("", false, true, true),
     COMPANION_CLASS("-CC", false, true),
+    EMULATED_INTERFACE_CLASS("-EL", false, true),
     LAMBDA("Lambda", false),
     INIT_TYPE_ARGUMENT("-IA", false, true),
     HORIZONTAL_INIT_TYPE_ARGUMENT_1(SYNTHETIC_CLASS_SEPARATOR + "IA$1", false, true),