Relands "Desugared lib API conversion D8 Cf to Cf"

This reverts commit a7805655b434d945cdb5beb7825aba11388ceb10.

Bug: 193001902
Bug: 193002912
Bug: 193002915
Change-Id: Ib1a5f628410cd62c7e0d6715c50296e04ad1a47c
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index e8f5829..0100fd5 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -9,6 +9,7 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.Marker;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexDebugEvent.AdvanceLine;
 import com.android.tools.r8.graph.DexDebugEvent.AdvancePC;
 import com.android.tools.r8.graph.DexDebugEvent.Default;
@@ -717,6 +718,44 @@
           .put(doubleType, boxedDoubleType)
           .build());
 
+  public final Map<DexType, DexMethod> unboxPrimitiveMethod =
+      ImmutableMap.<DexType, DexMethod>builder()
+          .put(boxedBooleanType, createUnboxMethod(booleanType, unboxBooleanMethodName))
+          .put(boxedByteType, createUnboxMethod(byteType, unboxByteMethodName))
+          .put(boxedCharType, createUnboxMethod(charType, unboxCharMethodName))
+          .put(boxedShortType, createUnboxMethod(shortType, unboxShortMethodName))
+          .put(boxedIntType, createUnboxMethod(intType, unboxIntMethodName))
+          .put(boxedLongType, createUnboxMethod(longType, unboxLongMethodName))
+          .put(boxedFloatType, createUnboxMethod(floatType, unboxFloatMethodName))
+          .put(boxedDoubleType, createUnboxMethod(doubleType, unboxDoubleMethodName))
+          .build();
+
+  private DexMethod createUnboxMethod(DexType primitiveType, DexString unboxMethodName) {
+    DexProto proto = createProto(primitiveType);
+    return createMethod(primitiveToBoxed.get(primitiveType), proto, unboxMethodName);
+  }
+
+  // Works both with the boxed and unboxed type.
+  public DexMethod getUnboxPrimitiveMethod(DexType type) {
+    DexType boxType = primitiveToBoxed.getOrDefault(type, type);
+    DexMethod unboxMethod = unboxPrimitiveMethod.get(boxType);
+    if (unboxMethod == null) {
+      throw new Unreachable("Invalid primitive type descriptor: " + type);
+    }
+    return unboxMethod;
+  }
+
+  // Works both with the boxed and unboxed type.
+  public DexMethod getBoxPrimitiveMethod(DexType type) {
+    DexType boxType = primitiveToBoxed.getOrDefault(type, type);
+    DexType primitive = getPrimitiveFromBoxed(boxType);
+    if (primitive == null) {
+      throw new Unreachable("Invalid primitive type descriptor: " + type);
+    }
+    DexProto proto = createProto(boxType, primitive);
+    return createMethod(boxType, proto, valueOfMethodName);
+  }
+
   public DexType getBoxedForPrimitiveType(DexType primitive) {
     assert primitive.isPrimitiveType();
     return primitiveToBoxed.get(primitive);
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 28d6be7..c9788bd 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
@@ -8,6 +8,8 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter.Mode;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
@@ -30,33 +32,33 @@
     converter.registerCallbackIfRequired(method);
   }
 
-  private void traceInvoke(DexMethod invokedMethod) {
-    converter.registerWrappersForLibraryInvokeIfRequired(invokedMethod);
+  private void traceInvoke(DexMethod invokedMethod, Type invokeType, ProgramMethod context) {
+    converter.registerWrappersForLibraryInvokeIfRequired(invokedMethod, invokeType, context);
   }
 
   @Override
   public void traceInvokeStatic(DexMethod invokedMethod, ProgramMethod context) {
-    this.traceInvoke(invokedMethod);
+    this.traceInvoke(invokedMethod, Type.STATIC, context);
   }
 
   @Override
   public void traceInvokeDirect(DexMethod invokedMethod, ProgramMethod context) {
-    this.traceInvoke(invokedMethod);
+    this.traceInvoke(invokedMethod, Type.DIRECT, context);
   }
 
   @Override
   public void traceInvokeInterface(DexMethod invokedMethod, ProgramMethod context) {
-    this.traceInvoke(invokedMethod);
+    this.traceInvoke(invokedMethod, Type.INTERFACE, context);
   }
 
   @Override
   public void traceInvokeSuper(DexMethod invokedMethod, ProgramMethod context) {
-    this.traceInvoke(invokedMethod);
+    this.traceInvoke(invokedMethod, Type.SUPER, context);
   }
 
   @Override
   public void traceInvokeVirtual(DexMethod invokedMethod, ProgramMethod context) {
-    this.traceInvoke(invokedMethod);
+    this.traceInvoke(invokedMethod, Invoke.Type.VIRTUAL, context);
   }
 
   public ProgramMethodSet generateCallbackMethods() {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
index 136d452..3129b82 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
@@ -76,6 +76,11 @@
       D8CfInstructionDesugaringEventConsumer instructionDesugaringEventConsumer =
           CfInstructionDesugaringEventConsumer.createForD8(methodProcessor);
 
+      // TODO(b/191656218): Move upfront the loop and use maybe the class event consumer.
+      if (appView.options().isDesugaredLibraryCompilation()) {
+        converter.ensureWrappersForL8(instructionDesugaringEventConsumer);
+      }
+
       // Process the wave and wait for all IR processing to complete.
       methodProcessor.newWave();
       ThreadUtils.processItems(
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index cdf3974..4177124 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -96,7 +96,6 @@
 import com.android.tools.r8.ir.optimize.string.StringOptimizer;
 import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
-import com.android.tools.r8.ir.synthetic.SynthesizedCode;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.naming.IdentifierNameStringMarker;
 import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagator;
@@ -229,8 +228,7 @@
       this.instructionDesugaring = CfInstructionDesugaringCollection.create(appView);
       this.classDesugaring = instructionDesugaring.createClassDesugaringCollection();
       this.interfaceMethodRewriter = null;
-      this.desugaredLibraryAPIConverter =
-          new DesugaredLibraryAPIConverter(appView, Mode.GENERATE_CALLBACKS_AND_WRAPPERS);
+      this.desugaredLibraryAPIConverter = null;
       this.covariantReturnTypeAnnotationTransformer = null;
       this.dynamicTypeOptimization = null;
       this.classInliner = null;
@@ -322,10 +320,7 @@
       this.identifierNameStringMarker = null;
       this.devirtualizer = null;
       this.typeChecker = null;
-      this.desugaredLibraryAPIConverter =
-          appView.rewritePrefix.isRewriting()
-              ? new DesugaredLibraryAPIConverter(appView, Mode.GENERATE_CALLBACKS_AND_WRAPPERS)
-              : null;
+      this.desugaredLibraryAPIConverter = null;
       this.serviceLoaderRewriter = null;
       this.methodOptimizationInfoCollector = null;
       this.enumValueOptimizer = null;
@@ -366,6 +361,13 @@
         D8NestBasedAccessDesugaring::clearNestAttributes);
   }
 
+  public void ensureWrappersForL8(
+      D8CfInstructionDesugaringEventConsumer instructionDesugaringEventConsumer) {
+    assert appView.options().isDesugaredLibraryCompilation();
+    instructionDesugaring.withDesugaredLibraryAPIConverter(
+        converter -> converter.ensureWrappersForL8(instructionDesugaringEventConsumer));
+  }
+
   private void staticizeClasses(
       OptimizationFeedback feedback, ExecutorService executorService, GraphLens applied)
       throws ExecutionException {
@@ -431,7 +433,6 @@
       new EmulatedInterfaceApplicationRewriter(appView).rewriteApplication(builder);
     }
     processCovariantReturnTypeAnnotations(builder);
-    generateDesugaredLibraryAPIWrappers(builder, executor);
 
     timing.end();
 
@@ -458,13 +459,14 @@
       D8MethodProcessor methodProcessor, ExecutorService executorService)
       throws ExecutionException {
     D8CfPostProcessingDesugaringEventConsumer eventConsumer =
-        CfPostProcessingDesugaringEventConsumer.createForD8(methodProcessor, appView);
+        CfPostProcessingDesugaringEventConsumer.createForD8(methodProcessor);
     methodProcessor.newWave();
     InterfaceMethodProcessorFacade interfaceDesugaring =
         instructionDesugaring.getInterfaceMethodPostProcessingDesugaring(ExcludeDexResources);
     CfPostProcessingDesugaringCollection.create(
             appView, interfaceDesugaring, instructionDesugaring.getRetargetingInfo())
         .postProcessingDesugaring(eventConsumer, executorService);
+    methodProcessor.awaitMethodProcessing();
     eventConsumer.finalizeDesugaring();
   }
 
@@ -482,6 +484,9 @@
 
     rewriteEnclosingLambdaMethodAttributes(
         appView, classConverterResult.getForcefullyMovedLambdaMethods());
+
+    instructionDesugaring.withDesugaredLibraryAPIConverter(
+        DesugaredLibraryAPIConverter::generateTrackingWarnings);
   }
 
   public void desugarClassesForD8(
@@ -590,32 +595,14 @@
     }
   }
 
