Generate conversion wrappers in the desugared library.

Bug: 157681341
Bug: 158645207
Change-Id: I558fcb4a53c4ae97cb1a27629f7d0572c41fbb1c
diff --git a/src/main/java/com/android/tools/r8/L8.java b/src/main/java/com/android/tools/r8/L8.java
index 4bef8cec..ddca96a 100644
--- a/src/main/java/com/android/tools/r8/L8.java
+++ b/src/main/java/com/android/tools/r8/L8.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.LazyLoadedDexApplication;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.desugar.PrefixRewritingMapper;
 import com.android.tools.r8.jar.CfApplicationWriter;
@@ -118,11 +119,16 @@
       // on it.
       options.enableLoadStoreOptimization = false;
 
-      DexApplication app = new ApplicationReader(inputApp, options, timing).read(executor);
+      LazyLoadedDexApplication lazyApp =
+          new ApplicationReader(inputApp, options, timing).read(executor);
+      // Store a direct lookup to determine actual library definitions for wrapper generation.
+      options.desugaredLibraryBootclasspathDefinitions =
+          t -> lazyApp.libraryDefintionFor(t) != null;
+
       PrefixRewritingMapper rewritePrefix =
           options.desugaredLibraryConfiguration.createPrefixRewritingMapper(options);
 
-      app = new L8TreePruner(options).prune(app, rewritePrefix);
+      DexApplication app = new L8TreePruner(options).prune(lazyApp, rewritePrefix);
       AppInfo appInfo = new AppInfo(app);
 
       AppView<?> appView = AppView.createForL8(appInfo, options, rewritePrefix);
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index ede2193..ca70ab0 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -74,11 +74,11 @@
     this.inputApp = inputApp;
   }
 
