Split API conversion Cf code provider

- 1 class to hold all the api conversion method generation
- 1 class for nullable conversions
- prepare for subsequent code sharing

Change-Id: Iac07e0252bcf12a4ecca97e8c9e550c0aff45c5f
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPICallbackSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPICallbackSynthesizer.java
index a0f8961..8b80594 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPICallbackSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPICallbackSynthesizer.java
@@ -23,7 +23,7 @@
 import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryAPICallbackSynthesizorEventConsumer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
-import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APICallbackWrapperCfCodeProvider;
+import com.android.tools.r8.ir.synthetic.apiconverter.APIConversionCfCodeProvider.CallbackConversionCfCodeProvider;
 import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.WorkList;
 import com.google.common.collect.Sets;
@@ -200,7 +200,7 @@
     DexMethod methodToInstall =
         methodWithVivifiedTypeInSignature(originalMethod.getReference(), clazz.type, appView);
     CfCode cfCode =
-        new APICallbackWrapperCfCodeProvider(
+        new CallbackConversionCfCodeProvider(
                 appView,
                 originalMethod.getReference(),
                 wrapperSynthesizor,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPIConverter.java
index ed9c024..a207dec 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPIConverter.java
@@ -35,7 +35,7 @@
 import com.android.tools.r8.ir.desugar.FreshLocalProvider;
 import com.android.tools.r8.ir.desugar.LocalStackAllocator;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryClasspathWrapperSynthesizeEventConsumer;
-import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConversionCfCodeProvider;
+import com.android.tools.r8.ir.synthetic.apiconverter.APIConversionCfCodeProvider.OutlinedAPIConversionCfCodeProvider;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.Iterables;
@@ -293,7 +293,6 @@
       MethodProcessingContext methodProcessingContext) {
     DexType returnType = invokedMethod.proto.returnType;
     if (wrapperSynthesizor.shouldConvert(returnType, invokedMethod, context)) {
-      DexType newReturnType = DesugaredLibraryAPIConverter.vivifiedTypeFor(returnType, appView);
       return wrapperSynthesizor.ensureConversionMethod(
           returnType, false, eventConsumer, methodProcessingContext::createUniqueContext);
     }
@@ -310,7 +309,6 @@
     for (int i = 0; i < parameters.length; i++) {
       DexType argType = parameters[i];
       if (wrapperSynthesizor.shouldConvert(argType, invokedMethod, context)) {
-        DexType argVivifiedType = vivifiedTypeFor(argType, appView);
         parameterConversions[i] =
             wrapperSynthesizor.ensureConversionMethod(
                 argType, true, eventConsumer, methodProcessingContext::createUniqueContext);
@@ -536,7 +534,7 @@
                         .disableAndroidApiLevelCheck()
                         .setCode(
                             methodSignature ->
-                                new APIConversionCfCodeProvider(
+                                new OutlinedAPIConversionCfCodeProvider(
                                         appView,
                                         methodSignature.holder,
                                         invoke,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryEnumConversionSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryEnumConversionSynthesizer.java
index d69a158..a72ee2f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryEnumConversionSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryEnumConversionSynthesizer.java
@@ -17,7 +17,7 @@
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryClasspathWrapperSynthesizeEventConsumer;
-import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.EnumConversionCfCodeProvider;
+import com.android.tools.r8.ir.synthetic.apiconverter.NullableConversionCfCodeProvider.EnumConversionCfCodeProvider;
 import com.android.tools.r8.synthesis.SyntheticClasspathClassBuilder;
 import com.android.tools.r8.synthesis.SyntheticMethodBuilder;
 import com.android.tools.r8.synthesis.SyntheticMethodBuilder.SyntheticCodeGenerator;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
index f2daac5..da552a7 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
@@ -31,11 +31,11 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.CustomConversionDescriptor;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
-import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterConstructorCfCodeProvider;
-import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterVivifiedWrapperCfCodeProvider;
-import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterWrapperCfCodeProvider;
-import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterWrapperConversionCfCodeProvider;
-import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.ArrayConversionCfCodeProvider;
+import com.android.tools.r8.ir.synthetic.apiconverter.APIConversionCfCodeProvider.VivifiedWrapperConversionCfCodeProvider;
+import com.android.tools.r8.ir.synthetic.apiconverter.APIConversionCfCodeProvider.WrapperConversionCfCodeProvider;
+import com.android.tools.r8.ir.synthetic.apiconverter.NullableConversionCfCodeProvider;
+import com.android.tools.r8.ir.synthetic.apiconverter.NullableConversionCfCodeProvider.ArrayConversionCfCodeProvider;
+import com.android.tools.r8.ir.synthetic.apiconverter.WrapperConstructorCfCodeProvider;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.MethodPosition;
 import com.android.tools.r8.position.Position;
@@ -481,7 +481,7 @@
   private CfCode computeProgramConversionMethodCode(
       DexField wrapperField, DexField reverseWrapperField, DexClass context) {
     assert context.isProgramClass();
-    return new APIConverterWrapperConversionCfCodeProvider(
+    return new NullableConversionCfCodeProvider.WrapperConversionCfCodeProvider(
             appView, reverseWrapperField, wrapperField)
         .generateCfCode();
   }
@@ -531,7 +531,7 @@
         .disableAndroidApiLevelCheck()
         .setCode(
             codeSynthesizor ->
-                new APIConverterConstructorCfCodeProvider(
+                new WrapperConstructorCfCodeProvider(
                         appView, wrappedValueField.getReference(), superType)
                     .generateCfCode());
   }
@@ -571,7 +571,7 @@
     } else {
       isInterface = holderClass.isInterface();
     }
-    return new APIConverterVivifiedWrapperCfCodeProvider(
+    return new VivifiedWrapperConversionCfCodeProvider(
             appView, method, wrapperField, this, isInterface, eventConsumer, contextSupplier)
         .generateCfCode();
   }
@@ -600,7 +600,7 @@
     DexClass holderClass = appView.definitionFor(method.getHolderType());
     assert holderClass != null || appView.options().isDesugaredLibraryCompilation();
     boolean isInterface = holderClass == null || holderClass.isInterface();
-    return new APIConverterWrapperCfCodeProvider(
+    return new WrapperConversionCfCodeProvider(
             appView, method, wrapperField, this, isInterface, eventConsumer, contextSupplier)
         .generateCfCode();
   }
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
deleted file mode 100644
index 0ffa663..0000000
--- a/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
+++ /dev/null
@@ -1,646 +0,0 @@
-// 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.ir.synthetic;
-
-import com.android.tools.r8.cf.code.CfArithmeticBinop;
-import com.android.tools.r8.cf.code.CfArithmeticBinop.Opcode;
-import com.android.tools.r8.cf.code.CfArrayLength;
-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.CfConstNull;
-import com.android.tools.r8.cf.code.CfConstNumber;
-import com.android.tools.r8.cf.code.CfConstString;
-import com.android.tools.r8.cf.code.CfFrame;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
-import com.android.tools.r8.cf.code.CfGoto;
-import com.android.tools.r8.cf.code.CfIf;
-import com.android.tools.r8.cf.code.CfIfCmp;
-import com.android.tools.r8.cf.code.CfInstanceFieldRead;
-import com.android.tools.r8.cf.code.CfInstanceFieldWrite;
-import com.android.tools.r8.cf.code.CfInstanceOf;
-import com.android.tools.r8.cf.code.CfInstruction;
-import com.android.tools.r8.cf.code.CfInvoke;
-import com.android.tools.r8.cf.code.CfLabel;
-import com.android.tools.r8.cf.code.CfLoad;
-import com.android.tools.r8.cf.code.CfNew;
-import com.android.tools.r8.cf.code.CfNewArray;
-import com.android.tools.r8.cf.code.CfReturn;
-import com.android.tools.r8.cf.code.CfReturnVoid;
-import com.android.tools.r8.cf.code.CfStackInstruction;
-import com.android.tools.r8.cf.code.CfStaticFieldRead;
-import com.android.tools.r8.cf.code.CfStore;
-import com.android.tools.r8.cf.code.CfThrow;
-import com.android.tools.r8.contexts.CompilationContext.UniqueContext;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProto;
-import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.code.If;
-import com.android.tools.r8.ir.code.MemberType;
-import com.android.tools.r8.ir.code.NumericType;
-import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizer;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryClasspathWrapperSynthesizeEventConsumer;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer;
-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;
-import java.util.Iterator;
-import java.util.List;
-import java.util.function.Supplier;
-import org.objectweb.asm.Opcodes;
-
-public abstract class DesugaredLibraryAPIConversionCfCodeProvider extends SyntheticCfCodeProvider {
-
-  DesugaredLibraryAPIConversionCfCodeProvider(AppView<?> appView, DexType holder) {
-    super(appView, holder);
-  }
-
-  DexType vivifiedTypeFor(DexType type) {
-    return DesugaredLibraryAPIConverter.vivifiedTypeFor(type, appView);
-  }
-
-  public static class APIConverterVivifiedWrapperCfCodeProvider
-      extends AbstractAPIConversionCfCodeProvider {
-
-    private final DexField wrapperField;
-    private final DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer;
-    private final Supplier<UniqueContext> contextSupplier;
-
-    public APIConverterVivifiedWrapperCfCodeProvider(
-        AppView<?> appView,
-        DexMethod forwardMethod,
-        DexField wrapperField,
-        DesugaredLibraryWrapperSynthesizer wrapperSynthesizer,
-        boolean itfCall,
-        DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer,
-        Supplier<UniqueContext> contextSupplier) {
-      super(appView, wrapperField.holder, forwardMethod, wrapperSynthesizer, itfCall);
-      this.wrapperField = wrapperField;
-      this.eventConsumer = eventConsumer;
-      this.contextSupplier = contextSupplier;
-    }
-
-    @Override
-    void generatePushReceiver(List<CfInstruction> instructions) {
-      instructions.add(new CfLoad(ValueType.fromDexType(wrapperField.holder), 0));
-      instructions.add(new CfInstanceFieldRead(wrapperField));
-    }
-
-    @Override
-    DexMethod ensureConversionMethod(DexType type, boolean destIsVivified) {
-      return wrapperSynthesizor.getExistingProgramConversionMethod(
-          type, destIsVivified, eventConsumer, contextSupplier);
-    }
-
-    @Override
-    DexMethod getMethodToForwardTo() {
-      DexType[] newParameters = forwardMethod.proto.parameters.values.clone();
-      for (int i = 0; i < forwardMethod.proto.parameters.values.length; i++) {
-        DexType param = forwardMethod.proto.parameters.values[i];
-        if (wrapperSynthesizor.shouldConvert(param, forwardMethod)) {
-          newParameters[i] = vivifiedTypeFor(param);
-        }
-      }
-
-      DexType returnType = forwardMethod.proto.returnType;
-      DexType forwardMethodReturnType =
-          wrapperSynthesizor.shouldConvert(returnType, forwardMethod)
-              ? vivifiedTypeFor(returnType)
-              : returnType;
-
-      DexProto newProto =
-          appView.dexItemFactory().createProto(forwardMethodReturnType, newParameters);
-      return appView.dexItemFactory().createMethod(wrapperField.type, newProto, forwardMethod.name);
-    }
-
-    @Override
-    DexMethod parameterConversion(DexType param) {
-      return ensureConversionMethod(param, true);
-    }
-
-    @Override
-    DexMethod returnConversion(DexType param) {
-      return ensureConversionMethod(param, false);
-    }
-  }
-
-  public abstract static class AbstractAPIConversionCfCodeProvider
-      extends DesugaredLibraryAPIConversionCfCodeProvider {
-
-    DexMethod forwardMethod;
-    DesugaredLibraryWrapperSynthesizer wrapperSynthesizor;
-    boolean itfCall;
-
-    public AbstractAPIConversionCfCodeProvider(
-        AppView<?> appView,
-        DexType holder,
-        DexMethod forwardMethod,
-        DesugaredLibraryWrapperSynthesizer wrapperSynthesizor,
-        boolean itfCall) {
-      super(appView, holder);
-      this.forwardMethod = forwardMethod;
-      this.wrapperSynthesizor = wrapperSynthesizor;
-      this.itfCall = itfCall;
-    }
-
-    abstract void generatePushReceiver(List<CfInstruction> instructions);
-
-    abstract DexMethod ensureConversionMethod(DexType type, boolean destIsVivified);
-
-    abstract DexMethod parameterConversion(DexType param);
-
-    abstract DexMethod returnConversion(DexType param);
-
-    abstract DexMethod getMethodToForwardTo();
-
-    @Override
-    public CfCode generateCfCode() {
-      DexItemFactory factory = appView.dexItemFactory();
-      List<CfInstruction> instructions = new ArrayList<>();
-      generatePushReceiver(instructions);
-      generateParameterConvertAndLoad(factory, instructions);
-      generateForwardCall(instructions);
-      generateConvertAndReturn(factory, instructions);
-      return standardCfCodeFromInstructions(instructions);
-    }
-
-    private void generateConvertAndReturn(
-        DexItemFactory factory, List<CfInstruction> instructions) {
-      DexType returnType = forwardMethod.proto.returnType;
-      if (wrapperSynthesizor.shouldConvert(returnType, forwardMethod)) {
-        instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, returnConversion(returnType), false));
-        returnType = vivifiedTypeFor(returnType);
-      }
-      if (returnType == factory.voidType) {
-        instructions.add(new CfReturnVoid());
-      } else {
-        instructions.add(new CfReturn(ValueType.fromDexType(returnType)));
-      }
-    }
-
-    private void generateForwardCall(List<CfInstruction> instructions) {
-      if (itfCall) {
-        instructions.add(new CfInvoke(Opcodes.INVOKEINTERFACE, getMethodToForwardTo(), true));
-      } else {
-        instructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, getMethodToForwardTo(), false));
-      }
-    }
-
-    private void generateParameterConvertAndLoad(
-        DexItemFactory factory, List<CfInstruction> instructions) {
-      int stackIndex = 1;
-      for (DexType param : forwardMethod.proto.parameters.values) {
-        instructions.add(new CfLoad(ValueType.fromDexType(param), stackIndex));
-        if (wrapperSynthesizor.shouldConvert(param, forwardMethod)) {
-          instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, parameterConversion(param), false));
-        }
-        if (param == factory.longType || param == factory.doubleType) {
-          stackIndex++;
-        }
-        stackIndex++;
-      }
-    }
-  }
-
-  public static class APICallbackWrapperCfCodeProvider extends AbstractAPIConversionCfCodeProvider {
-
-    private final DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer;
-    private final Supplier<UniqueContext> contextSupplier;
-
-    public APICallbackWrapperCfCodeProvider(
-        AppView<?> appView,
-        DexMethod forwardMethod,
-        DesugaredLibraryWrapperSynthesizer wrapperSynthesizor,
-        boolean itfCall,
-        DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer,
-        Supplier<UniqueContext> contextSupplier) {
-      super(appView, forwardMethod.holder, forwardMethod, wrapperSynthesizor, itfCall);
-      this.eventConsumer = eventConsumer;
-      this.contextSupplier = contextSupplier;
-    }
-
-    @Override
-    void generatePushReceiver(List<CfInstruction> instructions) {
-      instructions.add(new CfLoad(ValueType.fromDexType(forwardMethod.holder), 0));
-    }
-
-    @Override
-    DexMethod ensureConversionMethod(DexType type, boolean destIsVivified) {
-      return wrapperSynthesizor.ensureConversionMethod(
-          type, destIsVivified, eventConsumer, contextSupplier);
-    }
-
-    @Override
-    DexMethod parameterConversion(DexType param) {
-      return ensureConversionMethod(param, false);
-    }
-
-    @Override
-    DexMethod returnConversion(DexType param) {
-      return ensureConversionMethod(param, true);
-    }
-
-    @Override
-    DexMethod getMethodToForwardTo() {
-      return forwardMethod;
-    }
-  }
-
-  public static class APIConverterWrapperCfCodeProvider
-      extends AbstractAPIConversionCfCodeProvider {
-
-    private final DexField wrapperField;
-    private final DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer;
-    private final Supplier<UniqueContext> contextSupplier;
-
-    public APIConverterWrapperCfCodeProvider(
-        AppView<?> appView,
-        DexMethod forwardMethod,
-        DexField wrapperField,
-        DesugaredLibraryWrapperSynthesizer wrapperSynthesizor,
-        boolean itfCall,
-        DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer,
-        Supplier<UniqueContext> contextSupplier) {
-      super(appView, wrapperField.holder, forwardMethod, wrapperSynthesizor, itfCall);
-      this.wrapperField = wrapperField;
-      this.eventConsumer = eventConsumer;
-      this.contextSupplier = contextSupplier;
-    }
-
-    @Override
-    void generatePushReceiver(List<CfInstruction> instructions) {
-      instructions.add(new CfLoad(ValueType.fromDexType(wrapperField.holder), 0));
-      instructions.add(new CfInstanceFieldRead(wrapperField));
-    }
-
-    @Override
-    DexMethod ensureConversionMethod(DexType type, boolean destIsVivified) {
-      return wrapperSynthesizor.getExistingProgramConversionMethod(
-          type, destIsVivified, eventConsumer, contextSupplier);
-    }
-
-    @Override
-    DexMethod parameterConversion(DexType param) {
-      return ensureConversionMethod(param, false);
-    }
-
-    @Override
-    DexMethod returnConversion(DexType param) {
-      return ensureConversionMethod(param, true);
-    }
-
-    @Override
-    DexMethod getMethodToForwardTo() {
-      return forwardMethod;
-    }
-  }
-
-  public static class APIConverterWrapperConversionCfCodeProvider extends SyntheticCfCodeProvider {
-
-    DexField reverseWrapperField;
-    DexField wrapperField;
-
-    public APIConverterWrapperConversionCfCodeProvider(
-        AppView<?> appView, DexField reverseWrapperField, DexField wrapperField) {
-      super(appView, wrapperField.holder);
-      this.reverseWrapperField = reverseWrapperField;
-      this.wrapperField = wrapperField;
-    }
-
-    @Override
-    public CfCode generateCfCode() {
-      DexItemFactory factory = appView.dexItemFactory();
-      List<CfInstruction> instructions = new ArrayList<>();
-
-      DexType argType = wrapperField.type;
-      ImmutableInt2ReferenceSortedMap<FrameType> locals =
-          ImmutableInt2ReferenceSortedMap.<FrameType>builder()
-              .put(0, FrameType.initialized(argType))
-              .build();
-
-      // if (arg == null) { return null };
-      CfLabel nullDest = new CfLabel();
-      instructions.add(new CfLoad(ValueType.fromDexType(argType), 0));
-      instructions.add(new CfIf(If.Type.NE, ValueType.OBJECT, nullDest));
-      instructions.add(new CfConstNull());
-      instructions.add(new CfReturn(ValueType.OBJECT));
-      instructions.add(nullDest);
-      instructions.add(new CfFrame(locals, ImmutableDeque.of()));
-
-      // if (arg instanceOf ReverseWrapper) { return ((ReverseWrapper) arg).wrapperField};
-      assert reverseWrapperField != null;
-      CfLabel unwrapDest = new CfLabel();
-      instructions.add(new CfLoad(ValueType.fromDexType(argType), 0));
-      instructions.add(new CfInstanceOf(reverseWrapperField.holder));
-      instructions.add(new CfIf(If.Type.EQ, ValueType.INT, unwrapDest));
-      instructions.add(new CfLoad(ValueType.fromDexType(argType), 0));
-      instructions.add(new CfCheckCast(reverseWrapperField.holder));
-      instructions.add(new CfInstanceFieldRead(reverseWrapperField));
-      instructions.add(new CfReturn(ValueType.fromDexType(reverseWrapperField.type)));
-      instructions.add(unwrapDest);
-      instructions.add(new CfFrame(locals, ImmutableDeque.of()));
-
-      // return new Wrapper(wrappedValue);
-      instructions.add(new CfNew(wrapperField.holder));
-      instructions.add(CfStackInstruction.fromAsm(Opcodes.DUP));
-      instructions.add(new CfLoad(ValueType.fromDexType(argType), 0));
-      instructions.add(
-          new CfInvoke(
-              Opcodes.INVOKESPECIAL,
-              factory.createMethod(
-                  wrapperField.holder,
-                  factory.createProto(factory.voidType, argType),
-                  factory.constructorMethodName),
-              false));
-      instructions.add(new CfReturn(ValueType.fromDexType(wrapperField.holder)));
-      return standardCfCodeFromInstructions(instructions);
-    }
-  }
-
-  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 ArrayConversionCfCodeProvider extends SyntheticCfCodeProvider {
-
-    private final DexType typeArray;
-    private final DexType convertedTypeArray;
-    private final DexMethod conversion;
-
-    public ArrayConversionCfCodeProvider(
-        AppView<?> appView,
-        DexType holder,
-        DexType typeArray,
-        DexType convertedTypeArray,
-        DexMethod conversion) {
-      super(appView, holder);
-      this.typeArray = typeArray;
-      this.convertedTypeArray = convertedTypeArray;
-      this.conversion = conversion;
-    }
-
-    @Override
-    public CfCode generateCfCode() {
-      DexItemFactory factory = appView.dexItemFactory();
-      List<CfInstruction> instructions = new ArrayList<>();
-
-      // if (arg == null) { return null; }
-      instructions.add(new CfLoad(ValueType.fromDexType(typeArray), 0));
-      instructions.add(new CfConstNull());
-      CfLabel nonNull = new CfLabel();
-      instructions.add(new CfIfCmp(If.Type.NE, ValueType.OBJECT, nonNull));
-      instructions.add(new CfConstNull());
-      instructions.add(new CfReturn(ValueType.fromDexType(convertedTypeArray)));
-      instructions.add(nonNull);
-      instructions.add(
-          new CfFrame(
-              ImmutableInt2ReferenceSortedMap.<FrameType>builder()
-                  .put(0, FrameType.initialized(typeArray))
-                  .build(),
-              ImmutableDeque.of()));
-
-      ImmutableInt2ReferenceSortedMap<FrameType> locals =
-          ImmutableInt2ReferenceSortedMap.<FrameType>builder()
-              .put(0, FrameType.initialized(typeArray))
-              .put(1, FrameType.initialized(factory.intType))
-              .put(2, FrameType.initialized(convertedTypeArray))
-              .put(3, FrameType.initialized(factory.intType))
-              .build();
-
-      // int t1 = arg.length;
-      instructions.add(new CfLoad(ValueType.fromDexType(typeArray), 0));
-      instructions.add(new CfArrayLength());
-      instructions.add(new CfStore(ValueType.INT, 1));
-      // ConvertedType[] t2 = new ConvertedType[t1];
-      instructions.add(new CfLoad(ValueType.INT, 1));
-      instructions.add(new CfNewArray(convertedTypeArray));
-      instructions.add(new CfStore(ValueType.fromDexType(convertedTypeArray), 2));
-      // int t3 = 0;
-      instructions.add(new CfConstNumber(0, ValueType.INT));
-      instructions.add(new CfStore(ValueType.INT, 3));
-      // while (t3 < t1) {
-      CfLabel returnLabel = new CfLabel();
-      CfLabel loopLabel = new CfLabel();
-      instructions.add(loopLabel);
-      instructions.add(new CfFrame(locals, ImmutableDeque.of()));
-      instructions.add(new CfLoad(ValueType.INT, 3));
-      instructions.add(new CfLoad(ValueType.INT, 1));
-      instructions.add(new CfIfCmp(If.Type.GE, ValueType.INT, returnLabel));
-      // t2[t3] = convert(arg[t3]);
-      instructions.add(new CfLoad(ValueType.fromDexType(convertedTypeArray), 2));
-      instructions.add(new CfLoad(ValueType.INT, 3));
-      instructions.add(new CfLoad(ValueType.fromDexType(typeArray), 0));
-      instructions.add(new CfLoad(ValueType.INT, 3));
-      instructions.add(new CfArrayLoad(MemberType.OBJECT));
-      instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, conversion, false));
-      instructions.add(new CfArrayStore(MemberType.OBJECT));
-      // t3 = t3 + 1; }
-      instructions.add(new CfLoad(ValueType.INT, 3));
-      instructions.add(new CfConstNumber(1, ValueType.INT));
-      instructions.add(new CfArithmeticBinop(Opcode.Add, NumericType.INT));
-      instructions.add(new CfStore(ValueType.INT, 3));
-      instructions.add(new CfGoto(loopLabel));
-      // return t2;
-      instructions.add(returnLabel);
-      instructions.add(new CfFrame(locals, ImmutableDeque.of()));
-      instructions.add(new CfLoad(ValueType.fromDexType(convertedTypeArray), 2));
-      instructions.add(new CfReturn(ValueType.fromDexType(convertedTypeArray)));
-      return standardCfCodeFromInstructions(instructions);
-    }
-  }
-
-  public static class EnumConversionCfCodeProvider extends SyntheticCfCodeProvider {
-
-    private final Iterable<DexEncodedField> enumFields;
-    private final DexType enumType;
-    private final DexType convertedType;
-
-    public EnumConversionCfCodeProvider(
-        AppView<?> appView,
-        DexType holder,
-        Iterable<DexEncodedField> enumFields,
-        DexType enumType,
-        DexType convertedType) {
-      super(appView, holder);
-      this.enumFields = enumFields;
-      this.enumType = enumType;
-      this.convertedType = convertedType;
-    }
-
-    @Override
-    public CfCode generateCfCode() {
-      DexItemFactory factory = appView.dexItemFactory();
-      List<CfInstruction> instructions = new ArrayList<>();
-
-      ImmutableInt2ReferenceSortedMap<FrameType> locals =
-          ImmutableInt2ReferenceSortedMap.<FrameType>builder()
-              .put(0, FrameType.initialized(enumType))
-              .build();
-
-      // if (arg == null) { return null; }
-      instructions.add(new CfLoad(ValueType.fromDexType(enumType), 0));
-      instructions.add(new CfConstNull());
-      CfLabel nonNull = new CfLabel();
-      instructions.add(new CfIfCmp(If.Type.NE, ValueType.OBJECT, nonNull));
-      instructions.add(new CfConstNull());
-      instructions.add(new CfReturn(ValueType.fromDexType(convertedType)));
-      instructions.add(nonNull);
-      instructions.add(new CfFrame(locals, ImmutableDeque.of()));
-
-      // if (arg == enumType.enumField1) { return convertedType.enumField1; }
-      Iterator<DexEncodedField> iterator = enumFields.iterator();
-      while (iterator.hasNext()) {
-        DexEncodedField enumField = iterator.next();
-        CfLabel notEqual = new CfLabel();
-        if (iterator.hasNext()) {
-          instructions.add(new CfLoad(ValueType.fromDexType(enumType), 0));
-          instructions.add(
-              new CfStaticFieldRead(factory.createField(enumType, enumType, enumField.getName())));
-          instructions.add(new CfIfCmp(If.Type.NE, ValueType.OBJECT, notEqual));
-        }
-        instructions.add(
-            new CfStaticFieldRead(
-                factory.createField(convertedType, convertedType, enumField.getName())));
-        instructions.add(new CfReturn(ValueType.fromDexType(convertedType)));
-        if (iterator.hasNext()) {
-          instructions.add(notEqual);
-          instructions.add(new CfFrame(locals, ImmutableDeque.of()));
-        }
-      }
-      return standardCfCodeFromInstructions(instructions);
-    }
-  }
-
-  public static class APIConverterConstructorCfCodeProvider extends SyntheticCfCodeProvider {
-
-    private final DexField wrapperField;
-    private final DexType superType;
-
-    public APIConverterConstructorCfCodeProvider(
-        AppView<?> appView, DexField wrapperField, DexType superType) {
-      super(appView, wrapperField.holder);
-      this.wrapperField = wrapperField;
-      this.superType = superType;
-    }
-
-    @Override
-    public CfCode generateCfCode() {
-      DexItemFactory factory = appView.dexItemFactory();
-      List<CfInstruction> instructions = new ArrayList<>();
-      instructions.add(new CfLoad(ValueType.fromDexType(wrapperField.holder), 0));
-      instructions.add(
-          new CfInvoke(
-              Opcodes.INVOKESPECIAL,
-              factory.createMethod(
-                  superType, factory.createProto(factory.voidType), factory.constructorMethodName),
-              false));
-      instructions.add(new CfLoad(ValueType.fromDexType(wrapperField.holder), 0));
-      instructions.add(new CfLoad(ValueType.fromDexType(wrapperField.type), 1));
-      instructions.add(new CfInstanceFieldWrite(wrapperField));
-      instructions.add(new CfReturnVoid());
-      return standardCfCodeFromInstructions(instructions);
-    }
-  }
-
-  public static class APIConverterThrowRuntimeExceptionCfCodeProvider
-      extends SyntheticCfCodeProvider {
-
-    DexString message;
-
-    public APIConverterThrowRuntimeExceptionCfCodeProvider(
-        AppView<?> appView, DexString message, DexType holder) {
-      super(appView, holder);
-      this.message = message;
-    }
-
-    @Override
-    public CfCode generateCfCode() {
-      DexItemFactory factory = appView.dexItemFactory();
-      List<CfInstruction> instructions = new ArrayList<>();
-      instructions.add(new CfNew(factory.runtimeExceptionType));
-      instructions.add(CfStackInstruction.fromAsm(Opcodes.DUP));
-      instructions.add(new CfConstString(message));
-      instructions.add(
-          new CfInvoke(
-              Opcodes.INVOKESPECIAL,
-              factory.createMethod(
-                  factory.runtimeExceptionType,
-                  factory.createProto(factory.voidType, factory.stringType),
-                  factory.constructorMethodName),
-              false));
-      instructions.add(new CfThrow());
-      return standardCfCodeFromInstructions(instructions);
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/APIConversionCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/APIConversionCfCodeProvider.java
new file mode 100644
index 0000000..63cbec0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/APIConversionCfCodeProvider.java
@@ -0,0 +1,325 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.synthetic.apiconverter;
+
+import com.android.tools.r8.cf.code.CfInstanceFieldRead;
+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.CfReturn;
+import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.contexts.CompilationContext.UniqueContext;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryClasspathWrapperSynthesizeEventConsumer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer;
+import com.android.tools.r8.ir.synthetic.SyntheticCfCodeProvider;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+import org.objectweb.asm.Opcodes;
+
+public abstract class APIConversionCfCodeProvider extends SyntheticCfCodeProvider {
+
+  DexMethod forwardMethod;
+  DesugaredLibraryWrapperSynthesizer wrapperSynthesizor;
+  boolean itfCall;
+
+  public APIConversionCfCodeProvider(
+      AppView<?> appView,
+      DexType holder,
+      DexMethod forwardMethod,
+      DesugaredLibraryWrapperSynthesizer wrapperSynthesizor,
+      boolean itfCall) {
+    super(appView, holder);
+    this.forwardMethod = forwardMethod;
+    this.wrapperSynthesizor = wrapperSynthesizor;
+    this.itfCall = itfCall;
+  }
+
+  DexType vivifiedTypeFor(DexType type) {
+    return DesugaredLibraryAPIConverter.vivifiedTypeFor(type, appView);
+  }
+
+  abstract void generatePushReceiver(List<CfInstruction> instructions);
+
+  abstract DexMethod ensureConversionMethod(DexType type, boolean destIsVivified);
+
+  abstract DexMethod parameterConversion(DexType param);
+
+  abstract DexMethod returnConversion(DexType param);
+
+  abstract DexMethod getMethodToForwardTo();
+
+  @Override
+  public CfCode generateCfCode() {
+    List<CfInstruction> instructions = new ArrayList<>();
+    generatePushReceiver(instructions);
+    generateParameterConvertAndLoad(instructions);
+    generateForwardCall(instructions);
+    generateConvertAndReturn(instructions);
+    return standardCfCodeFromInstructions(instructions);
+  }
+
+  private void generateConvertAndReturn(List<CfInstruction> instructions) {
+    DexType returnType = forwardMethod.proto.returnType;
+    if (wrapperSynthesizor.shouldConvert(returnType, forwardMethod)) {
+      instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, returnConversion(returnType), false));
+      returnType = vivifiedTypeFor(returnType);
+    }
+    if (returnType.isVoidType()) {
+      instructions.add(new CfReturnVoid());
+    } else {
+      instructions.add(new CfReturn(ValueType.fromDexType(returnType)));
+    }
+  }
+
+  private void generateForwardCall(List<CfInstruction> instructions) {
+    int opcode = itfCall ? Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL;
+    instructions.add(new CfInvoke(opcode, getMethodToForwardTo(), itfCall));
+  }
+
+  private void generateParameterConvertAndLoad(List<CfInstruction> instructions) {
+    int stackIndex = 1;
+    for (DexType param : forwardMethod.proto.parameters.values) {
+      instructions.add(new CfLoad(ValueType.fromDexType(param), stackIndex));
+      if (wrapperSynthesizor.shouldConvert(param, forwardMethod)) {
+        instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, parameterConversion(param), false));
+      }
+      if (param.isWideType()) {
+        stackIndex++;
+      }
+      stackIndex++;
+    }
+  }
+
+  public static class VivifiedWrapperConversionCfCodeProvider extends APIConversionCfCodeProvider {
+
+    private final DexField wrapperField;
+    private final DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer;
+    private final Supplier<UniqueContext> contextSupplier;
+
+    public VivifiedWrapperConversionCfCodeProvider(
+        AppView<?> appView,
+        DexMethod forwardMethod,
+        DexField wrapperField,
+        DesugaredLibraryWrapperSynthesizer wrapperSynthesizer,
+        boolean itfCall,
+        DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer,
+        Supplier<UniqueContext> contextSupplier) {
+      super(appView, wrapperField.holder, forwardMethod, wrapperSynthesizer, itfCall);
+      this.wrapperField = wrapperField;
+      this.eventConsumer = eventConsumer;
+      this.contextSupplier = contextSupplier;
+    }
+
+    @Override
+    void generatePushReceiver(List<CfInstruction> instructions) {
+      instructions.add(new CfLoad(ValueType.fromDexType(wrapperField.holder), 0));
+      instructions.add(new CfInstanceFieldRead(wrapperField));
+    }
+
+    @Override
+    DexMethod ensureConversionMethod(DexType type, boolean destIsVivified) {
+      return wrapperSynthesizor.getExistingProgramConversionMethod(
+          type, destIsVivified, eventConsumer, contextSupplier);
+    }
+
+    @Override
+    DexMethod getMethodToForwardTo() {
+      DexType[] newParameters = forwardMethod.proto.parameters.values.clone();
+      for (int i = 0; i < forwardMethod.proto.parameters.values.length; i++) {
+        DexType param = forwardMethod.proto.parameters.values[i];
+        if (wrapperSynthesizor.shouldConvert(param, forwardMethod)) {
+          newParameters[i] = vivifiedTypeFor(param);
+        }
+      }
+
+      DexType returnType = forwardMethod.proto.returnType;
+      DexType forwardMethodReturnType =
+          wrapperSynthesizor.shouldConvert(returnType, forwardMethod)
+              ? vivifiedTypeFor(returnType)
+              : returnType;
+
+      DexProto newProto =
+          appView.dexItemFactory().createProto(forwardMethodReturnType, newParameters);
+      return appView.dexItemFactory().createMethod(wrapperField.type, newProto, forwardMethod.name);
+    }
+
+    @Override
+    DexMethod parameterConversion(DexType param) {
+      return ensureConversionMethod(param, true);
+    }
+
+    @Override
+    DexMethod returnConversion(DexType param) {
+      return ensureConversionMethod(param, false);
+    }
+  }
+
+  public static class CallbackConversionCfCodeProvider extends APIConversionCfCodeProvider {
+
+    private final DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer;
+    private final Supplier<UniqueContext> contextSupplier;
+
+    public CallbackConversionCfCodeProvider(
+        AppView<?> appView,
+        DexMethod forwardMethod,
+        DesugaredLibraryWrapperSynthesizer wrapperSynthesizor,
+        boolean itfCall,
+        DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer,
+        Supplier<UniqueContext> contextSupplier) {
+      super(appView, forwardMethod.holder, forwardMethod, wrapperSynthesizor, itfCall);
+      this.eventConsumer = eventConsumer;
+      this.contextSupplier = contextSupplier;
+    }
+
+    @Override
+    void generatePushReceiver(List<CfInstruction> instructions) {
+      instructions.add(new CfLoad(ValueType.fromDexType(forwardMethod.holder), 0));
+    }
+
+    @Override
+    DexMethod ensureConversionMethod(DexType type, boolean destIsVivified) {
+      return wrapperSynthesizor.ensureConversionMethod(
+          type, destIsVivified, eventConsumer, contextSupplier);
+    }
+
+    @Override
+    DexMethod parameterConversion(DexType param) {
+      return ensureConversionMethod(param, false);
+    }
+
+    @Override
+    DexMethod returnConversion(DexType param) {
+      return ensureConversionMethod(param, true);
+    }
+
+    @Override
+    DexMethod getMethodToForwardTo() {
+      return forwardMethod;
+    }
+  }
+
+  public static class WrapperConversionCfCodeProvider extends APIConversionCfCodeProvider {
+
+    private final DexField wrapperField;
+    private final DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer;
+    private final Supplier<UniqueContext> contextSupplier;
+
+    public WrapperConversionCfCodeProvider(
+        AppView<?> appView,
+        DexMethod forwardMethod,
+        DexField wrapperField,
+        DesugaredLibraryWrapperSynthesizer wrapperSynthesizor,
+        boolean itfCall,
+        DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer,
+        Supplier<UniqueContext> contextSupplier) {
+      super(appView, wrapperField.holder, forwardMethod, wrapperSynthesizor, itfCall);
+      this.wrapperField = wrapperField;
+      this.eventConsumer = eventConsumer;
+      this.contextSupplier = contextSupplier;
+    }
+
+    @Override
+    void generatePushReceiver(List<CfInstruction> instructions) {
+      instructions.add(new CfLoad(ValueType.fromDexType(wrapperField.holder), 0));
+      instructions.add(new CfInstanceFieldRead(wrapperField));
+    }
+
+    @Override
+    DexMethod ensureConversionMethod(DexType type, boolean destIsVivified) {
+      return wrapperSynthesizor.getExistingProgramConversionMethod(
+          type, destIsVivified, eventConsumer, contextSupplier);
+    }
+
+    @Override
+    DexMethod parameterConversion(DexType param) {
+      return ensureConversionMethod(param, false);
+    }
+
+    @Override
+    DexMethod returnConversion(DexType param) {
+      return ensureConversionMethod(param, true);
+    }
+
+    @Override
+    DexMethod getMethodToForwardTo() {
+      return forwardMethod;
+    }
+  }
+
+  public static class OutlinedAPIConversionCfCodeProvider extends SyntheticCfCodeProvider {
+
+    private final CfInvoke initialInvoke;
+    private final DexMethod returnConversion;
+    private final DexMethod[] parameterConversions;
+
+    public OutlinedAPIConversionCfCodeProvider(
+        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);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/NullableConversionCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/NullableConversionCfCodeProvider.java
new file mode 100644
index 0000000..98ec596
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/NullableConversionCfCodeProvider.java
@@ -0,0 +1,260 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.synthetic.apiconverter;
+
+import com.android.tools.r8.cf.code.CfArithmeticBinop;
+import com.android.tools.r8.cf.code.CfArithmeticBinop.Opcode;
+import com.android.tools.r8.cf.code.CfArrayLength;
+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.CfConstNull;
+import com.android.tools.r8.cf.code.CfConstNumber;
+import com.android.tools.r8.cf.code.CfFrame;
+import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.cf.code.CfGoto;
+import com.android.tools.r8.cf.code.CfIf;
+import com.android.tools.r8.cf.code.CfIfCmp;
+import com.android.tools.r8.cf.code.CfInstanceFieldRead;
+import com.android.tools.r8.cf.code.CfInstanceOf;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfNew;
+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.CfStaticFieldRead;
+import com.android.tools.r8.cf.code.CfStore;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.synthetic.SyntheticCfCodeProvider;
+import com.android.tools.r8.utils.collections.ImmutableDeque;
+import com.android.tools.r8.utils.collections.ImmutableInt2ReferenceSortedMap;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import org.objectweb.asm.Opcodes;
+
+public abstract class NullableConversionCfCodeProvider extends SyntheticCfCodeProvider {
+
+  protected NullableConversionCfCodeProvider(AppView<?> appView, DexType holder) {
+    super(appView, holder);
+  }
+
+  void generateNullCheck(List<CfInstruction> instructions) {
+    CfLabel nullDest = new CfLabel();
+    instructions.add(new CfLoad(ValueType.OBJECT, 0));
+    instructions.add(new CfIf(If.Type.NE, ValueType.OBJECT, nullDest));
+    instructions.add(new CfConstNull());
+    instructions.add(new CfReturn(ValueType.OBJECT));
+    instructions.add(nullDest);
+  }
+
+  public static class ArrayConversionCfCodeProvider extends NullableConversionCfCodeProvider {
+
+    private final DexType typeArray;
+    private final DexType convertedTypeArray;
+    private final DexMethod conversion;
+
+    public ArrayConversionCfCodeProvider(
+        AppView<?> appView,
+        DexType holder,
+        DexType typeArray,
+        DexType convertedTypeArray,
+        DexMethod conversion) {
+      super(appView, holder);
+      this.typeArray = typeArray;
+      this.convertedTypeArray = convertedTypeArray;
+      this.conversion = conversion;
+    }
+
+    @Override
+    public CfCode generateCfCode() {
+      DexItemFactory factory = appView.dexItemFactory();
+      List<CfInstruction> instructions = new ArrayList<>();
+
+      // if (arg == null) { return null; }
+      generateNullCheck(instructions);
+      instructions.add(
+          new CfFrame(
+              ImmutableInt2ReferenceSortedMap.<FrameType>builder()
+                  .put(0, FrameType.initialized(typeArray))
+                  .build(),
+              ImmutableDeque.of()));
+
+      ImmutableInt2ReferenceSortedMap<FrameType> locals =
+          ImmutableInt2ReferenceSortedMap.<FrameType>builder()
+              .put(0, FrameType.initialized(typeArray))
+              .put(1, FrameType.initialized(factory.intType))
+              .put(2, FrameType.initialized(convertedTypeArray))
+              .put(3, FrameType.initialized(factory.intType))
+              .build();
+
+      // int t1 = arg.length;
+      instructions.add(new CfLoad(ValueType.fromDexType(typeArray), 0));
+      instructions.add(new CfArrayLength());
+      instructions.add(new CfStore(ValueType.INT, 1));
+      // ConvertedType[] t2 = new ConvertedType[t1];
+      instructions.add(new CfLoad(ValueType.INT, 1));
+      instructions.add(new CfNewArray(convertedTypeArray));
+      instructions.add(new CfStore(ValueType.fromDexType(convertedTypeArray), 2));
+      // int t3 = 0;
+      instructions.add(new CfConstNumber(0, ValueType.INT));
+      instructions.add(new CfStore(ValueType.INT, 3));
+      // while (t3 < t1) {
+      CfLabel returnLabel = new CfLabel();
+      CfLabel loopLabel = new CfLabel();
+      instructions.add(loopLabel);
+      instructions.add(new CfFrame(locals, ImmutableDeque.of()));
+      instructions.add(new CfLoad(ValueType.INT, 3));
+      instructions.add(new CfLoad(ValueType.INT, 1));
+      instructions.add(new CfIfCmp(If.Type.GE, ValueType.INT, returnLabel));
+      // t2[t3] = convert(arg[t3]);
+      instructions.add(new CfLoad(ValueType.fromDexType(convertedTypeArray), 2));
+      instructions.add(new CfLoad(ValueType.INT, 3));
+      instructions.add(new CfLoad(ValueType.fromDexType(typeArray), 0));
+      instructions.add(new CfLoad(ValueType.INT, 3));
+      instructions.add(new CfArrayLoad(MemberType.OBJECT));
+      instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, conversion, false));
+      instructions.add(new CfArrayStore(MemberType.OBJECT));
+      // t3 = t3 + 1; }
+      instructions.add(new CfLoad(ValueType.INT, 3));
+      instructions.add(new CfConstNumber(1, ValueType.INT));
+      instructions.add(new CfArithmeticBinop(Opcode.Add, NumericType.INT));
+      instructions.add(new CfStore(ValueType.INT, 3));
+      instructions.add(new CfGoto(loopLabel));
+      // return t2;
+      instructions.add(returnLabel);
+      instructions.add(new CfFrame(locals, ImmutableDeque.of()));
+      instructions.add(new CfLoad(ValueType.fromDexType(convertedTypeArray), 2));
+      instructions.add(new CfReturn(ValueType.fromDexType(convertedTypeArray)));
+      return standardCfCodeFromInstructions(instructions);
+    }
+  }
+
+  public static class EnumConversionCfCodeProvider extends NullableConversionCfCodeProvider {
+
+    private final Iterable<DexEncodedField> enumFields;
+    private final DexType enumType;
+    private final DexType convertedType;
+
+    public EnumConversionCfCodeProvider(
+        AppView<?> appView,
+        DexType holder,
+        Iterable<DexEncodedField> enumFields,
+        DexType enumType,
+        DexType convertedType) {
+      super(appView, holder);
+      this.enumFields = enumFields;
+      this.enumType = enumType;
+      this.convertedType = convertedType;
+    }
+
+    @Override
+    public CfCode generateCfCode() {
+      DexItemFactory factory = appView.dexItemFactory();
+      List<CfInstruction> instructions = new ArrayList<>();
+
+      ImmutableInt2ReferenceSortedMap<FrameType> locals =
+          ImmutableInt2ReferenceSortedMap.<FrameType>builder()
+              .put(0, FrameType.initialized(enumType))
+              .build();
+
+      // if (arg == null) { return null; }
+      generateNullCheck(instructions);
+      instructions.add(new CfFrame(locals, ImmutableDeque.of()));
+
+      // if (arg == enumType.enumField1) { return convertedType.enumField1; }
+      Iterator<DexEncodedField> iterator = enumFields.iterator();
+      while (iterator.hasNext()) {
+        DexEncodedField enumField = iterator.next();
+        CfLabel notEqual = new CfLabel();
+        if (iterator.hasNext()) {
+          instructions.add(new CfLoad(ValueType.fromDexType(enumType), 0));
+          instructions.add(
+              new CfStaticFieldRead(factory.createField(enumType, enumType, enumField.getName())));
+          instructions.add(new CfIfCmp(If.Type.NE, ValueType.OBJECT, notEqual));
+        }
+        instructions.add(
+            new CfStaticFieldRead(
+                factory.createField(convertedType, convertedType, enumField.getName())));
+        instructions.add(new CfReturn(ValueType.fromDexType(convertedType)));
+        if (iterator.hasNext()) {
+          instructions.add(notEqual);
+          instructions.add(new CfFrame(locals, ImmutableDeque.of()));
+        }
+      }
+      return standardCfCodeFromInstructions(instructions);
+    }
+  }
+
+  public static class WrapperConversionCfCodeProvider extends NullableConversionCfCodeProvider {
+
+    DexField reverseWrapperField;
+    DexField wrapperField;
+
+    public WrapperConversionCfCodeProvider(
+        AppView<?> appView, DexField reverseWrapperField, DexField wrapperField) {
+      super(appView, wrapperField.holder);
+      this.reverseWrapperField = reverseWrapperField;
+      this.wrapperField = wrapperField;
+    }
+
+    @Override
+    public CfCode generateCfCode() {
+      DexItemFactory factory = appView.dexItemFactory();
+      List<CfInstruction> instructions = new ArrayList<>();
+
+      DexType argType = wrapperField.type;
+      ImmutableInt2ReferenceSortedMap<FrameType> locals =
+          ImmutableInt2ReferenceSortedMap.<FrameType>builder()
+              .put(0, FrameType.initialized(argType))
+              .build();
+
+      // if (arg == null) { return null };
+      generateNullCheck(instructions);
+      instructions.add(new CfFrame(locals, ImmutableDeque.of()));
+
+      // if (arg instanceOf ReverseWrapper) { return ((ReverseWrapper) arg).wrapperField};
+      assert reverseWrapperField != null;
+      CfLabel unwrapDest = new CfLabel();
+      instructions.add(new CfLoad(ValueType.fromDexType(argType), 0));
+      instructions.add(new CfInstanceOf(reverseWrapperField.holder));
+      instructions.add(new CfIf(If.Type.EQ, ValueType.INT, unwrapDest));
+      instructions.add(new CfLoad(ValueType.fromDexType(argType), 0));
+      instructions.add(new CfCheckCast(reverseWrapperField.holder));
+      instructions.add(new CfInstanceFieldRead(reverseWrapperField));
+      instructions.add(new CfReturn(ValueType.fromDexType(reverseWrapperField.type)));
+      instructions.add(unwrapDest);
+      instructions.add(new CfFrame(locals, ImmutableDeque.of()));
+
+      // return new Wrapper(wrappedValue);
+      instructions.add(new CfNew(wrapperField.holder));
+      instructions.add(CfStackInstruction.fromAsm(Opcodes.DUP));
+      instructions.add(new CfLoad(ValueType.fromDexType(argType), 0));
+      instructions.add(
+          new CfInvoke(
+              Opcodes.INVOKESPECIAL,
+              factory.createMethod(
+                  wrapperField.holder,
+                  factory.createProto(factory.voidType, argType),
+                  factory.constructorMethodName),
+              false));
+      instructions.add(new CfReturn(ValueType.fromDexType(wrapperField.holder)));
+      return standardCfCodeFromInstructions(instructions);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/WrapperConstructorCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/WrapperConstructorCfCodeProvider.java
new file mode 100644
index 0000000..94ba378
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/WrapperConstructorCfCodeProvider.java
@@ -0,0 +1,52 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.synthetic.apiconverter;
+
+import com.android.tools.r8.cf.code.CfInstanceFieldWrite;
+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.CfReturnVoid;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.synthetic.SyntheticCfCodeProvider;
+import java.util.ArrayList;
+import java.util.List;
+import org.objectweb.asm.Opcodes;
+
+public class WrapperConstructorCfCodeProvider extends SyntheticCfCodeProvider {
+
+  private final DexField wrapperField;
+  private final DexType superType;
+
+  public WrapperConstructorCfCodeProvider(
+      AppView<?> appView, DexField wrapperField, DexType superType) {
+    super(appView, wrapperField.holder);
+    this.wrapperField = wrapperField;
+    this.superType = superType;
+  }
+
+  @Override
+  public CfCode generateCfCode() {
+    DexItemFactory factory = appView.dexItemFactory();
+    List<CfInstruction> instructions = new ArrayList<>();
+    instructions.add(new CfLoad(ValueType.fromDexType(wrapperField.holder), 0));
+    instructions.add(
+        new CfInvoke(
+            Opcodes.INVOKESPECIAL,
+            factory.createMethod(
+                superType, factory.createProto(factory.voidType), factory.constructorMethodName),
+            false));
+    instructions.add(new CfLoad(ValueType.fromDexType(wrapperField.holder), 0));
+    instructions.add(new CfLoad(ValueType.fromDexType(wrapperField.type), 1));
+    instructions.add(new CfInstanceFieldWrite(wrapperField));
+    instructions.add(new CfReturnVoid());
+    return standardCfCodeFromInstructions(instructions);
+  }
+}