-  private boolean needsIRConversion(ProgramMethod method) {
+  private boolean needsIRConversion() {
     if (appView.enableWholeProgramOptimizations()) {
       return true;
     }
     if (options.testing.forceIRForCfToCfDesugar) {
       return true;
     }
-    if (options.isDesugaredLibraryCompilation()) {
-      return true;
-    }
-    if (!options.cfToCfDesugar) {
-      return true;
-    }
-    if (desugaredLibraryAPIConverter != null
-        && desugaredLibraryAPIConverter.shouldRegisterCallback(method)) {
-      return true;
-    }
-    if (method.getDefinition().getCode() instanceof SynthesizedCode) {
-      // SynthesizedCode needs IR to generate the code.
-      return true;
-    } else {
-      NeedsIRDesugarUseRegistry useRegistry =
-          new NeedsIRDesugarUseRegistry(method, appView, desugaredLibraryAPIConverter);
-      method.registerCodeReferences(useRegistry);
-      return useRegistry.needsDesugaring();
-    }
+    return !options.cfToCfDesugar;
   }
 
   private void checkPrefixMerging(ProgramMethod method) {
@@ -830,9 +817,6 @@
     runInterfaceDesugaringProcessorsForR8(IncludeAllResources, executorService);
     feedback.updateVisibleOptimizationInfo();
 
-    printPhase("Desugared library API Conversion finalization");
-    generateDesugaredLibraryAPIWrappers(builder, executorService);
-
     if (serviceLoaderRewriter != null) {
       processSynthesizedServiceLoaderMethods(
           serviceLoaderRewriter.getServiceLoadMethods(), executorService);
@@ -996,14 +980,6 @@
     removeDeadCodeAndFinalizeIR(code, OptimizationFeedbackIgnore.getInstance(), Timing.empty());
   }
 
-  private void generateDesugaredLibraryAPIWrappers(
-      DexApplication.Builder<?> builder, ExecutorService executorService)
-      throws ExecutionException {
-    if (desugaredLibraryAPIConverter != null) {
-      desugaredLibraryAPIConverter.finalizeWrappers(builder, this, executorService);
-    }
-  }
-
   private void clearDexMethodCompilationState() {
     appView.appInfo().classes().forEach(this::clearDexMethodCompilationState);
   }
@@ -1171,7 +1147,7 @@
       options.testing.hookInIrConversion.run();
     }
 
-    if (!needsIRConversion(method) || options.skipIR) {
+    if (!needsIRConversion() || options.skipIR) {
       feedback.markProcessed(method.getDefinition(), ConstraintWithTarget.NEVER);
       return Timing.empty();
     }
@@ -1523,10 +1499,9 @@
 
     previous = printMethod(code, "IR after interface method rewriting (SSA)", previous);
 
-    // This pass has to be after interfaceMethodRewriter and BackportedMethodRewriter.
     if (desugaredLibraryAPIConverter != null
-        && (!appView.enableWholeProgramOptimizations()
-            || methodProcessor.isPrimaryMethodProcessor())) {
+        && appView.enableWholeProgramOptimizations()
+        && methodProcessor.isPrimaryMethodProcessor()) {
       timing.begin("Desugar library API");
       desugaredLibraryAPIConverter.desugar(code);
       timing.end();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java b/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
deleted file mode 100644
index 0d1c587..0000000
--- a/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
+++ /dev/null
@@ -1,108 +0,0 @@
-// 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.ir.conversion;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexCallSite;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.UseRegistry;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
-
-class NeedsIRDesugarUseRegistry extends UseRegistry {
-
-  private boolean needsDesugaring = false;
-  private final ProgramMethod context;
-  private final DesugaredLibraryAPIConverter desugaredLibraryAPIConverter;
-
-  public NeedsIRDesugarUseRegistry(
-      ProgramMethod method,
-      AppView<?> appView,
-      DesugaredLibraryAPIConverter desugaredLibraryAPIConverter) {
-    super(appView.dexItemFactory());
-    this.context = method;
-    this.desugaredLibraryAPIConverter = desugaredLibraryAPIConverter;
-  }
-
-  public boolean needsDesugaring() {
-    return needsDesugaring;
-  }
-
-  @Override
-  public void registerInitClass(DexType type) {
-    if (!needsDesugaring
-        && desugaredLibraryAPIConverter != null
-        && desugaredLibraryAPIConverter.canConvert(type)) {
-      needsDesugaring = true;
-    }
-  }
-
-  @Override
-  public void registerInvokeVirtual(DexMethod method) {
-    registerDesugaredLibraryAPIConverter(method);
-  }
-
-  @Override
-  public void registerInvokeDirect(DexMethod method) {
-    registerDesugaredLibraryAPIConverter(method);
-  }
-
-  private void registerDesugaredLibraryAPIConverter(DexMethod method) {
-    if (!needsDesugaring) {
-      needsDesugaring =
-          desugaredLibraryAPIConverter != null
-              && desugaredLibraryAPIConverter.shouldRewriteInvoke(method);
-    }
-  }
-
-  @Override
-  public void registerInvokeStatic(DexMethod method) {
-    registerDesugaredLibraryAPIConverter(method);
-  }
-
-  @Override
-  public void registerInvokeInterface(DexMethod method) {
-    registerDesugaredLibraryAPIConverter(method);
-  }
-
-  @Override
-  public void registerInvokeStatic(DexMethod method, boolean itf) {
-    registerInvokeStatic(method);
-  }
-
-  @Override
-  public void registerCallSite(DexCallSite callSite) {
-    super.registerCallSite(callSite);
-    needsDesugaring = true;
-  }
-
-  @Override
-  public void registerInvokeSuper(DexMethod method) {
-    registerDesugaredLibraryAPIConverter(method);
-  }
-
-  @Override
-  public void registerInstanceFieldRead(DexField field) {}
-
-  @Override
-  public void registerInstanceFieldWrite(DexField field) {}
-
-  @Override
-  public void registerNewInstance(DexType type) {}
-
-  @Override
-  public void registerStaticFieldRead(DexField field) {}
-
-  @Override
-  public void registerStaticFieldWrite(DexField field) {}
-
-  @Override
-  public void registerTypeReference(DexType type) {}
-
-  @Override
-  public void registerInstanceOf(DexType type) {}
-}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
index 8512734..b4f0bec 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
@@ -7,11 +7,13 @@
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.RetargetingInfo;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodProcessorFacade;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.Flavor;
 import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
 import com.android.tools.r8.utils.ThrowingConsumer;
+import java.util.function.Consumer;
 
 /**
  * Abstracts a collection of low-level desugarings (i.e., mappings from class-file instructions to
@@ -64,4 +66,7 @@
       Flavor flavor);
 
   public abstract RetargetingInfo getRetargetingInfo();
+
+  public abstract void withDesugaredLibraryAPIConverter(
+      Consumer<DesugaredLibraryAPIConverter> consumer);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
index d497332..bb2feb2 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.ir.conversion.ClassConverterResult;
 import com.android.tools.r8.ir.conversion.D8MethodProcessor;
 import com.android.tools.r8.ir.desugar.backports.BackportedMethodDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverterEventConsumer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeterInstructionEventConsumer;
 import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialBridgeInfo;
 import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialToSelfDesugaringEventConsumer;
@@ -48,7 +49,8 @@
         RecordDesugaringEventConsumer,
         TwrCloseResourceDesugaringEventConsumer,
         InterfaceMethodDesugaringEventConsumer,
-        DesugaredLibraryRetargeterInstructionEventConsumer {
+        DesugaredLibraryRetargeterInstructionEventConsumer,
+        DesugaredLibraryAPIConverterEventConsumer {
 
   public static D8CfInstructionDesugaringEventConsumer createForD8(
       D8MethodProcessor methodProcessor) {
@@ -68,6 +70,21 @@
     return new CfInstructionDesugaringEventConsumer() {
 
       @Override
+      public void acceptWrapperProgramClass(DexProgramClass clazz) {
+        assert false;
+      }
+
+      @Override
+      public void acceptWrapperClasspathClass(DexClasspathClass clazz) {
+        assert false;
+      }
+
+      @Override
+      public void acceptAPIConversion(ProgramMethod method) {
+        assert false;
+      }
+
+      @Override
       public void acceptDesugaredLibraryRetargeterDispatchProgramClass(DexProgramClass clazz) {
         assert false;
       }
@@ -229,6 +246,21 @@
       methodProcessor.scheduleDesugaredMethodForProcessing(method);
     }
 
+    @Override
+    public void acceptWrapperProgramClass(DexProgramClass clazz) {
+      methodProcessor.scheduleDesugaredMethodsForProcessing(clazz.programMethods());
+    }
+
+    @Override
+    public void acceptWrapperClasspathClass(DexClasspathClass clazz) {
+      // Intentionally empty.
+    }
+
+    @Override
+    public void acceptAPIConversion(ProgramMethod method) {
+      methodProcessor.scheduleDesugaredMethodForProcessing(method);
+    }
+
     public List<ProgramMethod> finalizeDesugaring(
         AppView<?> appView, ClassConverterResult.Builder classConverterResultBuilder) {
       List<ProgramMethod> needsProcessing = new ArrayList<>();
@@ -348,6 +380,24 @@
     }
 
     @Override
+    public void acceptWrapperProgramClass(DexProgramClass clazz) {
+      // TODO(b/189912077): There should be nothing to do.
+      assert false;
+    }
+
+    @Override
+    public void acceptWrapperClasspathClass(DexClasspathClass clazz) {
+      // TODO(b/189912077): Should be added to live non program types.
+      assert false;
+    }
+
+    @Override
+    public void acceptAPIConversion(ProgramMethod method) {
+      // TODO(b/189912077): There should be nothing to do.
+      assert false;
+    }
+
+    @Override
     public void acceptBackportedMethod(ProgramMethod backportedMethod, ProgramMethod context) {
       // Intentionally empty. The backported method will be hit by the tracing in R8 as if it was
       // present in the input code, and thus nothing needs to be done.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java
index 986c6ea..367e0ca 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.ir.desugar;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeterPostProcessor;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.RetargetingInfo;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodProcessorFacade;
@@ -47,10 +48,6 @@
         AppView<?> appView,
         InterfaceMethodProcessorFacade interfaceMethodProcessorFacade,
         RetargetingInfo retargetingInfo) {
-      if (appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()
-          && interfaceMethodProcessorFacade == null) {
-        return empty();
-      }
       ArrayList<CfPostProcessingDesugaring> desugarings = new ArrayList<>();
       if (!appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()) {
         desugarings.add(new DesugaredLibraryRetargeterPostProcessor(appView, retargetingInfo));
@@ -58,6 +55,18 @@
       if (interfaceMethodProcessorFacade != null) {
         desugarings.add(interfaceMethodProcessorFacade);
       }
+      DesugaredLibraryAPIConverter desugaredLibraryAPIConverter =
+          appView.rewritePrefix.isRewriting() && !appView.enableWholeProgramOptimizations()
+              ? new DesugaredLibraryAPIConverter(appView, null)
+              : null;
+      // At this point the desugaredLibraryAPIConverter is required to be last to generate
+      // call-backs on the forwarding methods.
+      if (desugaredLibraryAPIConverter != null) {
+        desugarings.add(desugaredLibraryAPIConverter);
+      }
+      if (desugarings.isEmpty()) {
+        return empty();
+      }
       return new NonEmptyCfPostProcessingDesugaringCollection(desugarings);
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
index 5734cab..0ed1e16 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.D8MethodProcessor;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverterEventConsumer.DesugaredLibraryAPIConverterPostProcessingEventConsumer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeterInstructionEventConsumer.DesugaredLibraryRetargeterPostProcessingEventConsumer;
 import com.android.tools.r8.ir.desugar.itf.InterfaceProcessingDesugaringEventConsumer;
 import com.android.tools.r8.shaking.Enqueuer.SyntheticAdditions;
@@ -23,16 +24,12 @@
  */
 public abstract class CfPostProcessingDesugaringEventConsumer
     implements DesugaredLibraryRetargeterPostProcessingEventConsumer,
-        InterfaceProcessingDesugaringEventConsumer {
-  protected DesugaredLibraryAPIConverter desugaredLibraryAPIConverter;
-
-  protected CfPostProcessingDesugaringEventConsumer(AppView<?> appView) {
-    this.desugaredLibraryAPIConverter = new DesugaredLibraryAPIConverter(appView, null);
-  }
+        InterfaceProcessingDesugaringEventConsumer,
+        DesugaredLibraryAPIConverterPostProcessingEventConsumer {
 
   public static D8CfPostProcessingDesugaringEventConsumer createForD8(
-      D8MethodProcessor methodProcessor, AppView<?> appView) {
-    return new D8CfPostProcessingDesugaringEventConsumer(methodProcessor, appView);
+      D8MethodProcessor methodProcessor) {
+    return new D8CfPostProcessingDesugaringEventConsumer(methodProcessor);
   }
 
   public static R8PostProcessingDesugaringEventConsumer createForR8(
@@ -49,9 +46,7 @@
     // concurrently processing other methods.
     private final ProgramMethodSet methodsToReprocess = ProgramMethodSet.createConcurrent();
 
-    private D8CfPostProcessingDesugaringEventConsumer(
-        D8MethodProcessor methodProcessor, AppView<?> appView) {
-      super(appView);
+    private D8CfPostProcessingDesugaringEventConsumer(D8MethodProcessor methodProcessor) {
       this.methodProcessor = methodProcessor;
     }
 
@@ -92,15 +87,24 @@
       methodProcessor.scheduleDesugaredMethodsForProcessing(methodsToReprocess);
       methodProcessor.awaitMethodProcessing();
     }
+
+    @Override
+    public void acceptAPIConversionCallback(ProgramMethod method) {
+      methodProcessor.scheduleDesugaredMethodForProcessing(method);
+    }
   }
 
   public static class R8PostProcessingDesugaringEventConsumer
       extends CfPostProcessingDesugaringEventConsumer {
     private final SyntheticAdditions additions;
 
-    protected R8PostProcessingDesugaringEventConsumer(
-        AppView<?> appView, SyntheticAdditions additions) {
-      super(appView);
+    // The desugaredLibraryAPIConverter is required here because call-backs need to be generated
+    // once forwarding methods are generated. We should be able to remove it once the interface
+    // method desugaring and the API converter are moved cf to cf in R8.
+    private final DesugaredLibraryAPIConverter desugaredLibraryAPIConverter;
+
+    R8PostProcessingDesugaringEventConsumer(AppView<?> appView, SyntheticAdditions additions) {
+      this.desugaredLibraryAPIConverter = new DesugaredLibraryAPIConverter(appView, null);
       this.additions = additions;
     }
 
@@ -127,10 +131,7 @@
     @Override
     public void acceptForwardingMethod(ProgramMethod method) {
       additions.addLiveMethod(method);
-      ProgramMethod callback = desugaredLibraryAPIConverter.generateCallbackIfRequired(method);
-      if (callback != null) {
-        additions.addLiveMethod(callback);
-      }
+      desugaredLibraryAPIConverter.generateCallbackIfRequired(method, this);
     }
 
     @Override
@@ -142,5 +143,10 @@
     public void acceptEmulatedInterfaceMethod(ProgramMethod method) {
       assert false : "TODO(b/183998768): Support Interface processing in R8";
     }
+
+    @Override
+    public void acceptAPIConversionCallback(ProgramMethod method) {
+      additions.addLiveMethod(method);
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java
index 7991ac3..17e219e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java
@@ -7,11 +7,13 @@
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.desugar.CfClassDesugaringCollection.EmptyCfClassDesugaringCollection;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.RetargetingInfo;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodProcessorFacade;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.Flavor;
 import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
 import com.android.tools.r8.utils.ThrowingConsumer;
+import java.util.function.Consumer;
 
 public class EmptyCfInstructionDesugaringCollection extends CfInstructionDesugaringCollection {
 
@@ -73,4 +75,9 @@
   public RetargetingInfo getRetargetingInfo() {
     return null;
   }
+
+  @Override
+  public void withDesugaredLibraryAPIConverter(Consumer<DesugaredLibraryAPIConverter> consumer) {
+    // Intentionally empty.
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
index 6975252..e0a9c07 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
@@ -407,7 +407,7 @@
       }
       DexType fromTypeAsPrimitive = factory.getPrimitiveFromBoxed(boxedType);
       if (fromTypeAsPrimitive != null) {
-        addPrimitiveUnboxing(fromTypeAsPrimitive, boxedType, instructions, factory);
+        addPrimitiveUnboxing(boxedType, instructions, factory);
         addPrimitiveWideningConversion(fromTypeAsPrimitive, toType, instructions);
         return;
       }
@@ -424,7 +424,7 @@
           || (boxedFromType != factory.booleanType
               && boxedFromType != factory.charType
               && toType == factory.boxedNumberType)) {
-        addPrimitiveBoxing(fromType, boxedFromType, instructions, factory);
+        addPrimitiveBoxing(boxedFromType, instructions, factory);
         return;
       }
     }
@@ -513,55 +513,19 @@
         "converted to " + toType.toSourceString() + " via primitive widening conversion.");
   }
 
-  private static DexMethod getUnboxMethod(byte primitive, DexType boxType, DexItemFactory factory) {
-    DexProto proto;
-    switch (primitive) {
-      case 'Z':  // byte
-        proto = factory.createProto(factory.booleanType);
-        return factory.createMethod(boxType, proto, factory.unboxBooleanMethodName);
-      case 'B':  // byte
-        proto = factory.createProto(factory.byteType);
-        return factory.createMethod(boxType, proto, factory.unboxByteMethodName);
-      case 'S':  // short
-        proto = factory.createProto(factory.shortType);
-        return factory.createMethod(boxType, proto, factory.unboxShortMethodName);
-      case 'C':  // char
-        proto = factory.createProto(factory.charType);
-        return factory.createMethod(boxType, proto, factory.unboxCharMethodName);
-      case 'I':  // int
-        proto = factory.createProto(factory.intType);
-        return factory.createMethod(boxType, proto, factory.unboxIntMethodName);
-      case 'J':  // long
-        proto = factory.createProto(factory.longType);
-        return factory.createMethod(boxType, proto, factory.unboxLongMethodName);
-      case 'F':  // float
-        proto = factory.createProto(factory.floatType);
-        return factory.createMethod(boxType, proto, factory.unboxFloatMethodName);
-      case 'D':  // double
-        proto = factory.createProto(factory.doubleType);
-        return factory.createMethod(boxType, proto, factory.unboxDoubleMethodName);
-      default:
-        throw new Unreachable("Invalid primitive type descriptor: " + primitive);
-    }
-  }
-
   private static void addPrimitiveUnboxing(
-      DexType primitiveType,
       DexType boxType,
       Builder<CfInstruction> instructions,
       DexItemFactory factory) {
-    DexMethod method = getUnboxMethod(primitiveType.descriptor.content[0], boxType, factory);
+    DexMethod method = factory.getUnboxPrimitiveMethod(boxType);
     instructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, method, false));
   }
 
   private static void addPrimitiveBoxing(
-      DexType primitiveType,
       DexType boxType,
       Builder<CfInstruction> instructions,
       DexItemFactory factory) {
-    // Generate factory method fo boxing.
-    DexProto proto = factory.createProto(boxType, primitiveType);
-    DexMethod method = factory.createMethod(boxType, proto, factory.valueOfMethodName);
+    DexMethod method = factory.getBoxPrimitiveMethod(boxType);
     instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, method, false));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
index a7f1a33..25556ca 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.desugar.CfClassDesugaringCollection.EmptyCfClassDesugaringCollection;
 import com.android.tools.r8.ir.desugar.CfClassDesugaringCollection.NonEmptyCfClassDesugaringCollection;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.RetargetingInfo;
 import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialToSelfDesugaring;
@@ -35,6 +36,7 @@
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
+import java.util.function.Consumer;
 
 public class NonEmptyCfInstructionDesugaringCollection extends CfInstructionDesugaringCollection {
 
@@ -45,6 +47,7 @@
   private final RecordRewriter recordRewriter;
   private final DesugaredLibraryRetargeter desugaredLibraryRetargeter;
   private final InterfaceMethodRewriter interfaceMethodRewriter;
+  private final DesugaredLibraryAPIConverter desugaredLibraryAPIConverter;
 
   NonEmptyCfInstructionDesugaringCollection(AppView<?> appView) {
     this.appView = appView;
@@ -53,6 +56,7 @@
       this.recordRewriter = null;
       this.desugaredLibraryRetargeter = null;
       this.interfaceMethodRewriter = null;
+      this.desugaredLibraryAPIConverter = null;
       return;
     }
     this.nestBasedAccessDesugaring = NestBasedAccessDesugaring.create(appView);
@@ -72,14 +76,25 @@
       desugarings.add(new TwrInstructionDesugaring(appView));
     }
     // TODO(b/183998768): Enable interface method rewriter cf to cf also in R8.
-    interfaceMethodRewriter =
-        appView.options().isInterfaceMethodDesugaringEnabled()
-                && !appView.enableWholeProgramOptimizations()
-            ? new InterfaceMethodRewriter(
-                appView, backportedMethodRewriter, desugaredLibraryRetargeter)
-            : null;
-    if (interfaceMethodRewriter != null) {
+    if (appView.options().isInterfaceMethodDesugaringEnabled()
+        && !appView.enableWholeProgramOptimizations()) {
+      interfaceMethodRewriter =
+          new InterfaceMethodRewriter(
+              appView, backportedMethodRewriter, desugaredLibraryRetargeter);
       desugarings.add(interfaceMethodRewriter);
+    } else {
+      interfaceMethodRewriter = null;
+    }
+    desugaredLibraryAPIConverter =
+        appView.rewritePrefix.isRewriting() && !appView.enableWholeProgramOptimizations()
+            ? new DesugaredLibraryAPIConverter(
+                appView,
+                interfaceMethodRewriter,
+                desugaredLibraryRetargeter,
+                backportedMethodRewriter)
+            : null;
+    if (desugaredLibraryAPIConverter != null) {
+      desugarings.add(desugaredLibraryAPIConverter);
     }
     desugarings.add(new LambdaInstructionDesugaring(appView));
     desugarings.add(new InvokeSpecialToSelfDesugaring(appView));
@@ -332,4 +347,11 @@
     }
     return null;
   }
+
+  @Override
+  public void withDesugaredLibraryAPIConverter(Consumer<DesugaredLibraryAPIConverter> consumer) {
+    if (desugaredLibraryAPIConverter != null) {
+      consumer.accept(desugaredLibraryAPIConverter);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverter.java
index b50ac86..c454af6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverter.java
@@ -4,10 +4,21 @@
 
 package com.android.tools.r8.ir.desugar.desugaredlibrary;
 
+import com.android.tools.r8.cf.code.CfArrayLoad;
+import com.android.tools.r8.cf.code.CfArrayStore;
+import com.android.tools.r8.cf.code.CfCheckCast;
+import com.android.tools.r8.cf.code.CfConstNumber;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfNewArray;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DebugLocalInfo;
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexClasspathClass;
@@ -17,6 +28,7 @@
 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.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -25,11 +37,24 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaring;
+import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.FreshLocalProvider;
+import com.android.tools.r8.ir.desugar.LocalStackAllocator;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverterEventConsumer.DesugaredLibraryAPIConverterPostProcessingEventConsumer;
+import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
+import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConversionCfCodeProvider;
 import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterWrapperCfCodeProvider;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.OptionalBool;
@@ -38,15 +63,16 @@
 import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.function.Consumer;
+import org.objectweb.asm.Opcodes;
 
 // 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
@@ -62,7 +88,8 @@
 // DesugarType is only a rewritten type (generated through rewriting of type).
 // The type, from the library, may either be rewritten to the desugarType,
 // or be a rewritten type (generated through rewriting of vivifiedType).
-public class DesugaredLibraryAPIConverter {
+public class DesugaredLibraryAPIConverter
+    implements CfInstructionDesugaring, CfPostProcessingDesugaring {
 
   static final String VIVIFIED_PREFIX = "$-vivified-$.";
   public static final String DESCRIPTOR_VIVIFIED_PREFIX = "L$-vivified-$/";
@@ -72,6 +99,11 @@
   // For debugging only, allows to assert that synthesized code in R8 have been synthesized in the
   // Enqueuer and not during IR processing.
   private final Mode mode;
+  // This is used to filter out double desugaring on backported methods.
+  private final BackportedMethodRewriter backportedMethodRewriter;
+  private final InterfaceMethodRewriter interfaceMethodRewriter;
+  private final DesugaredLibraryRetargeter retargeter;
+
   private final DesugaredLibraryWrapperSynthesizer wrapperSynthesizor;
   private final Map<DexClass, Set<DexEncodedMethod>> callBackMethods = new IdentityHashMap<>();
   private final Map<DexProgramClass, List<DexEncodedMethod>> pendingCallBackMethods =
@@ -85,9 +117,29 @@
   }
 
   public DesugaredLibraryAPIConverter(AppView<?> appView, Mode mode) {
+    this(appView, mode, null, null, null);
+  }
+
+  public DesugaredLibraryAPIConverter(
+      AppView<?> appView,
+      InterfaceMethodRewriter interfaceMethodRewriter,
+      DesugaredLibraryRetargeter retargeter,
+      BackportedMethodRewriter backportedMethodRewriter) {
+    this(appView, null, interfaceMethodRewriter, retargeter, backportedMethodRewriter);
+  }
+
+  private DesugaredLibraryAPIConverter(
+      AppView<?> appView,
+      Mode mode,
+      InterfaceMethodRewriter interfaceMethodRewriter,
+      DesugaredLibraryRetargeter retargeter,
+      BackportedMethodRewriter backportedMethodRewriter) {
     this.appView = appView;
     this.factory = appView.dexItemFactory();
     this.mode = mode;
+    this.interfaceMethodRewriter = interfaceMethodRewriter;
+    this.retargeter = retargeter;
+    this.backportedMethodRewriter = backportedMethodRewriter;
     this.wrapperSynthesizor = new DesugaredLibraryWrapperSynthesizer(appView, this);
     if (appView.options().testing.trackDesugaredAPIConversions) {
       trackedCallBackAPIs = Sets.newConcurrentHashSet();
@@ -98,6 +150,86 @@
     }
   }
 
+  // TODO(b/191656218): Consider parallelizing post processing across classes instead of per
+  // implementor
+  // method.
+  @Override
+  public void postProcessingDesugaring(
+      CfPostProcessingDesugaringEventConsumer eventConsumer, ExecutorService executorService) {
+    assert noPendingWrappersOrConversions();
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      if (!appView.isAlreadyLibraryDesugared(clazz)) {
+        ArrayList<DexEncodedMethod> callbacks = new ArrayList<>();
+        for (ProgramMethod virtualProgramMethod : clazz.virtualProgramMethods()) {
+          if (shouldRegisterCallback(virtualProgramMethod)) {
+            if (trackedCallBackAPIs != null) {
+              trackedCallBackAPIs.add(virtualProgramMethod.getReference());
+            }
+            ProgramMethod callback =
+                generateCallbackMethod(
+                    virtualProgramMethod.getDefinition(),
+                    virtualProgramMethod.getHolder(),
+                    eventConsumer);
+            callbacks.add(callback.getDefinition());
+          }
+        }
+        if (!callbacks.isEmpty()) {
+          clazz.addVirtualMethods(callbacks);
+        }
+      }
+    }
+    assert noPendingWrappersOrConversions();
+    generateTrackingWarnings();
+  }
+
+  private boolean noPendingWrappersOrConversions() {
+    for (DexProgramClass pendingSyntheticClass :
+        appView.getSyntheticItems().getPendingSyntheticClasses()) {
+      assert !isAPIConversionSyntheticType(pendingSyntheticClass.type);
+    }
+    return true;
+  }
+
+  @Override
+  public Collection<CfInstruction> desugarInstruction(
+      CfInstruction instruction,
+      FreshLocalProvider freshLocalProvider,
+      LocalStackAllocator localStackAllocator,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      ProgramMethod context,
+      MethodProcessingContext methodProcessingContext,
+      DexItemFactory dexItemFactory) {
+    assert !appView.enableWholeProgramOptimizations();
+    if (needsDesugaring(instruction, context)) {
+      assert instruction.isInvoke();
+      return rewriteLibraryInvoke(
+          instruction.asInvoke(),
+          methodProcessingContext,
+          localStackAllocator,
+          eventConsumer,
+          context);
+    }
+    return null;
+  }
+
+  @Override
+  public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
+    if (!instruction.isInvoke()) {
+      return false;
+    }
+    if (isAPIConversionSyntheticType(context.getHolderType())) {
+      return false;
+    }
+    CfInvoke invoke = instruction.asInvoke();
+    return shouldRewriteInvoke(
+        invoke.getMethod(), invoke.getInvokeType(context), invoke.isInterface(), context);
+  }
+
+  private boolean isAPIConversionSyntheticType(DexType type) {
+    return wrapperSynthesizor.isSyntheticWrapper(type)
+        || appView.getSyntheticItems().isSyntheticOfKind(type, SyntheticKind.API_CONVERSION);
+  }
+
   public static boolean isVivifiedType(DexType type) {
     return type.descriptor.toString().startsWith(DESCRIPTOR_VIVIFIED_PREFIX);
   }
@@ -108,6 +240,8 @@
 
   public void desugar(IRCode code) {
 
+    assert appView.enableWholeProgramOptimizations();
+
     if (wrapperSynthesizor.isSyntheticWrapper(code.method().getHolderType())) {
       return;
     }
@@ -128,26 +262,32 @@
           continue;
         }
         InvokeMethod invokeMethod = instruction.asInvokeMethod();
-        DexMethod invokedMethod;
-        if (invokeMethod.isInvokeSuper()) {
-          DexClassAndMethod result =
-              appView
-                  .appInfoForDesugaring()
-                  .lookupSuperTarget(invokeMethod.getInvokedMethod(), code.context());
-          invokedMethod = result != null ? result.getReference() : null;
-        } else {
-          // TODO(b/192439456): Make a test to prove resolution is needed here and fix it.
-          invokedMethod = invokeMethod.getInvokedMethod();
-        }
+        DexMethod invokedMethod = invokeMethod.getInvokedMethod();
         // Library methods do not understand desugared types, hence desugared types have to be
         // converted around non desugared library calls for the invoke to resolve.
-        if (invokedMethod != null && shouldRewriteInvoke(invokedMethod)) {
+        if (invokedMethod != null
+            && shouldRewriteInvoke(
+                invokedMethod,
+                invokeMethod.getType(),
+                invokeMethod.getInterfaceBit(),
+                code.context())) {
           rewriteLibraryInvoke(code, invokeMethod, iterator, blockIterator);
         }
       }
     }
   }
 