-  public DexApplication read() throws IOException {
+  public LazyLoadedDexApplication read() throws IOException {
     return read((StringResource) null);
   }
 
-  public DexApplication read(StringResource proguardMap) throws IOException {
+  public LazyLoadedDexApplication read(StringResource proguardMap) throws IOException {
     ExecutorService executor = Executors.newSingleThreadExecutor();
     try {
       return read(proguardMap, executor);
@@ -87,20 +87,20 @@
     }
   }
 
-  public final DexApplication read(ExecutorService executorService) throws IOException {
+  public final LazyLoadedDexApplication read(ExecutorService executorService) throws IOException {
     return read(
         null, executorService, ProgramClassCollection.defaultConflictResolver(options.reporter));
   }
 
-  public final DexApplication read(StringResource proguardMap, ExecutorService executorService)
-      throws IOException {
+  public final LazyLoadedDexApplication read(
+      StringResource proguardMap, ExecutorService executorService) throws IOException {
     return read(
         proguardMap,
         executorService,
         ProgramClassCollection.defaultConflictResolver(options.reporter));
   }
 
-  public final DexApplication read(
+  public final LazyLoadedDexApplication read(
       StringResource proguardMap,
       ExecutorService executorService,
       ProgramClassConflictResolver resolver)
diff --git a/src/main/java/com/android/tools/r8/dex/CodeToKeep.java b/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
index f327f21..9eb7caa 100644
--- a/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
+++ b/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
@@ -64,7 +64,14 @@
     }
 
     private boolean shouldKeep(DexType type) {
-      return namingLens.prefixRewrittenType(type) != null || potentialTypesToKeep.contains(type);
+      return namingLens.prefixRewrittenType(type) != null
+          || potentialTypesToKeep.contains(type)
+          // TODO(b/158632510): This should prefix match on DexString.
+          || type.toDescriptorString()
+              .startsWith(
+                  "L"
+                      + options.desugaredLibraryConfiguration
+                          .getSynthesizedLibraryClassesPackagePrefix());
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
index 9e96ab4..7127794 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
@@ -88,6 +88,11 @@
     return programClasses.get(type);
   }
 
+  public DexLibraryClass libraryDefintionFor(DexType type) {
+    assert type.isClassType() : "Cannot lookup library definition for type: " + type;
+    return libraryClasses.get(type);
+  }
+
   static class AllClasses {
 
     // Mapping of all types to their definitions.
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
index 96b5de8..1fa68dd 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
@@ -4,28 +4,23 @@
 package com.android.tools.r8.graph.analysis;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClasspathClass;
-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.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter.Mode;
-import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
-import java.util.ArrayList;
 import java.util.IdentityHashMap;
-import java.util.List;
 import java.util.Map;
+import java.util.function.Consumer;
 
 public class DesugaredLibraryConversionWrapperAnalysis extends EnqueuerAnalysis
     implements EnqueuerInvokeAnalysis {
 
   private final AppView<?> appView;
   private final DesugaredLibraryAPIConverter converter;
-  private Map<DexType, DexProgramClass> synthesizedWrappers = new IdentityHashMap<>();
+  private Map<DexType, DexClasspathClass> synthesizedWrappers = new IdentityHashMap<>();
 
   public DesugaredLibraryConversionWrapperAnalysis(AppView<?> appView) {
     this.appView = appView;
@@ -71,34 +66,7 @@
     return converter.generateCallbackMethods();
   }
 
-  public List<DexProgramClass> generateWrappers() {
-    return converter.synthesizeWrappers(synthesizedWrappers);
-  }
-
-  // Generate a mock classpath class for all vivified types.
-  // Types will be available at runtime in the desugared library dex file.
-  public List<DexClasspathClass> generateWrappersSuperTypeMock(List<DexProgramClass> wrappers) {
-    List<DexClasspathClass> classpathClasses = new ArrayList<>();
-    for (DexProgramClass wrapper : wrappers) {
-      boolean mockIsInterface = wrapper.interfaces.size() == 1;
-      DexType mockType = mockIsInterface ? wrapper.interfaces.values[0] : wrapper.superType;
-      if (appView.definitionFor(mockType) == null) {
-        assert DesugaredLibraryAPIConverter.isVivifiedType(mockType);
-        assert wrapper.instanceFields().size() == 1;
-        DexType typeToMock = wrapper.instanceFields().get(0).field.type;
-        DexClass classToMock = appView.definitionFor(typeToMock);
-        assert classToMock != null;
-        DexClasspathClass mockedSuperClass =
-            converter.synthesizeClasspathMock(classToMock, mockType, mockIsInterface);
-        classpathClasses.add(mockedSuperClass);
-        for (DexEncodedMethod virtualMethod : wrapper.virtualMethods()) {
-          // The mock is generated at the end of the enqueuing phase, so we need to manually set the
-          // library override.
-          assert mockedSuperClass.lookupVirtualMethod(virtualMethod.method) != null;
-          virtualMethod.setLibraryMethodOverride(OptionalBool.TRUE);
-        }
-      }
-    }
-    return classpathClasses;
+  public void generateWrappers(Consumer<DexClasspathClass> synthesizedCallback) {
+    converter.synthesizeWrappers(synthesizedWrappers, synthesizedCallback);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
index a91c1aa..277ba09 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
@@ -45,8 +45,8 @@
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
+import java.util.function.Consumer;
 
-// TODO(b/134732760): In progress.
 // I convert library calls with desugared parameters/return values so they can work normally.
 // In the JSON of the desugared library, one can specify conversions between desugared and
 // non-desugared types. If no conversion is specified, D8/R8 simply generate wrapper classes around
@@ -287,7 +287,9 @@
     }
     SortedProgramMethodSet callbacks = generateCallbackMethods();
     irConverter.processMethodsConcurrently(callbacks, executorService);
-    wrapperSynthesizor.finalizeWrappersForD8(builder, irConverter, executorService);
+    if (appView.options().isDesugaredLibraryCompilation()) {
+      wrapperSynthesizor.finalizeWrappersForD8(builder, irConverter, executorService);
+    }
   }
 
   public SortedProgramMethodSet generateCallbackMethods() {
@@ -313,14 +315,10 @@
     return allCallbackMethods;
   }
 
-  public List<DexProgramClass> synthesizeWrappers(
-      Map<DexType, DexProgramClass> synthesizedWrappers) {
-    return wrapperSynthesizor.synthesizeWrappers(synthesizedWrappers);
-  }
-
-  public DexClasspathClass synthesizeClasspathMock(
-      DexClass classToMock, DexType mockType, boolean mockIsInterface) {
-    return wrapperSynthesizor.synthesizeClasspathMock(classToMock, mockType, mockIsInterface);
+  public void synthesizeWrappers(
+      Map<DexType, DexClasspathClass> synthesizedWrappers,
+      Consumer<DexClasspathClass> synthesizedCallback) {
+    wrapperSynthesizor.synthesizeWrappersForClasspath(synthesizedWrappers, synthesizedCallback);
   }
 
   private ProgramMethod generateCallbackMethod(
@@ -351,6 +349,11 @@
   }
 
   public void reportInvalidInvoke(DexType type, DexMethod invokedMethod, String debugString) {
+    if (appView.options().isDesugaredLibraryCompilation()) {
+      // TODO(b/158645207): If wrappers are exactly specified this should fail both for normal
+      //  compilation and L8.
+      return;
+    }
     DexType desugaredType = appView.rewritePrefix.rewrittenType(type, appView);
     appView
         .options()
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
index a7d5252..60cf4c6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
@@ -29,7 +29,7 @@
 
   public static final String FALL_BACK_SYNTHESIZED_CLASSES_PACKAGE_PREFIX = "j$/";
 
-  // TODO(b/134732760): should use DexString, DexType, DexMethod or so on when possible.
+  // TODO(b/158632510): should use DexString, DexType, DexMethod or so on when possible.
   private final AndroidApiLevel requiredCompilationAPILevel;
   private final boolean libraryCompilation;
   private final String synthesizedLibraryClassesPackagePrefix;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java
index a659cc6..8896170 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java
@@ -107,6 +107,22 @@
       }
       parseFlags(jsonFlagSet.getAsJsonObject());
     }
+    if (libraryCompilation && formatVersion <= 4) {
+      // Read custom conversions from program flags.
+      for (JsonElement jsonFlagSet : jsonConfig.getAsJsonArray("program_flags")) {
+        JsonObject jsonFlagSetObject = jsonFlagSet.getAsJsonObject();
+        int api_level_below_or_equal = jsonFlagSetObject.get("api_level_below_or_equal").getAsInt();
+        if (minAPILevel > api_level_below_or_equal) {
+          continue;
+        }
+        String customConversionKey = "custom_conversion";
+        if (jsonFlagSetObject.has(customConversionKey)) {
+          JsonObject backportedFlags = new JsonObject();
+          backportedFlags.add(customConversionKey, jsonFlagSetObject.get(customConversionKey));
+          parseFlags(backportedFlags);
+        }
+      }
+    }
     if (jsonConfig.has("shrinker_config")) {
       JsonArray jsonKeepRules = jsonConfig.get("shrinker_config").getAsJsonArray();
       List<String> extraKeepRules = new ArrayList<>(jsonKeepRules.size());
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
index 5fbf3df..c911d6b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
@@ -9,6 +9,7 @@
 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.ClassKind;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexApplication;
@@ -33,7 +34,6 @@
 import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterWrapperCfCodeProvider;
 import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterWrapperConversionCfCodeProvider;
 import com.android.tools.r8.origin.SynthesizedOrigin;
-import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
@@ -48,6 +48,8 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
 
 // I am responsible for the generation of wrappers used to call library APIs when desugaring
 // libraries. Wrappers can be both ways, wrapping the desugarType as a type, or the type as
@@ -115,7 +117,13 @@
   DesugaredLibraryWrapperSynthesizer(AppView<?> appView, DesugaredLibraryAPIConverter converter) {
     this.appView = appView;
     this.factory = appView.dexItemFactory();
-    dexWrapperPrefixString = "L" + appView.options().synthesizedClassPrefix + WRAPPER_PREFIX;
+    dexWrapperPrefixString =
+        "L"
+            + appView
+                .options()
+                .desugaredLibraryConfiguration
+                .getSynthesizedLibraryClassesPackagePrefix()
+            + WRAPPER_PREFIX;
     dexWrapperPrefixDexString = factory.createString(dexWrapperPrefixString);
     this.converter = converter;
     this.vivifiedSourceFile = appView.dexItemFactory().createString("vivified");
@@ -126,11 +134,96 @@
   }
 
   boolean canGenerateWrapper(DexType type) {
-    DexClass dexClass = appView.definitionFor(type);
-    if (dexClass == null || dexClass.accessFlags.isFinal()) {
+    return typeWrappers.containsKey(type) || canGenerateWrapper(appView.definitionFor(type));
+  }
+
+  /**
+   * Wrappers can/are generated for all types that appear to support wrapper generation. The
+   * assumptions right now are that the 'type' must be specified in the bootclasspath library, that
+   * means the library component for any build (all of D8, R8 *and* L8). For L8 the type lookup
+   * needs to bypass the usual definitionsFor and look directly in the library on the initial app.
+   *
+   * <p>In addition (or rather before doing the lookup) the class to be wrapped must satisfy several
+   * requirements: its super types can also be wrapped, it has no instance fields, all instance
+   * methods are non-final and public and the types in them are also library defined. If all of that
+   * holds wrappers for the type are created.
+   *
+   * <p>TODO(b/158645207): Consider adding wrappers to the spec and have this be precise.
+   */
+  boolean canGenerateWrapper(DexClass clazz) {
+    if (typeWrappers.containsKey(clazz.type)) {
+      return true;
+    }
+    if (clazz == null
+        || clazz.accessFlags.isFinal()
+        || !clazz.accessFlags.isPublic()
+        || clazz.isAnonymousClass()) {
       return false;
     }
-    return dexClass.isLibraryClass() || appView.options().isDesugaredLibraryCompilation();
+    if (!clazz.isLibraryClass()) {
+      if (!appView.options().isDesugaredLibraryCompilation()) {
+        return false;
+      }
+      // If doing the desugared library compilation, then the class to be wrapped must be defined on
+      // the actual bootclasspath (not the desugared library). The definition lookup must thus
+      // bypass the usual lookup in this case.
+      if (!appView.options().desugaredLibraryBootclasspathDefinitions.test(clazz.type)) {
+        return false;
+      }
+    }
+    if (clazz.superType == null) {
+      return false;
+    }
+    if (clazz.superType != factory.objectType) {
+      if (!canGenerateWrapper(clazz.superType)) {
+        return false;
+      }
+    }
+    for (DexType iface : clazz.interfaces.values) {
+      if (!canGenerateWrapper(iface)) {
+        return false;
+      }
+    }
+    for (DexEncodedField field : clazz.instanceFields()) {
+      return false;
+    }
+    for (DexEncodedMethod method : clazz.virtualMethods()) {
+      if (method.isFinal() || !method.isPublic()) {
+        return false;
+      }
+      if (isUndefinedOrNonLibraryType(method.proto().returnType)) {
+        return false;
+      }
+      for (DexType param : method.proto().parameters.values) {
+        if (isUndefinedOrNonLibraryType(param)) {
+          return false;
+        }
+      }
+    }
+    return true;
+  }
+
+  private boolean isUndefinedOrNonLibraryType(DexType type) {
+    if (type.isPrimitiveType()) {
+      return false;
+    }
+    DexType baseType = type.toBaseType(appView.dexItemFactory());
+    if (!baseType.isClassType()) {
+      return false;
+    }
+    DexClass clazz = appView.definitionFor(baseType);
+    if (clazz == null) {
+      return true;
+    }
+    if (clazz.isLibraryClass()) {
+      return false;
+    }
+    if (!appView.options().isDesugaredLibraryCompilation()) {
+      return true;
+    }
+    // The wrapper referenced type is also considered undefined if not on the bootclasspath.
+    // TODO(b/158718959): Notice use of 'type' vs 'clazz.type' as emulated interfaces are mutated.
+    return !appView.options().desugaredLibraryBootclasspathDefinitions.test(type);
   }
 
   DexType getTypeWrapper(DexType type) {
@@ -155,9 +248,10 @@
     return wrappers.computeIfAbsent(
         type,
         t -> {
+          assert canGenerateWrapper(type);
           DexType wrapperType = createWrapperType(type, suffix);
           assert converter.canGenerateWrappersAndCallbacks()
-                  || appView.definitionForProgramType(wrapperType) != null
+                  || appView.definitionFor(wrapperType).isClasspathClass()
               : "Wrapper " + wrapperType + " should have been generated in the enqueuer.";
           return wrapperType;
         });
@@ -176,10 +270,12 @@
     return DesugaredLibraryAPIConverter.vivifiedTypeFor(type, appView);
   }
 
-  private DexProgramClass generateTypeWrapper(DexClass dexClass, DexType typeWrapperType) {
+  private DexClass generateTypeWrapper(
+      ClassKind classKind, DexClass dexClass, DexType typeWrapperType) {
     DexType type = dexClass.type;
     DexEncodedField wrapperField = synthesizeWrappedValueEncodedField(typeWrapperType, type);
     return synthesizeWrapper(
+        classKind,
         vivifiedTypeFor(type),
         dexClass,
         synthesizeVirtualMethodsForTypeWrapper(dexClass, wrapperField),
@@ -187,12 +283,13 @@
         wrapperField);
   }
 
-  private DexProgramClass generateVivifiedTypeWrapper(
-      DexClass dexClass, DexType vivifiedTypeWrapperType) {
+  private DexClass generateVivifiedTypeWrapper(
+      ClassKind classKind, DexClass dexClass, DexType vivifiedTypeWrapperType) {
     DexType type = dexClass.type;
     DexEncodedField wrapperField =
         synthesizeWrappedValueEncodedField(vivifiedTypeWrapperType, vivifiedTypeFor(type));
     return synthesizeWrapper(
+        classKind,
         type,
         dexClass,
         synthesizeVirtualMethodsForVivifiedTypeWrapper(dexClass, wrapperField),
@@ -200,7 +297,8 @@
         wrapperField);
   }
 
-  private DexProgramClass synthesizeWrapper(
+  private DexClass synthesizeWrapper(
+      ClassKind classKind,
       DexType wrappingType,
       DexClass clazz,
       DexEncodedMethod[] virtualMethods,
@@ -210,9 +308,9 @@
     DexType superType = isItf ? factory.objectType : wrappingType;
     DexTypeList interfaces =
         isItf ? new DexTypeList(new DexType[] {wrappingType}) : DexTypeList.empty();
-    return new DexProgramClass(
+    return classKind.create(
         wrapperField.holder(),
-        null,
+        Kind.CF,
         new SynthesizedOrigin("Desugared library API Converter", getClass()),
         ClassAccessFlags.fromSharedAccessFlags(
             Constants.ACC_FINAL | Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC),
@@ -229,8 +327,7 @@
         new DexEncodedMethod[] {synthesizeConstructor(wrapperField.field), conversionMethod},
         virtualMethods,
         factory.getSkipNameValidationForTesting(),
-        DexProgramClass::checksumFromType,
-        Collections.emptyList());
+        DexProgramClass::checksumFromType);
   }
 
   private DexEncodedMethod[] synthesizeVirtualMethodsForVivifiedTypeWrapper(
@@ -325,51 +422,6 @@
     return finalizeWrapperMethods(generatedMethods, finalMethods);
   }
 
-  private DexEncodedMethod[] synthesizeVirtualMethodsForClasspathMock(
-      DexClass dexClass, DexType mockType) {
-    List<DexEncodedMethod> dexMethods = allImplementedMethods(dexClass);
-    List<DexEncodedMethod> generatedMethods = new ArrayList<>();
-    // Generate only abstract methods for library override detection.
-    for (DexEncodedMethod dexEncodedMethod : dexMethods) {
-      DexClass holderClass = appView.definitionFor(dexEncodedMethod.holder());
-      assert holderClass != null || appView.options().isDesugaredLibraryCompilation();
-      if (!dexEncodedMethod.isFinal()) {
-        DexMethod methodToInstall =
-            DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature(
-                dexEncodedMethod.method, mockType, appView);
-        DexEncodedMethod newDexEncodedMethod =
-            newSynthesizedMethod(methodToInstall, dexEncodedMethod, null);
-        generatedMethods.add(newDexEncodedMethod);
-      }
-    }
-    return generatedMethods.toArray(DexEncodedMethod.EMPTY_ARRAY);
-  }
-
-  DexClasspathClass synthesizeClasspathMock(
-      DexClass classToMock, DexType mockType, boolean mockIsInterface) {
-    return new DexClasspathClass(
-        mockType,
-        Kind.CF,
-        new SynthesizedOrigin("Desugared library wrapper super class ", getClass()),
-        ClassAccessFlags.fromDexAccessFlags(
-            Constants.ACC_SYNTHETIC
-                | Constants.ACC_PUBLIC
-                | (BooleanUtils.intValue(mockIsInterface) * Constants.ACC_INTERFACE)),
-        appView.dexItemFactory().objectType,
-        DexTypeList.empty(),
-        vivifiedSourceFile,
-        null,
-        Collections.emptyList(),
-        null,
-        Collections.emptyList(),
-        DexAnnotationSet.empty(),
-        DexEncodedField.EMPTY_ARRAY,
-        DexEncodedField.EMPTY_ARRAY,
-        DexEncodedMethod.EMPTY_ARRAY,
-        synthesizeVirtualMethodsForClasspathMock(classToMock, mockType),
-        appView.dexItemFactory().getSkipNameValidationForTesting());
-  }
-
   private DexEncodedMethod[] finalizeWrapperMethods(
       List<DexEncodedMethod> generatedMethods, Set<DexMethod> finalMethods) {
     if (finalMethods.isEmpty()) {
@@ -495,33 +547,69 @@
   void finalizeWrappersForD8(
       DexApplication.Builder<?> builder, IRConverter irConverter, ExecutorService executorService)
       throws ExecutionException {
-    List<DexProgramClass> synthesizedWrappers = synthesizeWrappers(new IdentityHashMap<>());
+    List<DexProgramClass> synthesizedWrappers = synthesizeWrappers();
     registerAndProcessWrappers(builder, irConverter, executorService, synthesizedWrappers);
   }
 
-  List<DexProgramClass> synthesizeWrappers(Map<DexType, DexProgramClass> synthesizedWrappers) {
+  private List<DexProgramClass> synthesizeWrappers() {
+    Map<DexType, DexClass> synthesizedWrappers = new IdentityHashMap<>();
     List<DexProgramClass> additions = new ArrayList<>();
-    // Generating a wrapper may require other wrappers to be generated, iterate until fix point.
-    while (synthesizedWrappers.size() != typeWrappers.size() + vivifiedTypeWrappers.size()) {
+    DesugaredLibraryConfiguration conf = appView.options().desugaredLibraryConfiguration;
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      if (conf.getCustomConversions().get(clazz.type) == null
+          && appView.rewritePrefix.hasRewrittenType(clazz.type, appView)
+          && canGenerateWrapper(clazz)) {
+        getTypeWrapper(clazz.type);
+      }
+    }
+    int total = typeWrappers.size() + vivifiedTypeWrappers.size();
+    generateWrappers(
+        ClassKind.PROGRAM,
+        synthesizedWrappers.keySet(),
+        (type, wrapper) -> {
+          synthesizedWrappers.put(type, wrapper);
+          additions.add(wrapper.asProgramClass());
+        });
+    assert total == typeWrappers.size() + vivifiedTypeWrappers.size() : "unexpected additions";
+    return additions;
+  }
+
+  void synthesizeWrappersForClasspath(
+      Map<DexType, DexClasspathClass> synthesizedWrappers,
+      Consumer<DexClasspathClass> synthesizedCallback) {
+    generateWrappers(
+        ClassKind.CLASSPATH,
+        synthesizedWrappers.keySet(),
+        (type, wrapper) -> {
+          DexClasspathClass classpathWrapper = wrapper.asClasspathClass();
+          synthesizedWrappers.put(type, classpathWrapper);
+          synthesizedCallback.accept(classpathWrapper);
+        });
+  }
+
+  void generateWrappers(
+      ClassKind classKind,
+      Set<DexType> synthesized,
+      BiConsumer<DexType, DexClass> generatedCallback) {
+    while (synthesized.size() != typeWrappers.size() + vivifiedTypeWrappers.size()) {
       for (DexType type : typeWrappers.keySet()) {
         DexType typeWrapperType = typeWrappers.get(type);
-        if (!synthesizedWrappers.containsKey(typeWrapperType)) {
-          DexProgramClass wrapper = generateTypeWrapper(getValidClassToWrap(type), typeWrapperType);
-          synthesizedWrappers.put(typeWrapperType, wrapper);
-          additions.add(wrapper);
+        if (!synthesized.contains(typeWrapperType)) {
+          DexClass wrapper =
+              generateTypeWrapper(classKind, getValidClassToWrap(type), typeWrapperType);
+          generatedCallback.accept(typeWrapperType, wrapper);
         }
       }
       for (DexType type : vivifiedTypeWrappers.keySet()) {
         DexType vivifiedTypeWrapperType = vivifiedTypeWrappers.get(type);
-        if (!synthesizedWrappers.containsKey(vivifiedTypeWrapperType)) {
-          DexProgramClass wrapper =
-              generateVivifiedTypeWrapper(getValidClassToWrap(type), vivifiedTypeWrapperType);
-          synthesizedWrappers.put(vivifiedTypeWrapperType, wrapper);
-          additions.add(wrapper);
+        if (!synthesized.contains(vivifiedTypeWrapperType)) {
+          DexClass wrapper =
+              generateVivifiedTypeWrapper(
+                  classKind, getValidClassToWrap(type), vivifiedTypeWrapperType);
+          generatedCallback.accept(vivifiedTypeWrapperType, wrapper);
         }
       }
     }
-    return additions;
   }
 
   private void registerAndProcessWrappers(
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 76c9e4c..e432e69 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -2725,7 +2725,10 @@
     Set<DexType> mainDexTypes = Sets.newIdentityHashSet();
 
     boolean isEmpty() {
-      boolean empty = syntheticInstantiations.isEmpty() && liveMethods.isEmpty();
+      boolean empty =
+          syntheticInstantiations.isEmpty()
+              && liveMethods.isEmpty()
+              && syntheticClasspathClasses.isEmpty();
       assert !empty || (liveMethodsWithKeepActions.isEmpty() && mainDexTypes.isEmpty());
       return empty;
     }
@@ -2787,6 +2790,7 @@
         enqueuer.markMethodAsTargeted(liveMethod, fakeReason);
         enqueuer.enqueueMarkMethodLiveAction(liveMethod, fakeReason);
       }
+      enqueuer.liveNonProgramTypes.addAll(syntheticClasspathClasses.values());
     }
   }
 
@@ -3099,21 +3103,8 @@
     ProgramMethodSet callbacks = desugaredLibraryWrapperAnalysis.generateCallbackMethods();
     callbacks.forEach(additions::addLiveMethod);
 
-    // Generate the wrappers.
-    List<DexProgramClass> wrappers = desugaredLibraryWrapperAnalysis.generateWrappers();
-    for (DexProgramClass wrapper : wrappers) {
-      additions.addInstantiatedClass(wrapper, null, false);
-      // Mark all methods on the wrapper as live and targeted.
-      for (DexEncodedMethod method : wrapper.methods()) {
-        additions.addLiveMethod(new ProgramMethod(wrapper, method));
-      }
-    }
-
-    // Add all vivified types as classpath classes.
-    // They will be available at runtime in the desugared library dex file.
-    desugaredLibraryWrapperAnalysis
-        .generateWrappersSuperTypeMock(wrappers)
-        .forEach(additions::addClasspathClass);
+    // Generate wrappers on classpath so types are defined.
+    desugaredLibraryWrapperAnalysis.generateWrappers(additions::addClasspathClass);
   }
 
   private void rewriteLambdaCallSites(
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 00853e0..8599b02 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -419,7 +419,8 @@
       if (outputMode == OutputMode.DexIndexed) {
         DexIndexedConsumer.ArchiveConsumer.writeResources(
             archive, getDexProgramResourcesForTesting(), getDataEntryResourcesForTesting());
-      } else if (outputMode == OutputMode.DexFilePerClassFile) {
+      } else if (outputMode == OutputMode.DexFilePerClassFile
+          || outputMode == OutputMode.DexFilePerClass) {
         List<ProgramResource> resources = getDexProgramResourcesForTesting();
         DexFilePerClassFileConsumer.ArchiveConsumer.writeResources(
             archive, resources, programResourcesMainDescriptor);
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 35e4614..f83359a 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -680,6 +680,9 @@
   public DesugaredLibraryConfiguration desugaredLibraryConfiguration =
       DesugaredLibraryConfiguration.empty();
 
+  // Method to lookup library definitions.
+  public Predicate<DexType> desugaredLibraryBootclasspathDefinitions;
+
   public boolean relocatorCompilation = false;
 
   // If null, no keep rules are recorded.
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibaryChecksumsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibaryChecksumsTest.java
new file mode 100644
index 0000000..3e819e2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibaryChecksumsTest.java
@@ -0,0 +1,69 @@
+// Copyright (c) 2020, 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.desugar.desugaredlibrary;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.L8;
+import com.android.tools.r8.L8Command;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.NoneRuntime;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import java.nio.file.Path;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DesugaredLibaryChecksumsTest extends TestBase {
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public DesugaredLibaryChecksumsTest(TestParameters parameters) {
+    assertEquals(NoneRuntime.getInstance(), parameters.getRuntime());
+  }
+
+  @Test
+  public void test() throws Exception {
+    Path out = temp.newFolder().toPath().resolve("out.jar");
+    L8.run(
+        L8Command.builder()
+            .setIncludeClassesChecksum(true)
+            .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+            .addProgramFiles(ToolHelper.getDesugarJDKLibs())
+            .addProgramFiles(ToolHelper.DESUGAR_LIB_CONVERSIONS)
+            .setMode(CompilationMode.DEBUG)
+            .addDesugaredLibraryConfiguration(
+                StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING))
+            .setMinApiLevel(AndroidApiLevel.B.getLevel())
+            .setOutput(out, OutputMode.DexIndexed)
+            .build());
+
+    try {
+      CodeInspector inspector = new CodeInspector(out);
+      for (FoundClassSubject clazz : inspector.allClasses()) {
+        assertTrue(clazz.getDexProgramClass().getChecksum() > 0);
+      }
+    } catch (CompilationError e) {
+      // TODO(b/158746302): Desugared library should support checksums.
+      //  also, the failure should have occured in the L8.run above!
+      assertThat(e.getMessage(), containsString("has no checksum"));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTest.java
index 579b6f6..ad8e74c 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTest.java
@@ -31,7 +31,7 @@
   private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.N;
   private static final String EXPECTED_RESULT =
       StringUtils.lines(
-          "[5, 6, 7]", "$r8$wrapper$java$util$stream$IntStream$-V-WRP", "IntSummaryStatistics");
+          "[5, 6, 7]", "j$.$r8$wrapper$java$util$stream$IntStream$-V-WRP", "IntSummaryStatistics");
 
   @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
   public static List<Object[]> data() {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ClockAPIConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ClockAPIConversionTest.java
index 4581211..382f55d 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ClockAPIConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ClockAPIConversionTest.java
@@ -57,6 +57,7 @@
         .addLibraryClasses(CustomLibClass.class)
         .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
         .compile()
+        .assertNoMessages()
         .addDesugaredCoreLibraryRunClassPath(
             this::buildDesugaredLibrary,
             parameters.getApiLevel(),
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java
index 3d1b999..106dd72 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java
@@ -6,7 +6,6 @@
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
@@ -21,8 +20,6 @@
 import java.util.function.IntSupplier;
 import java.util.function.LongConsumer;
 import java.util.function.LongSupplier;
-import org.junit.Assert;
-import org.junit.Assume;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -101,43 +98,6 @@
         .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
-  @Test
-  public void testWrapperWithChecksum() throws Exception {
-    Assume.assumeTrue(
-        shrinkDesugaredLibrary && parameters.getApiLevel().getLevel() <= MIN_SUPPORTED.getLevel());
-    testForD8()
-        .addProgramClasses(
-            Executor.class, Executor.Object1.class, Executor.Object2.class, Executor.Object3.class)
-        .addLibraryClasses(CustomLibClass.class)
-        .setMinApi(parameters.getApiLevel())
-        .enableCoreLibraryDesugaring(parameters.getApiLevel())
-        .setIncludeClassesChecksum(true) // Compilation fails if some classes are missing checksum.
-        .compile()
-        .inspect(
-            inspector -> {
-              Assert.assertEquals(
-                  9,
-                  inspector.allClasses().stream()
-                      .filter(
-                          clazz ->
-                              clazz
-                                  .getFinalName()
-                                  .contains(DesugaredLibraryWrapperSynthesizer.TYPE_WRAPPER_SUFFIX))
-                      .count());
-              Assert.assertEquals(
-                  9,
-                  inspector.allClasses().stream()
-                      .filter(
-                          clazz ->
-                              clazz
-                                  .getFinalName()
-                                  .contains(
-                                      DesugaredLibraryWrapperSynthesizer
-                                          .VIVIFIED_TYPE_WRAPPER_SUFFIX))
-                      .count());
-            });
-  }
-
   static class Executor {
 
     public static void main(String[] args) {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperMergeTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperMergeTest.java
index 9a29671..c95420c 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperMergeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperMergeTest.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.TestParameters;
@@ -17,12 +16,10 @@
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
-import com.google.common.collect.Sets;
-import it.unimi.dsi.fastutil.objects.Object2ReferenceMap;
-import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap;
+import java.io.IOException;
 import java.nio.file.Path;
 import java.util.Arrays;
-import java.util.Set;
+import java.util.stream.Stream;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -57,21 +54,28 @@
   }
 
   @Test
-  public void testWrapperMerge() throws Exception {
+  public void testNoWrappers() throws Exception {
     assumeTrue(parameters.isDexRuntime());
-    // Multiple wrapper classes have to be merged here.
+    // No wrappers are made during program compilation.
     Path path1 = compileWithCoreLibraryDesugaring(MyArrays1.class);
     Path path2 = compileWithCoreLibraryDesugaring(MyArrays2.class);
     testForD8()
-        .addProgramFiles(path1, path2)
         .addProgramClasses(TestClass.class)
         .addAndroidBuildVersion()
         .enableCoreLibraryDesugaring(parameters.getApiLevel())
         .setMinApi(parameters.getApiLevel())
         .compile()
-        .inspect(this::assertWrappers)
-        .inspect(this::assertNoDuplicates)
-        .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
+        .inspect(this::assertNoWrappers)
+        .apply(
+            b -> {
+              if (!hasNativeIntUnaryOperator()) {
+                Path coreLib = buildDesugaredLibrary(parameters.getApiLevel());
+                assertCoreLibContainsWrappers(coreLib);
+                b.addRunClasspathFiles(coreLib);
+              }
+            })
+        // The previous compilations are appended to the classpath (no merge).
+        .addRunClasspathFiles(path1, path2)
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutput(EXPECTED);
   }
@@ -82,34 +86,32 @@
         .setMinApi(parameters.getApiLevel())
         .enableCoreLibraryDesugaring(parameters.getApiLevel())
         .compile()
-        .inspect(this::assertWrappers)
+        .inspect(this::assertNoWrappers)
         .writeToZip();
   }
 
-  private void assertNoDuplicates(CodeInspector inspector) {
-    Object2ReferenceMap<String, Set<FoundClassSubject>> map = new Object2ReferenceOpenHashMap<>();
-    for (FoundClassSubject clazz : inspector.allClasses()) {
-      map.computeIfAbsent(clazz.getFinalName(), k -> Sets.newIdentityHashSet()).add(clazz);
-    }
-    for (Set<FoundClassSubject> duplicates : map.values()) {
-      if (duplicates.size() > 1) {
-        fail("Unexpected duplicates: " + duplicates);
-      }
-    }
-  }
-
   private boolean hasNativeIntUnaryOperator() {
     return parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N);
   }
 
-  private void assertWrappers(CodeInspector inspector) {
+  private void assertCoreLibContainsWrappers(Path coreLib) throws IOException {
+    CodeInspector inspector = new CodeInspector(coreLib);
+    // TODO(b/158645207): Consider having wrappers in the spec and avoiding this issue.
     assertEquals(
-        hasNativeIntUnaryOperator() ? 0 : 2,
-        inspector.allClasses().stream()
-            .filter(
-                c ->
-                    c.getOriginalName().contains(DesugaredLibraryWrapperSynthesizer.WRAPPER_PREFIX))
-            .count());
+        "Number of generated wrappers changes. Manually verify validity of the change.",
+        144,
+        getWrappers(inspector).count());
+  }
+
+  private void assertNoWrappers(CodeInspector inspector) {
+    Stream<FoundClassSubject> wrappers = getWrappers(inspector);
+    assertEquals(0, wrappers.count());
+  }
+
+  private Stream<FoundClassSubject> getWrappers(CodeInspector inspector) {
+    return inspector.allClasses().stream()
+        .filter(
+            c -> c.getOriginalName().contains(DesugaredLibraryWrapperSynthesizer.WRAPPER_PREFIX));
   }
 
   static class MyArrays1 {