+  private DexClassAndMethod getMethodForDesugaring(
+      DexMethod invokedMethod, boolean isInvokeSuper, boolean isInterface, ProgramMethod context) {
+    // TODO(b/191656218): Use lookupInvokeSpecial instead when this is all to Cf.
+    return isInvokeSuper
+        ? appView.appInfoForDesugaring().lookupSuperTarget(invokedMethod, context)
+        : appView
+            .appInfoForDesugaring()
+            .resolveMethod(invokedMethod, isInterface)
+            .getResolutionPair();
+  }
+
   private boolean validateCallbackWasGeneratedInEnqueuer(ProgramMethod method) {
     if (!shouldRegisterCallback(method)) {
       return true;
@@ -157,16 +297,76 @@
     return true;
   }
 
-  public boolean shouldRewriteInvoke(DexMethod invokedMethod) {
-    if (appView.rewritePrefix.hasRewrittenType(invokedMethod.holder, appView)
-        || invokedMethod.holder.isArrayType()) {
+  // TODO(b/191656218): Consider caching the result.
+  private boolean shouldRewriteInvoke(
+      DexMethod unresolvedInvokedMethod,
+      Type invokeType,
+      boolean isInterface,
+      ProgramMethod context) {
+    DexClassAndMethod invokedMethod =
+        getMethodForDesugaring(
+            unresolvedInvokedMethod, invokeType == Type.SUPER, isInterface, context);
+    if (invokedMethod == null) {
+      // Implies a resolution/look-up failure, we do not convert to keep the runtime error.
       return false;
     }
-    DexClass dexClass = appView.definitionFor(invokedMethod.holder);
+    DexType holderType = invokedMethod.getHolderType();
+    if (appView.rewritePrefix.hasRewrittenType(holderType, appView) || holderType.isArrayType()) {
+      return false;
+    }
+    DexClass dexClass = appView.definitionFor(holderType);
     if (dexClass == null || !dexClass.isLibraryClass()) {
       return false;
     }
-    return appView.rewritePrefix.hasRewrittenTypeInSignature(invokedMethod.proto, appView);
+    if (isEmulatedInterfaceOverride(invokedMethod)) {
+      return false;
+    }
+    if (isAlreadyDesugared(unresolvedInvokedMethod, invokeType, isInterface, context)) {
+      return false;
+    }
+    return appView.rewritePrefix.hasRewrittenTypeInSignature(invokedMethod.getProto(), appView);
+  }
+
+  // The problem is that a method can resolve into a library method which is not present at runtime,
+  // the code relies in that case on emulated interface dispatch. We should not convert such API.
+  private boolean isEmulatedInterfaceOverride(DexClassAndMethod invokedMethod) {
+    if (interfaceMethodRewriter == null) {
+      return false;
+    }
+    if (!interfaceMethodRewriter.getEmulatedMethods().contains(invokedMethod.getName())) {
+      return false;
+    }
+    DexClassAndMethod interfaceResult =
+        appView
+            .appInfoForDesugaring()
+            .lookupMaximallySpecificMethod(invokedMethod.getHolder(), invokedMethod.getReference());
+    return interfaceResult != null
+        && appView
+            .options()
+            .desugaredLibraryConfiguration
+            .getEmulateLibraryInterface()
+            .containsKey(interfaceResult.getHolderType());
+  }
+
+  private boolean isAlreadyDesugared(
+      DexMethod unresolvedInvokedMethod,
+      Type invokeType,
+      boolean isInterface,
+      ProgramMethod context) {
+    if (interfaceMethodRewriter != null
+        && interfaceMethodRewriter.needsRewriting(unresolvedInvokedMethod, invokeType, context)) {
+      return true;
+    }
+    if (retargeter != null
+        && retargeter.hasNewInvokeTarget(
+            unresolvedInvokedMethod, isInterface, invokeType == Type.SUPER, context)) {
+      return true;
+    }
+    if (backportedMethodRewriter != null
+        && backportedMethodRewriter.methodIsBackport(unresolvedInvokedMethod)) {
+      return true;
+    }
+    return false;
   }
 
   public void registerCallbackIfRequired(ProgramMethod method) {
@@ -175,16 +375,18 @@
     }
   }
 
-  public ProgramMethod generateCallbackIfRequired(ProgramMethod method) {
+  public void generateCallbackIfRequired(
+      ProgramMethod method, DesugaredLibraryAPIConverterPostProcessingEventConsumer eventConsumer) {
     if (!shouldRegisterCallback(method)) {
-      return null;
+      return;
     }
     if (trackedCallBackAPIs != null) {
       trackedCallBackAPIs.add(method.getReference());
     }
-    ProgramMethod callback = generateCallbackMethod(method.getDefinition(), method.getHolder());
-    method.getHolder().addVirtualMethod(callback.getDefinition());
-    return callback;
+    ProgramMethod callback =
+        generateCallbackMethod(method.getDefinition(), method.getHolder(), eventConsumer);
+    callback.getHolder().addVirtualMethod(callback.getDefinition());
+    assert noPendingWrappersOrConversions();
   }
 
   public boolean shouldRegisterCallback(ProgramMethod method) {
@@ -198,6 +400,7 @@
     DexEncodedMethod definition = method.getDefinition();
     if (definition.isPrivateMethod()
         || definition.isStatic()
+        || definition.isAbstract()
         || definition.isLibraryMethodOverride().isFalse()) {
       return false;
     }
@@ -322,19 +525,9 @@
     return appView.dexItemFactory().createMethod(holder, newProto, originalMethod.name);
   }
 
-  public void finalizeWrappers(
-      DexApplication.Builder<?> builder, IRConverter irConverter, ExecutorService executorService)
-      throws ExecutionException {
-    // In D8, we generate the wrappers here. In R8, wrappers have already been generated in the
-    // enqueuer, so nothing needs to be done.
-    if (appView.enableWholeProgramOptimizations()) {
-      return;
-    }
-    SortedProgramMethodSet callbacks = generateCallbackMethods();
-    irConverter.processMethodsConcurrently(callbacks, executorService);
-    if (appView.options().isDesugaredLibraryCompilation()) {
-      wrapperSynthesizor.finalizeWrappersForL8();
-    }
+  public void ensureWrappersForL8(CfInstructionDesugaringEventConsumer eventConsumer) {
+    assert appView.options().isDesugaredLibraryCompilation();
+    wrapperSynthesizor.ensureWrappersForL8(eventConsumer);
   }
 
   public SortedProgramMethodSet generateCallbackMethods() {
@@ -345,7 +538,7 @@
           List<DexEncodedMethod> newVirtualMethods = new ArrayList<>();
           callbacks.forEach(
               callback -> {
-                ProgramMethod callbackMethod = generateCallbackMethod(callback, clazz);
+                ProgramMethod callbackMethod = generateCallbackMethod(callback, clazz, null);
                 newVirtualMethods.add(callbackMethod.getDefinition());
                 allCallbackMethods.add(callbackMethod);
               });
@@ -369,12 +562,14 @@
   }
 
   private ProgramMethod generateCallbackMethod(
-      DexEncodedMethod originalMethod, DexProgramClass clazz) {
+      DexEncodedMethod originalMethod,
+      DexProgramClass clazz,
+      DesugaredLibraryAPIConverterPostProcessingEventConsumer eventConsumer) {
     DexMethod methodToInstall =
         methodWithVivifiedTypeInSignature(originalMethod.getReference(), clazz.type, appView);
     CfCode cfCode =
         new APIConverterWrapperCfCodeProvider(
-                appView, originalMethod.getReference(), null, this, clazz.isInterface())
+                appView, originalMethod.getReference(), null, this, clazz.isInterface(), null)
             .generateCfCode();
     DexEncodedMethod newMethod =
         wrapperSynthesizor.newSynthesizedMethod(methodToInstall, originalMethod, cfCode);
@@ -382,7 +577,13 @@
     if (originalMethod.isLibraryMethodOverride().isTrue()) {
       newMethod.setLibraryMethodOverride(OptionalBool.TRUE);
     }
-    return new ProgramMethod(clazz, newMethod);
+    ProgramMethod callback = new ProgramMethod(clazz, newMethod);
+    if (eventConsumer != null) {
+      eventConsumer.acceptAPIConversionCallback(callback);
+    } else {
+      assert appView.enableWholeProgramOptimizations();
+    }
+    return callback;
   }
 
   private void generateTrackDesugaredAPIWarnings(Set<DexMethod> tracked, String inner) {
@@ -425,8 +626,11 @@
     return vivifiedType;
   }
 
-  public void registerWrappersForLibraryInvokeIfRequired(DexMethod invokedMethod) {
-    if (!shouldRewriteInvoke(invokedMethod)) {
+  public void registerWrappersForLibraryInvokeIfRequired(
+      DexMethod invokedMethod, Type invokeType, ProgramMethod context) {
+    // TODO(b/191656218): Once R8 support is done, use the isInterface bit instead of the inexact
+    //  invokeType == Type.INTERFACE here.
+    if (!shouldRewriteInvoke(invokedMethod, invokeType, invokeType == Type.INTERFACE, context)) {
       return;
     }
     if (trackedAPIs != null) {
@@ -443,6 +647,305 @@
     }
   }
 
+  private static DexType invalidType(
+      DexMethod invokedMethod,
+      DexMethod returnConversion,
+      DexMethod[] parameterConversions,
+      AppView<?> appView) {
+    DexMethod convertedMethod =
+        methodWithVivifiedTypeInSignature(invokedMethod, invokedMethod.holder, appView);
+    if (invokedMethod.getReturnType() != convertedMethod.getReturnType()
+        && returnConversion == null) {
+      return invokedMethod.getReturnType();
+    }
+    for (int i = 0; i < invokedMethod.getArity(); i++) {
+      if (invokedMethod.getParameter(i) != convertedMethod.getParameter(i)
+          && parameterConversions[i] == null) {
+        return invokedMethod.getParameter(i);
+      }
+    }
+    return null;
+  }
+
+  public static DexMethod getConvertedAPI(
+      DexMethod invokedMethod,
+      DexMethod returnConversion,
+      DexMethod[] parameterConversions,
+      AppView<?> appView) {
+    DexType newReturnType =
+        returnConversion != null ? returnConversion.getParameter(0) : invokedMethod.getReturnType();
+    DexType[] newParameterTypes = new DexType[parameterConversions.length];
+    for (int i = 0; i < parameterConversions.length; i++) {
+      newParameterTypes[i] =
+          parameterConversions[i] != null
+              ? parameterConversions[i].getReturnType()
+              : invokedMethod.getParameter(i);
+    }
+    DexMethod convertedAPI =
+        appView
+            .dexItemFactory()
+            .createMethod(
+                invokedMethod.holder,
+                appView.dexItemFactory().createProto(newReturnType, newParameterTypes),
+                invokedMethod.name);
+    assert convertedAPI
+            == methodWithVivifiedTypeInSignature(invokedMethod, invokedMethod.holder, appView)
+        || invalidType(invokedMethod, returnConversion, parameterConversions, appView) != null;
+    return convertedAPI;
+  }
+
+  private DexMethod computeReturnConversion(
+      DexMethod invokedMethod, DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
+    DexType returnType = invokedMethod.proto.returnType;
+    if (!appView.rewritePrefix.hasRewrittenType(returnType, appView)) {
+      return null;
+    }
+    if (canConvert(returnType)) {
+      DexType newReturnType = DesugaredLibraryAPIConverter.vivifiedTypeFor(returnType, appView);
+      return ensureConversionMethod(returnType, newReturnType, returnType, eventConsumer);
+    }
+    reportInvalidInvoke(returnType, invokedMethod, "return ");
+    return null;
+  }
+
+  private DexMethod[] computeParameterConversions(
+      DexMethod invokedMethod, DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
+    DexMethod[] parameterConversions = new DexMethod[invokedMethod.getArity()];
+    DexType[] parameters = invokedMethod.proto.parameters.values;
+    for (int i = 0; i < parameters.length; i++) {
+      DexType argType = parameters[i];
+      if (appView.rewritePrefix.hasRewrittenType(argType, appView)) {
+        if (canConvert(argType)) {
+          DexType argVivifiedType = vivifiedTypeFor(argType, appView);
+          parameterConversions[i] =
+              ensureConversionMethod(argType, argType, argVivifiedType, eventConsumer);
+        } else {
+          reportInvalidInvoke(argType, invokedMethod, "parameter ");
+        }
+      }
+    }
+    return parameterConversions;
+  }
+
+  private Collection<CfInstruction> rewriteLibraryInvoke(
+      CfInvoke invoke,
+      MethodProcessingContext methodProcessingContext,
+      LocalStackAllocator localStackAllocator,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      ProgramMethod context) {
+    DexMethod invokedMethod = invoke.getMethod();
+    if (trackedAPIs != null) {
+      trackedAPIs.add(invokedMethod);
+    }
+    if (shouldOutlineAPIConversion(invoke, context)) {
+      DexMethod outlinedAPIConversion =
+          createOutlinedAPIConversion(invoke, methodProcessingContext, eventConsumer);
+      return Collections.singletonList(
+          new CfInvoke(Opcodes.INVOKESTATIC, outlinedAPIConversion, false));
+    }
+    return rewriteLibraryInvokeToInlineAPIConversion(
+        invoke, methodProcessingContext, localStackAllocator, eventConsumer);
+  }
+
+  // If the option is set, we try to outline API conversions as much as possible to reduce the
+  // number
+  // of soft verification failures. We cannot outline API conversions through super invokes, to
+  // instance initializers and to non public methods.
+  private boolean shouldOutlineAPIConversion(CfInvoke invoke, ProgramMethod context) {
+    if (invoke.isInvokeSuper(context.getHolderType())) {
+      return false;
+    }
+    if (invoke.getMethod().isInstanceInitializer(appView.dexItemFactory())) {
+      return false;
+    }
+    DexClassAndMethod methodForDesugaring =
+        getMethodForDesugaring(invoke.getMethod(), false, invoke.isInterface(), context);
+    assert methodForDesugaring != null;
+    return methodForDesugaring.getAccessFlags().isPublic();
+  }
+
+  private Collection<CfInstruction> rewriteLibraryInvokeToInlineAPIConversion(
+      CfInvoke invoke,
+      MethodProcessingContext methodProcessingContext,
+      LocalStackAllocator localStackAllocator,
+      CfInstructionDesugaringEventConsumer eventConsumer) {
+
+    DexMethod invokedMethod = invoke.getMethod();
+    DexMethod returnConversion = computeReturnConversion(invokedMethod, eventConsumer);
+    DexMethod[] parameterConversions = computeParameterConversions(invokedMethod, eventConsumer);
+
+    // If only the last 2 parameters require conversion, we do everything inlined.
+    // If other parameters require conversion, we outline the parameter conversion but keep the API
+    // call inlined.
+    // The returned value is always converted inlined.
+    boolean requireOutlinedParameterConversion = false;
+    for (int i = 0; i < parameterConversions.length - 2; i++) {
+      requireOutlinedParameterConversion |= parameterConversions[i] != null;
+    }
+
+    ArrayList<CfInstruction> cfInstructions = new ArrayList<>();
+    if (requireOutlinedParameterConversion) {
+      addOutlineParameterConversionInstructions(
+          parameterConversions,
+          cfInstructions,
+          methodProcessingContext,
+          invokedMethod,
+          localStackAllocator,
+          eventConsumer);
+    } else {
+      addInlineParameterConversionInstructions(parameterConversions, cfInstructions);
+    }
+
+    DexMethod convertedMethod =
+        getConvertedAPI(invokedMethod, returnConversion, parameterConversions, appView);
+    cfInstructions.add(new CfInvoke(invoke.getOpcode(), convertedMethod, invoke.isInterface()));
+
+    if (returnConversion != null) {
+      cfInstructions.add(new CfInvoke(Opcodes.INVOKESTATIC, returnConversion, false));
+    }
+
+    return cfInstructions;
+  }
+
+  // The parameters are converted and returned in an array of converted parameters. The parameter
+  // array then needs to be unwrapped at the call site.
+  private void addOutlineParameterConversionInstructions(
+      DexMethod[] parameterConversions,
+      ArrayList<CfInstruction> cfInstructions,
+      MethodProcessingContext methodProcessingContext,
+      DexMethod invokedMethod,
+      LocalStackAllocator localStackAllocator,
+      CfInstructionDesugaringEventConsumer eventConsumer) {
+    localStackAllocator.allocateLocalStack(4);
+    DexProto newProto =
+        appView
+            .dexItemFactory()
+            .createProto(
+                appView.dexItemFactory().objectArrayType, invokedMethod.getParameters().values);
+    ProgramMethod parameterConversion =
+        appView
+            .getSyntheticItems()
+            .createMethod(
+                SyntheticKind.API_CONVERSION_PARAMETERS,
+                methodProcessingContext.createUniqueContext(),
+                appView,
+                builder ->
+                    builder
+                        .setProto(newProto)
+                        .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                        .setCode(
+                            methodSignature ->
+                                computeParameterConversionCfCode(
+                                    methodSignature.holder, invokedMethod, parameterConversions)));
+    eventConsumer.acceptAPIConversion(parameterConversion);
+    cfInstructions.add(
+        new CfInvoke(Opcodes.INVOKESTATIC, parameterConversion.getReference(), false));
+    for (int i = 0; i < parameterConversions.length; i++) {
+      cfInstructions.add(new CfStackInstruction(Opcode.Dup));
+      cfInstructions.add(new CfConstNumber(i, ValueType.INT));
+      DexType parameterType =
+          parameterConversions[i] != null
+              ? parameterConversions[i].getReturnType()
+              : invokedMethod.getParameter(i);
+      cfInstructions.add(new CfArrayLoad(MemberType.OBJECT));
+      if (parameterType.isPrimitiveType()) {
+        cfInstructions.add(new CfCheckCast(factory.getBoxedForPrimitiveType(parameterType)));
+        DexMethod method = appView.dexItemFactory().getUnboxPrimitiveMethod(parameterType);
+        cfInstructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, method, false));
+      } else {
+        cfInstructions.add(new CfCheckCast(parameterType));
+      }
+      cfInstructions.add(new CfStackInstruction(Opcode.Swap));
+    }
+    cfInstructions.add(new CfStackInstruction(Opcode.Pop));
+  }
+
+  private CfCode computeParameterConversionCfCode(
+      DexType holder, DexMethod invokedMethod, DexMethod[] parameterConversions) {
+    ArrayList<CfInstruction> cfInstructions = new ArrayList<>();
+    cfInstructions.add(new CfConstNumber(parameterConversions.length, ValueType.INT));
+    cfInstructions.add(new CfNewArray(factory.objectArrayType));
+    int stackIndex = 0;
+    for (int i = 0; i < invokedMethod.getArity(); i++) {
+      cfInstructions.add(new CfStackInstruction(Opcode.Dup));
+      cfInstructions.add(new CfConstNumber(i, ValueType.INT));
+      DexType param = invokedMethod.getParameter(i);
+      cfInstructions.add(new CfLoad(ValueType.fromDexType(param), stackIndex));
+      if (parameterConversions[i] != null) {
+        cfInstructions.add(new CfInvoke(Opcodes.INVOKESTATIC, parameterConversions[i], false));
+      }
+      if (param.isPrimitiveType()) {
+        DexMethod method = appView.dexItemFactory().getBoxPrimitiveMethod(param);
+        cfInstructions.add(new CfInvoke(Opcodes.INVOKESTATIC, method, false));
+      }
+      cfInstructions.add(new CfArrayStore(MemberType.OBJECT));
+      if (param == appView.dexItemFactory().longType
+          || param == appView.dexItemFactory().doubleType) {
+        stackIndex++;
+      }
+      stackIndex++;
+    }
+    cfInstructions.add(new CfReturn(ValueType.OBJECT));
+    return new CfCode(
+        holder,
+        invokedMethod.getParameters().size() + 4,
+        invokedMethod.getParameters().size(),
+        cfInstructions);
+  }
+
+  private void addInlineParameterConversionInstructions(
+      DexMethod[] parameterConversions, ArrayList<CfInstruction> cfInstructions) {
+    if (parameterConversions.length > 0
+        && parameterConversions[parameterConversions.length - 1] != null) {
+      cfInstructions.add(
+          new CfInvoke(
+              Opcodes.INVOKESTATIC, parameterConversions[parameterConversions.length - 1], false));
+    }
+    if (parameterConversions.length > 1
+        && parameterConversions[parameterConversions.length - 2] != null) {
+      cfInstructions.add(new CfStackInstruction(Opcode.Swap));
+      cfInstructions.add(
+          new CfInvoke(
+              Opcodes.INVOKESTATIC, parameterConversions[parameterConversions.length - 2], false));
+      cfInstructions.add(new CfStackInstruction(Opcode.Swap));
+    }
+  }
+
+  private DexMethod createOutlinedAPIConversion(
+      CfInvoke invoke,
+      MethodProcessingContext methodProcessingContext,
+      CfInstructionDesugaringEventConsumer eventConsumer) {
+    DexMethod invokedMethod = invoke.getMethod();
+    DexProto newProto =
+        invoke.isInvokeStatic()
+            ? invokedMethod.proto
+            : factory.prependTypeToProto(invokedMethod.getHolderType(), invokedMethod.getProto());
+    DexMethod returnConversion = computeReturnConversion(invokedMethod, eventConsumer);
+    DexMethod[] parameterConversions = computeParameterConversions(invokedMethod, eventConsumer);
+    ProgramMethod outline =
+        appView
+            .getSyntheticItems()
+            .createMethod(
+                SyntheticKind.API_CONVERSION,
+                methodProcessingContext.createUniqueContext(),
+                appView,
+                builder ->
+                    builder
+                        .setProto(newProto)
+                        .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                        .setCode(
+                            methodSignature ->
+                                new APIConversionCfCodeProvider(
+                                        appView,
+                                        methodSignature.holder,
+                                        invoke,
+                                        returnConversion,
+                                        parameterConversions)
+                                    .generateCfCode()));
+    eventConsumer.acceptAPIConversion(outline);
+    return outline.getReference();
+  }
+
   private void rewriteLibraryInvoke(
       IRCode code,
       InvokeMethod invokeMethod,
@@ -574,7 +1077,7 @@
 
   private Instruction createParameterConversion(
       IRCode code, DexType argType, DexType argVivifiedType, Value inValue) {
-    DexMethod conversionMethod = ensureConversionMethod(argType, argType, argVivifiedType);
+    DexMethod conversionMethod = ensureConversionMethod(argType, argType, argVivifiedType, null);
     // The value is null only if the input is null.
     Value convertedValue =
         createConversionValue(code, inValue.getType().nullability(), argVivifiedType, null);
@@ -583,7 +1086,8 @@
 
   private Instruction createReturnConversionAndReplaceUses(
       IRCode code, InvokeMethod invokeMethod, DexType returnType, DexType returnVivifiedType) {
-    DexMethod conversionMethod = ensureConversionMethod(returnType, returnVivifiedType, returnType);
+    DexMethod conversionMethod =
+        ensureConversionMethod(returnType, returnVivifiedType, returnType, null);
     Value outValue = invokeMethod.outValue();
     Value convertedValue =
         createConversionValue(code, Nullability.maybeNull(), returnType, outValue.getLocalInfo());
@@ -600,7 +1104,11 @@
     }
   }
 
-  public DexMethod ensureConversionMethod(DexType type, DexType srcType, DexType destType) {
+  public DexMethod ensureConversionMethod(
+      DexType type,
+      DexType srcType,
+      DexType destType,
+      DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
     // ConversionType holds the methods "rewrittenType convert(type)" and the other way around.
     // But everything is going to be rewritten, so we need to use vivifiedType and type".
     DexType conversionHolder =
@@ -608,8 +1116,8 @@
     if (conversionHolder == null) {
       conversionHolder =
           type == srcType
-              ? wrapperSynthesizor.ensureTypeWrapper(type)
-              : wrapperSynthesizor.ensureVivifiedTypeWrapper(type);
+              ? wrapperSynthesizor.ensureTypeWrapper(type, eventConsumer)
+              : wrapperSynthesizor.ensureVivifiedTypeWrapper(type, eventConsumer);
     }
     assert conversionHolder != null;
     return factory.createMethod(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverterEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverterEventConsumer.java
new file mode 100644
index 0000000..7af64bb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverterEventConsumer.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2021, 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;
+
+import com.android.tools.r8.graph.DexClasspathClass;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
+
+public interface DesugaredLibraryAPIConverterEventConsumer {
+
+  void acceptWrapperProgramClass(DexProgramClass clazz);
+
+  void acceptWrapperClasspathClass(DexClasspathClass clazz);
+
+  void acceptAPIConversion(ProgramMethod method);
+
+  interface DesugaredLibraryAPIConverterPostProcessingEventConsumer {
+
+    void acceptAPIConversionCallback(ProgramMethod method);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterSyntheticHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterSyntheticHelper.java
index 1f8322f..02eb59b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterSyntheticHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterSyntheticHelper.java
@@ -150,6 +150,7 @@
               .setCode(
                   methodSig ->
                       new EmulateInterfaceSyntheticCfCodeProvider(
+                              methodSig.getHolderType(),
                               emulatedDispatchMethod.getHolderType(),
                               desugarMethod,
                               itfMethod,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryWrapperSynthesizer.java
index 6c1e889..7e2164f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryWrapperSynthesizer.java
@@ -117,14 +117,16 @@
     return appView.options().desugaredLibraryConfiguration.getWrapperConversions().contains(type);
   }
 
-  DexType ensureTypeWrapper(DexType type) {
-    return ensureWrappers(type).getWrapper().type;
+  DexType ensureTypeWrapper(DexType type, DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
+    return ensureWrappers(type, eventConsumer).getWrapper().type;
   }
 
-  DexType ensureVivifiedTypeWrapper(DexType type) {
-    return ensureWrappers(type).getVivifiedWrapper().type;
+  DexType ensureVivifiedTypeWrapper(
+      DexType type, DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
+    return ensureWrappers(type, eventConsumer).getVivifiedWrapper().type;
   }
 
+
   public void registerWrapper(DexType type) {
     wrappersToGenerate.add(type);
     assert getValidClassToWrap(type) != null;
@@ -161,13 +163,17 @@
     }
   }
 
-  private Wrappers ensureWrappers(DexType type) {
+  private Wrappers ensureWrappers(
+      DexType type, DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
     assert canGenerateWrapper(type) : type;
     DexClass dexClass = getValidClassToWrap(type);
-    return ensureWrappers(dexClass, ignored -> {});
+    return ensureWrappers(dexClass, ignored -> {}, eventConsumer);
   }
 
-  private Wrappers ensureWrappers(DexClass context, Consumer<DexClasspathClass> creationCallback) {
+  private Wrappers ensureWrappers(
+      DexClass context,
+      Consumer<DexClasspathClass> creationCallback,
+      DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
     DexType type = context.type;
     DexClass wrapper;
     DexClass vivifiedWrapper;
@@ -180,15 +186,20 @@
               vivifiedTypeFor(type),
               type,
               programContext,
-              wrapperField -> synthesizeVirtualMethodsForTypeWrapper(programContext, wrapperField));
+              eventConsumer,
+              wrapperField ->
+                  synthesizeVirtualMethodsForTypeWrapper(
+                      programContext, wrapperField, eventConsumer));
       vivifiedWrapper =
           ensureProgramWrapper(
               SyntheticKind.VIVIFIED_WRAPPER,
               type,
               vivifiedTypeFor(type),
               programContext,
+              eventConsumer,
               wrapperField ->
-                  synthesizeVirtualMethodsForVivifiedTypeWrapper(programContext, wrapperField));
+                  synthesizeVirtualMethodsForVivifiedTypeWrapper(
+                      programContext, wrapperField, eventConsumer));
       DexField wrapperField = getWrapperUniqueField(wrapper);
       DexField vivifiedWrapperField = getWrapperUniqueField(vivifiedWrapper);
       ensureProgramConversionMethod(
@@ -205,7 +216,9 @@
               type,
               classpathOrLibraryContext,
               creationCallback,
-              wrapperField -> synthesizeVirtualMethodsForTypeWrapper(context, wrapperField));
+              eventConsumer,
+              wrapperField ->
+                  synthesizeVirtualMethodsForTypeWrapper(context, wrapperField, eventConsumer));
       vivifiedWrapper =
           ensureClasspathWrapper(
               SyntheticKind.VIVIFIED_WRAPPER,
@@ -213,8 +226,10 @@
               vivifiedTypeFor(type),
               classpathOrLibraryContext,
               creationCallback,
+              eventConsumer,
               wrapperField ->
-                  synthesizeVirtualMethodsForVivifiedTypeWrapper(context, wrapperField));
+                  synthesizeVirtualMethodsForVivifiedTypeWrapper(
+                      context, wrapperField, eventConsumer));
       DexField wrapperField = getWrapperUniqueField(wrapper);
       DexField vivifiedWrapperField = getWrapperUniqueField(vivifiedWrapper);
       ensureClasspathConversionMethod(
@@ -242,6 +257,7 @@
       DexType wrappingType,
       DexType wrappedType,
       DexProgramClass programContext,
+      DesugaredLibraryAPIConverterEventConsumer eventConsumer,
       Function<DexEncodedField, DexEncodedMethod[]> virtualMethodProvider) {
     return appView
         .getSyntheticItems()
@@ -252,9 +268,13 @@
             builder -> buildWrapper(wrappingType, wrappedType, programContext, builder),
             // The creation of virtual methods may require new wrappers, this needs to happen
             // once the wrapper is created to avoid infinite recursion.
-            wrapper ->
-                wrapper.setVirtualMethods(
-                    virtualMethodProvider.apply(getWrapperUniqueEncodedField(wrapper))));
+            wrapper -> {
+              if (eventConsumer != null) {
+                eventConsumer.acceptWrapperProgramClass(wrapper);
+              }
+              wrapper.setVirtualMethods(
+                  virtualMethodProvider.apply(getWrapperUniqueEncodedField(wrapper)));
+            });
   }
 
   private DexClasspathClass ensureClasspathWrapper(
@@ -263,6 +283,7 @@
       DexType wrappedType,
       ClasspathOrLibraryClass classpathOrLibraryContext,
       Consumer<DexClasspathClass> creationCallback,
+      DesugaredLibraryAPIConverterEventConsumer eventConsumer,
       Function<DexEncodedField, DexEncodedMethod[]> virtualMethodProvider) {
     return appView
         .getSyntheticItems()
@@ -276,6 +297,9 @@
             // The creation of virtual methods may require new wrappers, this needs to happen
             // once the wrapper is created to avoid infinite recursion.
             wrapper -> {
+              if (eventConsumer != null) {
+                eventConsumer.acceptWrapperClasspathClass(wrapper);
+              }
               wrapper.setVirtualMethods(
                   virtualMethodProvider.apply(getWrapperUniqueEncodedField(wrapper)));
               creationCallback.accept(wrapper);
@@ -382,7 +406,9 @@
   }
 
   private DexEncodedMethod[] synthesizeVirtualMethodsForVivifiedTypeWrapper(
-      DexClass dexClass, DexEncodedField wrapperField) {
+      DexClass dexClass,
+      DexEncodedField wrapperField,
+      DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
     List<DexEncodedMethod> dexMethods = allImplementedMethods(dexClass);
     List<DexEncodedMethod> generatedMethods = new ArrayList<>();
     // Each method should use only types in their signature, but each method the wrapper forwards
@@ -421,7 +447,12 @@
       } else {
         cfCode =
             new APIConverterVivifiedWrapperCfCodeProvider(
-                    appView, methodToInstall, wrapperField.getReference(), converter, isInterface)
+                    appView,
+                    methodToInstall,
+                    wrapperField.getReference(),
+                    converter,
+                    isInterface,
+                    eventConsumer)
                 .generateCfCode();
       }
       DexEncodedMethod newDexEncodedMethod =
@@ -432,7 +463,9 @@
   }
 
   private DexEncodedMethod[] synthesizeVirtualMethodsForTypeWrapper(
-      DexClass dexClass, DexEncodedField wrapperField) {
+      DexClass dexClass,
+      DexEncodedField wrapperField,
+      DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
     List<DexEncodedMethod> dexMethods = allImplementedMethods(dexClass);
     List<DexEncodedMethod> generatedMethods = new ArrayList<>();
     // Each method should use only vivified types in their signature, but each method the wrapper
@@ -465,7 +498,8 @@
                     dexEncodedMethod.getReference(),
                     wrapperField.getReference(),
                     converter,
-                    isInterface)
+                    isInterface,
+                    eventConsumer)
                 .generateCfCode();
       }
       DexEncodedMethod newDexEncodedMethod =
@@ -574,7 +608,7 @@
         field, fieldAccessFlags, FieldTypeSignature.noSignature(), DexAnnotationSet.empty(), null);
   }
 
-  void finalizeWrappersForL8() {
+  void ensureWrappersForL8(DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
     DesugaredLibraryConfiguration conf = appView.options().desugaredLibraryConfiguration;
     for (DexType type : conf.getWrapperConversions()) {
       assert !conf.getCustomConversions().containsKey(type);
@@ -582,7 +616,7 @@
       // In broken set-ups we can end up having a json files containing wrappers of non desugared
       // classes. Such wrappers are not required since the class won't be rewritten.
       if (validClassToWrap.isProgramClass()) {
-        ensureWrappers(validClassToWrap, ignored -> {});
+        ensureWrappers(validClassToWrap, ignored -> {}, eventConsumer);
       }
     }
   }
@@ -599,7 +633,8 @@
             classpathWrapper -> {
               changed.set(true);
               synthesizedCallback.accept(classpathWrapper);
-            });
+            },
+            null);
       }
     }
   }
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 1c29a09..c73f3e4 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
@@ -130,9 +130,10 @@
         .setProto(emulatedMethod.getProto())
         .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
         .setCode(
-            theMethod ->
+            emulatedInterfaceMethod ->
                 new EmulateInterfaceSyntheticCfCodeProvider(
-                        theMethod.getHolderType(),
+                        emulatedInterfaceMethod.getHolderType(),
+                        method.getHolderType(),
                         companionMethod,
                         libraryMethod,
                         extraDispatchCases,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
index 3e93691..640e740 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
@@ -214,6 +214,10 @@
     }
   }
 
+  public Set<DexString> getEmulatedMethods() {
+    return emulatedMethods;
+  }
+
   private void initializeEmulatedInterfaceVariables() {
     Map<DexType, DexType> emulateLibraryInterface =
         options.desugaredLibraryConfiguration.getEmulateLibraryInterface();
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
index db93ced..214eab9 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
@@ -32,6 +32,8 @@
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverterEventConsumer;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.collections.ImmutableDeque;
 import com.android.tools.r8.utils.collections.ImmutableInt2ReferenceSortedMap;
 import java.util.ArrayList;
@@ -62,22 +64,25 @@
   public static class APIConverterVivifiedWrapperCfCodeProvider
       extends DesugaredLibraryAPIConversionCfCodeProvider {
 
-    DexField wrapperField;
-    DexMethod forwardMethod;
-    DesugaredLibraryAPIConverter converter;
-    boolean itfCall;
+    private final DexField wrapperField;
+    private final DexMethod forwardMethod;
+    private final DesugaredLibraryAPIConverter converter;
+    private final boolean itfCall;
+    private final DesugaredLibraryAPIConverterEventConsumer eventConsumer;
 
     public APIConverterVivifiedWrapperCfCodeProvider(
         AppView<?> appView,
         DexMethod forwardMethod,
         DexField wrapperField,
         DesugaredLibraryAPIConverter converter,
-        boolean itfCall) {
+        boolean itfCall,
+        DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
       super(appView, wrapperField.holder);
       this.forwardMethod = forwardMethod;
       this.wrapperField = wrapperField;
       this.converter = converter;
       this.itfCall = itfCall;
+      this.eventConsumer = eventConsumer;
     }
 
     @Override
@@ -98,7 +103,8 @@
           instructions.add(
               new CfInvoke(
                   Opcodes.INVOKESTATIC,
-                  converter.ensureConversionMethod(param, param, vivifiedTypeFor(param)),
+                  converter.ensureConversionMethod(
+                      param, param, vivifiedTypeFor(param), eventConsumer),
                   false));
           newParameters[index - 1] = vivifiedTypeFor(param);
         }
@@ -130,7 +136,7 @@
             new CfInvoke(
                 Opcodes.INVOKESTATIC,
                 converter.ensureConversionMethod(
-                    returnType, vivifiedTypeFor(returnType), returnType),
+                    returnType, vivifiedTypeFor(returnType), returnType, eventConsumer),
                 false));
       }
       if (returnType == factory.voidType) {
@@ -149,19 +155,22 @@
     DexMethod forwardMethod;
     DesugaredLibraryAPIConverter converter;
     boolean itfCall;
+    private final DesugaredLibraryAPIConverterEventConsumer eventConsumer;
 
     public APIConverterWrapperCfCodeProvider(
         AppView<?> appView,
         DexMethod forwardMethod,
         DexField wrapperField,
         DesugaredLibraryAPIConverter converter,
-        boolean itfCall) {
+        boolean itfCall,
+        DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
       //  Var wrapperField is null if should forward to receiver.
       super(appView, wrapperField == null ? forwardMethod.holder : wrapperField.holder);
       this.forwardMethod = forwardMethod;
       this.wrapperField = wrapperField;
       this.converter = converter;
       this.itfCall = itfCall;
+      this.eventConsumer = eventConsumer;
     }
 
     @Override
@@ -185,7 +194,8 @@
           instructions.add(
               new CfInvoke(
                   Opcodes.INVOKESTATIC,
-                  converter.ensureConversionMethod(param, vivifiedTypeFor(param), param),
+                  converter.ensureConversionMethod(
+                      param, vivifiedTypeFor(param), param, eventConsumer),
                   false));
         }
         if (param == factory.longType || param == factory.doubleType) {
@@ -206,7 +216,7 @@
             new CfInvoke(
                 Opcodes.INVOKESTATIC,
                 converter.ensureConversionMethod(
-                    returnType, returnType, vivifiedTypeFor(returnType)),
+                    returnType, returnType, vivifiedTypeFor(returnType), eventConsumer),
                 false));
         returnType = vivifiedTypeFor(returnType);
       }
@@ -282,6 +292,70 @@
     }
   }
 
+  public static class APIConversionCfCodeProvider extends SyntheticCfCodeProvider {
+
+    private final CfInvoke initialInvoke;
+    private final DexMethod returnConversion;
+    private final DexMethod[] parameterConversions;
+
+    public APIConversionCfCodeProvider(
+        AppView<?> appView,
+        DexType holder,
+        CfInvoke initialInvoke,
+        DexMethod returnConversion,
+        DexMethod[] parameterConversions) {
+      super(appView, holder);
+      this.initialInvoke = initialInvoke;
+      this.returnConversion = returnConversion;
+      this.parameterConversions = parameterConversions;
+    }
+
+    @Override
+    public CfCode generateCfCode() {
+      DexMethod invokedMethod = initialInvoke.getMethod();
+      DexMethod convertedMethod =
+          DesugaredLibraryAPIConverter.getConvertedAPI(
+              invokedMethod, returnConversion, parameterConversions, appView);
+
+      List<CfInstruction> instructions = new ArrayList<>();
+
+      boolean isStatic = initialInvoke.getOpcode() == Opcodes.INVOKESTATIC;
+      if (!isStatic) {
+        instructions.add(new CfLoad(ValueType.fromDexType(invokedMethod.holder), 0));
+      }
+      int receiverShift = BooleanUtils.intValue(!isStatic);
+      int stackIndex = 0;
+      for (int i = 0; i < invokedMethod.getArity(); i++) {
+        DexType param = invokedMethod.getParameter(i);
+        instructions.add(new CfLoad(ValueType.fromDexType(param), stackIndex + receiverShift));
+        if (parameterConversions[i] != null) {
+          instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, parameterConversions[i], false));
+        }
+        if (param == appView.dexItemFactory().longType
+            || param == appView.dexItemFactory().doubleType) {
+          stackIndex++;
+        }
+        stackIndex++;
+      }
+
+      // Actual call to converted value.
+      instructions.add(
+          new CfInvoke(initialInvoke.getOpcode(), convertedMethod, initialInvoke.isInterface()));
+
+      // Return conversion.
+      if (returnConversion != null) {
+        instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, returnConversion, false));
+      }
+
+      if (invokedMethod.getReturnType().isVoidType()) {
+        instructions.add(new CfReturnVoid());
+      } else {
+        instructions.add(new CfReturn(ValueType.fromDexType(invokedMethod.getReturnType())));
+      }
+      return standardCfCodeFromInstructions(instructions);
+    }
+  }
+
   public static class APIConverterConstructorCfCodeProvider extends SyntheticCfCodeProvider {
 
     DexField wrapperField;
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/EmulateInterfaceSyntheticCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/EmulateInterfaceSyntheticCfCodeProvider.java
index 7f26a19..e1ba3b2 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/EmulateInterfaceSyntheticCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/EmulateInterfaceSyntheticCfCodeProvider.java
@@ -36,12 +36,13 @@
   private final List<Pair<DexType, DexMethod>> extraDispatchCases;
 
   public EmulateInterfaceSyntheticCfCodeProvider(
+      DexType holder,
       DexType interfaceType,
       DexMethod companionMethod,
       DexMethod libraryMethod,
       List<Pair<DexType, DexMethod>> extraDispatchCases,
       AppView<?> appView) {
-    super(appView, interfaceType);
+    super(appView, holder);
     this.interfaceType = interfaceType;
     this.companionMethod = companionMethod;
     this.libraryMethod = libraryMethod;
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 4c1ac7c..216fcfc 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -51,7 +51,9 @@
     THROW_NSME("ThrowNSME", 16, true),
     TWR_CLOSE_RESOURCE("TwrCloseResource", 17, true),
     SERVICE_LOADER("ServiceLoad", 18, true),
-    OUTLINE("Outline", 19, true);
+    OUTLINE("Outline", 19, true),
+    API_CONVERSION("APIConversion", 26, true),
+    API_CONVERSION_PARAMETERS("APIConversionParameters", 28, true);
 
     static {
       assert verifyNoOverlappingIds();
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java
index ddebca5..fdca331 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java
@@ -126,7 +126,7 @@
       Path desugaredLib =
           getDesugaredLibraryInCF(parameters, this::configurationForLibraryCompilation);
 
-      // Run on the JVM with desuagred library on classpath.
+      // Run on the JVM with desugared library on classpath.
       testForJvm()
           .addProgramFiles(jar)
           .addRunClasspathFiles(desugaredLib)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideInLibraryTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideInLibraryTest.java
index 1f4d791..b3ac06f 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideInLibraryTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideInLibraryTest.java
@@ -37,7 +37,7 @@
 @RunWith(Parameterized.class)
 public class DefaultMethodOverrideInLibraryTest extends DesugaredLibraryTestBase {
 
-  static final String EXPECTED = StringUtils.lines("0", "42");
+  static final String EXPECTED = StringUtils.lines("0", "42", "0", "0", "42", "42");
 
   private final TestParameters parameters;
 
@@ -127,16 +127,32 @@
   static class MyIntegerArrayListWithoutOverride extends ArrayList<Integer>
       implements MyIntegerList {
     // No override of spliterator.
+
+    public Spliterator<Integer> superSpliteratorItf() {
+      return MyIntegerList.super.spliterator();
+    }
+
+    public Spliterator<Integer> superSpliterator() {
+      return super.spliterator();
+    }
   }
 
   // Derived list with an override of spliterator. The call must hit the classes override and that
-  // will explictly call the custom default method.
+  // will explicitly call the custom default method.
   static class MyIntegerArrayListWithOverride extends ArrayList<Integer> implements MyIntegerList {
 
     @Override
     public Spliterator<Integer> spliterator() {
       return MyIntegerList.super.spliterator();
     }
+
+    public Spliterator<Integer> superSpliteratorItf() {
+      return MyIntegerList.super.spliterator();
+    }
+
+    public Spliterator<Integer> superSpliterator() {
+      return super.spliterator();
+    }
   }
 
   static class Main {
@@ -144,6 +160,11 @@
     public static void main(String[] args) {
       System.out.println(new MyIntegerArrayListWithoutOverride().spliterator().estimateSize());
       System.out.println(new MyIntegerArrayListWithOverride().spliterator().estimateSize());
+      System.out.println(new MyIntegerArrayListWithoutOverride().superSpliterator().estimateSize());
+      System.out.println(new MyIntegerArrayListWithOverride().superSpliterator().estimateSize());
+      System.out.println(
+          new MyIntegerArrayListWithoutOverride().superSpliteratorItf().estimateSize());
+      System.out.println(new MyIntegerArrayListWithOverride().superSpliteratorItf().estimateSize());
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/FreezePeriodTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/FreezePeriodTest.java
new file mode 100644
index 0000000..ac48549
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/FreezePeriodTest.java
@@ -0,0 +1,208 @@
+// Copyright (c) 2019, 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.conversiontests;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
+import java.time.MonthDay;
+import java.util.List;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class FreezePeriodTest extends DesugaredLibraryTestBase {
+
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+  private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.O;
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines(
+          "FP:--05-05;--06-06",
+          "FP:--05-05;--06-06",
+          "FP:--05-05;--06-0601",
+          "MFP:--05-05;--06-06");
+  private static Path CUSTOM_LIB;
+
+  @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getConversionParametersUpToExcluding(MIN_SUPPORTED), BooleanUtils.values());
+  }
+
+  public FreezePeriodTest(TestParameters parameters, boolean shrinkDesugaredLibrary) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  @BeforeClass
+  public static void compileCustomLib() throws Exception {
+    CUSTOM_LIB =
+        testForD8(getStaticTemp())
+            .addProgramClasses(FreezePeriod.class)
+            .setMinApi(MIN_SUPPORTED)
+            .compile()
+            .writeToZip();
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForD8()
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+        .setMinApi(parameters.getApiLevel())
+        .addProgramClasses(Executor.class, MyFreezePeriod.class)
+        .addLibraryClasses(FreezePeriod.class)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .addRunClasspathFiles(CUSTOM_LIB)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testD8CfToCf() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    Path jar =
+        testForD8(Backend.CF)
+            .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+            .setMinApi(parameters.getApiLevel())
+            .addProgramClasses(Executor.class, MyFreezePeriod.class)
+            .addLibraryClasses(FreezePeriod.class)
+            .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+            .compile()
+            .writeToZip();
+    String desugaredLibraryKeepRules = "";
+    if (shrinkDesugaredLibrary && keepRuleConsumer.get() != null) {
+      // Collection keep rules is only implemented in the DEX writer.
+      assertEquals(0, keepRuleConsumer.get().length());
+      desugaredLibraryKeepRules = "-keep class * { *; }";
+    }
+    if (parameters.getRuntime().isDex()) {
+      testForD8()
+          .addProgramFiles(jar)
+          .setMinApi(parameters.getApiLevel())
+          .disableDesugaring()
+          .compile()
+          .addDesugaredCoreLibraryRunClassPath(
+              this::buildDesugaredLibrary,
+              parameters.getApiLevel(),
+              desugaredLibraryKeepRules,
+              shrinkDesugaredLibrary)
+          .addRunClasspathFiles(CUSTOM_LIB)
+          .run(parameters.getRuntime(), Executor.class)
+          .assertSuccessWithOutput(EXPECTED_RESULT);
+    } else {
+      testForJvm()
+          .addProgramFiles(jar)
+          .addRunClasspathFiles(getDesugaredLibraryInCF(parameters, options -> {}))
+          .addRunClasspathFiles(CUSTOM_LIB)
+          .run(parameters.getRuntime(), Executor.class)
+          .assertSuccessWithOutput(EXPECTED_RESULT);
+    }
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForR8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Executor.class)
+        .addProgramClasses(Executor.class, MyFreezePeriod.class)
+        .addLibraryClasses(FreezePeriod.class)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .allowStdoutMessages()
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .addRunClasspathFiles(CUSTOM_LIB)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  static class Executor {
+
+    public static void main(String[] args) {
+      testConversion2ArgsOrLess();
+      testConversion3ArgsOrMore();
+      testConversionWithValuesOnStack();
+      testConversionSuperInit();
+    }
+
+    private static void testConversionSuperInit() {
+      MyFreezePeriod myFreezePeriod = new MyFreezePeriod(MonthDay.of(5, 5), MonthDay.of(6, 6));
+      System.out.println(myFreezePeriod.print());
+    }
+
+    private static void testConversionWithValuesOnStack() {
+      print(0, new FreezePeriod(MonthDay.of(5, 5), MonthDay.of(6, 6)), 1);
+    }
+
+    private static void testConversion3ArgsOrMore() {
+      FreezePeriod freezePeriod2 = new FreezePeriod(MonthDay.of(5, 5), MonthDay.of(6, 6), 0, 1);
+      System.out.println(freezePeriod2.print());
+    }
+
+    private static void testConversion2ArgsOrLess() {
+      FreezePeriod freezePeriod = new FreezePeriod(MonthDay.of(5, 5), MonthDay.of(6, 6));
+      System.out.println(freezePeriod.print());
+    }
+
+    static void print(int i1, FreezePeriod freezePeriod, int i2) {
+      System.out.println(freezePeriod.print() + i1 + i2);
+    }
+  }
+
+  static class MyFreezePeriod extends FreezePeriod {
+
+    public MyFreezePeriod(MonthDay start, MonthDay end) {
+      super(start, end);
+    }
+
+    public String print() {
+      return "M" + super.print();
+    }
+  }
+
+  // This class will be put at compilation time as library and on the runtime class path.
+  // This class is convenient for easy testing. Each method plays the role of methods in the
+  // platform APIs for which argument/return values need conversion.
+  static class FreezePeriod {
+
+    private final MonthDay start;
+    private final MonthDay end;
+
+    public FreezePeriod(MonthDay start, MonthDay end) {
+      this.start = start;
+      this.end = end;
+    }
+
+    public FreezePeriod(MonthDay start, MonthDay end, int extra1, Integer extra2) {
+      this.start = start;
+      this.end = end;
+    }
+
+    public String print() {
+      return "FP:" + start + ";" + end;
+    }
+  }
+}