Introduce LirToLirDesugaredLibraryApiConverter

This introduces a subclass of DesugaredLibraryAPIConverter, which will be used in lir-to-lir library desugaring.

This also rewrites DesugaredLibraryAPIConverter to mostly not use CfInvoke, since we do not have a CfInvoke in lir-to-lir library desugaring.

Bug: b/391572031
Change-Id: I487ecbd75d9f554e8b1a0a01b131833263f6b6c3
diff --git a/src/main/java/com/android/tools/r8/dex/CodeToKeep.java b/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
index fc0080a..5bba7c1 100644
--- a/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
+++ b/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.dex;
 
-import static com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter.isVivifiedType;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.VivifiedTypeUtils.isVivifiedType;
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
diff --git a/src/main/java/com/android/tools/r8/graph/LibraryMethod.java b/src/main/java/com/android/tools/r8/graph/LibraryMethod.java
index 29cdc41..66eb26a 100644
--- a/src/main/java/com/android/tools/r8/graph/LibraryMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/LibraryMethod.java
@@ -11,6 +11,10 @@
     super(holder, method);
   }
 
+  public static LibraryMethod asLibraryMethodOrNull(DexClassAndMethod method) {
+    return method != null && method.isLibraryMethod() ? method.asLibraryMethod() : null;
+  }
+
   @Override
   public DexLibraryClass getHolder() {
     DexClass holder = super.getHolder();
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 aef9da4..d20e039 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
@@ -18,6 +18,7 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.desugar.apimodel.ApiInvokeOutlinerDesugaring;
 import com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicInstructionDesugaring;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.CfToCfDesugaredLibraryApiConverter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.disabledesugarer.DesugaredLibraryDisableDesugarer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.AutoCloseableRetargeter;
@@ -43,7 +44,6 @@
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThrowingConsumer;
-import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
@@ -66,7 +66,7 @@
   private final NestBasedAccessDesugaring nestBasedAccessDesugaring;
   private final DesugaredLibraryRetargeter desugaredLibraryRetargeter;
   private final InterfaceMethodRewriter interfaceMethodRewriter;
-  private final DesugaredLibraryAPIConverter desugaredLibraryAPIConverter;
+  private final CfToCfDesugaredLibraryApiConverter desugaredLibraryAPIConverter;
   private final DesugaredLibraryDisableDesugarer disableDesugarer;
 
   private final CfInstructionDesugaring[][] asmOpcodeOrCompareToIdToDesugaringsMap;
@@ -164,15 +164,11 @@
       desugarings.add(new OutlineArrayCloneFromInterfaceMethodDesugaring(appView));
     }
     desugaredLibraryAPIConverter =
-        appView.options().getLibraryDesugaringOptions().hasTypeRewriter()
-            ? new DesugaredLibraryAPIConverter(
-                appView,
-                SetUtils.newImmutableSetExcludingNullItems(
-                    interfaceMethodRewriter, desugaredLibraryRetargeter, backportedMethodRewriter),
-                interfaceMethodRewriter != null
-                    ? interfaceMethodRewriter.getEmulatedMethods()
-                    : ImmutableSet.of())
-            : null;
+        DesugaredLibraryAPIConverter.createForCfToCf(
+            appView,
+            SetUtils.newImmutableSetExcludingNullItems(
+                interfaceMethodRewriter, desugaredLibraryRetargeter, backportedMethodRewriter),
+            interfaceMethodRewriter);
     if (desugaredLibraryAPIConverter != null) {
       desugarings.add(desugaredLibraryAPIConverter);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/LibraryDesugaringOptions.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/LibraryDesugaringOptions.java
index bfc67df..1a965ae 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/LibraryDesugaringOptions.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/LibraryDesugaringOptions.java
@@ -78,7 +78,11 @@
     return !synthesizedClassPrefix.isEmpty();
   }
 
-  public boolean isR8LirToLirLibraryDesugaringEnabled() {
+  public boolean isCfToCfLibraryDesugaringEnabled() {
+    return !isLirToLirLibraryDesugaringEnabled();
+  }
+
+  public boolean isLirToLirLibraryDesugaringEnabled() {
     return options.partialSubCompilationConfiguration != null;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/R8LibraryDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/R8LibraryDesugaring.java
index 66e7600..16099e7 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/R8LibraryDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/R8LibraryDesugaring.java
@@ -18,7 +18,10 @@
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringCollection;
 import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.LirToLirDesugaredLibraryApiConverter;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodProcessorFacade;
+import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
 import com.android.tools.r8.ir.optimize.DeadCodeRemover;
 import com.android.tools.r8.profile.rewriting.ProfileCollectionAdditions;
 import com.android.tools.r8.utils.InternalOptions;
@@ -48,7 +51,7 @@
     InternalOptions options = appView.options();
     if (options.isDesugaring()
         && options.getLibraryDesugaringOptions().isEnabled()
-        && options.getLibraryDesugaringOptions().isR8LirToLirLibraryDesugaringEnabled()
+        && options.getLibraryDesugaringOptions().isLirToLirLibraryDesugaringEnabled()
         && options.isGeneratingDex()) {
       new R8LibraryDesugaring(appView).run(executorService, timing);
     }
@@ -87,10 +90,15 @@
       ConcurrentProgramMethodSet synthesizedMethods,
       ExecutorService executorService)
       throws ExecutionException {
-    ProcessorContext processorContext = appView.createProcessorContext();
     CfInstructionDesugaringEventConsumer eventConsumer =
         CfInstructionDesugaringEventConsumer.createForR8LirToLirLibraryDesugaring(
             appView, profileCollectionAdditions);
+    // TODO(b/391572031): Implement lir-to-lir interface method rewriting.
+    InterfaceMethodRewriter interfaceMethodRewriter = null;
+    LirToLirDesugaredLibraryApiConverter desugaredLibraryAPIConverter =
+        DesugaredLibraryAPIConverter.createForLirToLir(
+            appView, eventConsumer, interfaceMethodRewriter);
+    ProcessorContext processorContext = appView.createProcessorContext();
     LensCodeRewriterUtils emptyRewriterUtils = LensCodeRewriterUtils.empty();
     ThreadUtils.processItems(
         appView.appInfo().classes(),
@@ -102,7 +110,12 @@
                       processorContext.createMethodProcessingContext(method);
                   R8LibraryDesugaringGraphLens libraryDesugaringGraphLens =
                       new R8LibraryDesugaringGraphLens(
-                          appView, eventConsumer, method, methodProcessingContext);
+                          appView,
+                          desugaredLibraryAPIConverter,
+                          interfaceMethodRewriter,
+                          eventConsumer,
+                          method,
+                          methodProcessingContext);
                   LirConverter.rewriteLirMethodWithLens(
                       method, appView, libraryDesugaringGraphLens, emptyRewriterUtils);
                 }),
@@ -114,7 +127,9 @@
     List<ProgramMethod> needsProcessing = eventConsumer.finalizeDesugaring();
     assert needsProcessing.isEmpty();
 
-    // TODO(b/391572031): Generate desugared library API tracking warnings.
+    if (desugaredLibraryAPIConverter != null) {
+      desugaredLibraryAPIConverter.generateTrackingWarnings();
+    }
 
     // Commit pending synthetics.
     appView.rebuildAppInfo();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/R8LibraryDesugaringGraphLens.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/R8LibraryDesugaringGraphLens.java
index 25a7ae1..22252fd 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/R8LibraryDesugaringGraphLens.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/R8LibraryDesugaringGraphLens.java
@@ -22,12 +22,19 @@
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.LirToLirDesugaredLibraryApiConverter;
+import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
 import com.android.tools.r8.ir.optimize.CustomLensCodeRewriter;
 import java.util.Collections;
 import java.util.Set;
 
 public class R8LibraryDesugaringGraphLens extends DefaultNonIdentityGraphLens {
 
+  private final LirToLirDesugaredLibraryApiConverter desugaredLibraryAPIConverter;
+
+  @SuppressWarnings("UnusedVariable")
+  private final InterfaceMethodRewriter interfaceMethodRewriter;
+
   @SuppressWarnings("UnusedVariable")
   private final CfInstructionDesugaringEventConsumer eventConsumer;
 
@@ -39,10 +46,14 @@
 
   public R8LibraryDesugaringGraphLens(
       AppView<? extends AppInfoWithClassHierarchy> appView,
+      LirToLirDesugaredLibraryApiConverter desugaredLibraryAPIConverter,
+      InterfaceMethodRewriter interfaceMethodRewriter,
       CfInstructionDesugaringEventConsumer eventConsumer,
       ProgramMethod method,
       MethodProcessingContext methodProcessingContext) {
     super(appView);
+    this.desugaredLibraryAPIConverter = desugaredLibraryAPIConverter;
+    this.interfaceMethodRewriter = interfaceMethodRewriter;
     this.eventConsumer = eventConsumer;
     this.method = method;
     this.methodProcessingContext = methodProcessingContext;
@@ -69,6 +80,12 @@
       MethodLookupResult previous, DexMethod context, GraphLens codeLens) {
     // TODO(b/391572031): Implement invoke desugaring.
     assert previous.getPrototypeChanges().isEmpty();
+
+    if (desugaredLibraryAPIConverter != null) {
+      return desugaredLibraryAPIConverter.lookupMethod(
+          previous, method, methodProcessingContext, this);
+    }
+
     return previous;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/CfToCfDesugaredLibraryApiConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/CfToCfDesugaredLibraryApiConverter.java
new file mode 100644
index 0000000..376b294
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/CfToCfDesugaredLibraryApiConverter.java
@@ -0,0 +1,114 @@
+// Copyright (c) 2025, 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.apiconversion;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfOpcodeUtils;
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.InvokeType;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.DesugarDescription;
+import com.android.tools.r8.ir.desugar.FreshLocalProvider;
+import com.android.tools.r8.ir.desugar.LocalStackAllocator;
+import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
+import com.google.common.collect.Iterables;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+import java.util.function.IntConsumer;
+import org.objectweb.asm.Opcodes;
+
+public class CfToCfDesugaredLibraryApiConverter extends DesugaredLibraryAPIConverter
+    implements CfInstructionDesugaring {
+
+  private final Set<CfInstructionDesugaring> precedingDesugarings;
+
+  CfToCfDesugaredLibraryApiConverter(
+      AppView<?> appView,
+      InterfaceMethodRewriter interfaceMethodRewriter,
+      Set<CfInstructionDesugaring> precedingDesugarings) {
+    super(appView, interfaceMethodRewriter);
+    this.precedingDesugarings = precedingDesugarings;
+  }
+
+  @Override
+  public void acceptRelevantAsmOpcodes(IntConsumer consumer) {
+    CfOpcodeUtils.acceptCfInvokeOpcodes(consumer);
+  }
+
+  @Override
+  public DesugarDescription compute(CfInstruction instruction, ProgramMethod context) {
+    if (!instruction.isInvoke()) {
+      return DesugarDescription.nothing();
+    }
+    CfInvoke invoke = instruction.asInvoke();
+    InvokeType invokeType =
+        invoke.getInvokeType(
+            appView, context.getDefinition().getCode().getCodeLens(appView), context);
+    if (!invokeNeedsDesugaring(invoke.getMethod(), invokeType, invoke.isInterface(), context)
+        || isAlreadyDesugared(invoke, context)) {
+      return DesugarDescription.nothing();
+    }
+    return DesugarDescription.builder()
+        .setDesugarRewrite(
+            (position,
+                freshLocalProvider,
+                localStackAllocator,
+                desugaringInfo,
+                eventConsumer,
+                theContext,
+                methodProcessingContext,
+                desugaringCollection,
+                dexItemFactory) ->
+                rewriteLibraryInvoke(
+                    invoke,
+                    invokeType,
+                    methodProcessingContext,
+                    freshLocalProvider,
+                    localStackAllocator,
+                    eventConsumer,
+                    context))
+        .build();
+  }
+
+  private boolean isAlreadyDesugared(CfInvoke invoke, ProgramMethod context) {
+    return Iterables.any(
+        precedingDesugarings, desugaring -> desugaring.compute(invoke, context).needsDesugaring());
+  }
+
+  private Collection<CfInstruction> rewriteLibraryInvoke(
+      CfInvoke invoke,
+      InvokeType invokeType,
+      MethodProcessingContext methodProcessingContext,
+      FreshLocalProvider freshLocalProvider,
+      LocalStackAllocator localStackAllocator,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      ProgramMethod context) {
+    DexMethod retargetMethod =
+        getRetargetMethod(
+            invoke.getMethod(),
+            invokeType,
+            invoke.isInterface(),
+            eventConsumer,
+            context,
+            methodProcessingContext);
+    if (retargetMethod != null) {
+      return Collections.singletonList(new CfInvoke(Opcodes.INVOKESTATIC, retargetMethod, false));
+    }
+    return getConversionCfProvider()
+        .generateInlinedAPIConversion(
+            invoke,
+            invokeType,
+            methodProcessingContext,
+            freshLocalProvider,
+            localStackAllocator,
+            eventConsumer,
+            context);
+  }
+}
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 04b88ee..837225a 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
@@ -1,37 +1,31 @@
 // 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.apiconversion;
 
-import com.android.tools.r8.cf.code.CfInstruction;
-import com.android.tools.r8.cf.code.CfInvoke;
-import com.android.tools.r8.cf.code.CfOpcodeUtils;
+import static com.android.tools.r8.graph.LibraryMethod.asLibraryMethodOrNull;
+
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
 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.graph.LibraryMethod;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.InvokeType;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
-import com.android.tools.r8.ir.desugar.DesugarDescription;
-import com.android.tools.r8.ir.desugar.FreshLocalProvider;
-import com.android.tools.r8.ir.desugar.LocalStackAllocator;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryTypeRewriter;
-import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.LibraryDesugaringOptions;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryAPIConverterEventConsumer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
+import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
-import com.google.common.collect.Iterables;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.Set;
-import java.util.function.IntConsumer;
-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
@@ -47,25 +41,28 @@
 // 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 implements CfInstructionDesugaring {
+public abstract class DesugaredLibraryAPIConverter {
 
-  static final String VIVIFIED_PREFIX = "$-vivified-$.";
-  public static final String DESCRIPTOR_VIVIFIED_PREFIX = "L$-vivified-$/";
-
-  private final AppView<?> appView;
-  private final Set<CfInstructionDesugaring> precedingDesugarings;
+  final AppView<?> appView;
   private final Set<DexString> emulatedMethods;
+  private final MachineDesugaredLibrarySpecification libraryDesugaringSpecification;
+  private final DesugaredLibraryTypeRewriter typeRewriter;
 
   private final DesugaredLibraryWrapperSynthesizer wrapperSynthesizor;
   private final Set<DexMethod> trackedAPIs;
 
-  public DesugaredLibraryAPIConverter(
-      AppView<?> appView,
-      Set<CfInstructionDesugaring> precedingDesugarings,
-      Set<DexString> emulatedMethods) {
+  DesugaredLibraryAPIConverter(
+      AppView<?> appView, InterfaceMethodRewriter interfaceMethodRewriter) {
     this.appView = appView;
-    this.precedingDesugarings = precedingDesugarings;
-    this.emulatedMethods = emulatedMethods;
+    this.emulatedMethods =
+        interfaceMethodRewriter != null
+            ? interfaceMethodRewriter.getEmulatedMethods()
+            : Collections.emptySet();
+    LibraryDesugaringOptions libraryDesugaringOptions =
+        appView.options().getLibraryDesugaringOptions();
+    this.libraryDesugaringSpecification =
+        libraryDesugaringOptions.getMachineDesugaredLibrarySpecification();
+    this.typeRewriter = libraryDesugaringOptions.getTypeRewriter();
     this.wrapperSynthesizor = new DesugaredLibraryWrapperSynthesizer(appView);
     if (appView.options().testing.trackDesugaredApiConversions) {
       trackedAPIs = SetUtils.newConcurrentHashSet();
@@ -74,49 +71,54 @@
     }
   }
 
-  private boolean invokeNeedsDesugaring(CfInvoke invoke, ProgramMethod context) {
+  public static CfToCfDesugaredLibraryApiConverter createForCfToCf(
+      AppView<?> appView,
+      Set<CfInstructionDesugaring> precedingDesugarings,
+      InterfaceMethodRewriter interfaceMethodRewriter) {
+    LibraryDesugaringOptions libraryDesugaringOptions =
+        appView.options().getLibraryDesugaringOptions();
+    if (libraryDesugaringOptions.isCfToCfLibraryDesugaringEnabled()
+        && libraryDesugaringOptions.hasTypeRewriter()) {
+      return new CfToCfDesugaredLibraryApiConverter(
+          appView, interfaceMethodRewriter, precedingDesugarings);
+    }
+    return null;
+  }
+
+  public static LirToLirDesugaredLibraryApiConverter createForLirToLir(
+      AppView<?> appView,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      InterfaceMethodRewriter interfaceMethodRewriter) {
+    LibraryDesugaringOptions libraryDesugaringOptions =
+        appView.options().getLibraryDesugaringOptions();
+    if (libraryDesugaringOptions.isLirToLirLibraryDesugaringEnabled()
+        && libraryDesugaringOptions.hasTypeRewriter()) {
+      return new LirToLirDesugaredLibraryApiConverter(
+          appView, eventConsumer, interfaceMethodRewriter);
+    }
+    return null;
+  }
+
+  boolean invokeNeedsDesugaring(
+      DexMethod invokedMethod, InvokeType invokeType, boolean isInterface, ProgramMethod context) {
     if (isAPIConversionSyntheticType(context.getHolderType(), wrapperSynthesizor, appView)) {
       return false;
     }
-    if (appView.dexItemFactory().multiDexTypes.contains(context.getHolderType())) {
+    if (appView.dexItemFactory().multiDexTypes.contains(context.getHolderType())
+        || invokedMethod.getHolderType().isArrayType()) {
       return false;
     }
-    return shouldRewriteInvoke(invoke.asInvoke(), context);
-  }
-
-  @Override
-  public void acceptRelevantAsmOpcodes(IntConsumer consumer) {
-    CfOpcodeUtils.acceptCfInvokeOpcodes(consumer);
-  }
-
-  @Override
-  public DesugarDescription compute(CfInstruction instruction, ProgramMethod context) {
-    if (!instruction.isInvoke()) {
-      return DesugarDescription.nothing();
+    LibraryMethod resolvedMethod =
+        getMethodForDesugaring(invokedMethod, invokeType, isInterface, context);
+    if (resolvedMethod == null
+        || typeRewriter.hasRewrittenType(resolvedMethod.getHolderType())
+        || isEmulatedInterfaceOverride(resolvedMethod)) {
+      return false;
     }
-    CfInvoke invoke = instruction.asInvoke();
-    if (!invokeNeedsDesugaring(invoke, context)) {
-      return DesugarDescription.nothing();
-    }
-    return DesugarDescription.builder()
-        .setDesugarRewrite(
-            (position,
-                freshLocalProvider,
-                localStackAllocator,
-                desugaringInfo,
-                eventConsumer,
-                theContext,
-                methodProcessingContext,
-                desugaringCollection,
-                dexItemFactory) ->
-                rewriteLibraryInvoke(
-                    invoke,
-                    methodProcessingContext,
-                    freshLocalProvider,
-                    localStackAllocator,
-                    eventConsumer,
-                    context))
-        .build();
+    return libraryDesugaringSpecification
+            .getApiGenericConversion()
+            .containsKey(resolvedMethod.getReference())
+        || typeRewriter.hasRewrittenTypeInSignature(invokedMethod.getProto());
   }
 
   static boolean isAPIConversionSyntheticType(
@@ -125,62 +127,18 @@
         || appView.getSyntheticItems().isSyntheticOfKind(type, kinds -> kinds.API_CONVERSION);
   }
 
-  public static boolean isVivifiedType(DexType type) {
-    return type.descriptor.toString().startsWith(DESCRIPTOR_VIVIFIED_PREFIX);
-  }
-
-  private DexClassAndMethod getMethodForDesugaring(CfInvoke invoke, ProgramMethod context) {
-    DexMethod invokedMethod = invoke.getMethod();
+  private LibraryMethod getMethodForDesugaring(
+      DexMethod invokedMethod, InvokeType invokeType, boolean isInterface, ProgramMethod context) {
     // TODO(b/191656218): Use lookupInvokeSpecial instead when this is all to Cf.
     AppInfoWithClassHierarchy appInfoForDesugaring = appView.appInfoForDesugaring();
-    return invoke.isInvokeSuper(context.getHolderType())
-        ? appInfoForDesugaring.lookupSuperTarget(
-            invokedMethod, context, appView, appInfoForDesugaring)
-        : appInfoForDesugaring
-            .resolveMethodLegacy(invokedMethod, invoke.isInterface())
-            .getResolutionPair();
-  }
-
-  // TODO(b/191656218): Consider caching the result.
-  private boolean shouldRewriteInvoke(CfInvoke invoke, ProgramMethod context) {
-    DexClassAndMethod invokedMethod = getMethodForDesugaring(invoke, context);
-    if (invokedMethod == null) {
-      // Implies a resolution/look-up failure, we do not convert to keep the runtime error.
-      return false;
-    }
-    DexType holderType = invokedMethod.getHolderType();
-    if (appView
-            .options()
-            .getLibraryDesugaringOptions()
-            .getTypeRewriter()
-            .hasRewrittenType(holderType)
-        || holderType.isArrayType()) {
-      return false;
-    }
-    DexClass dexClass = appView.definitionFor(holderType);
-    if (dexClass == null || !dexClass.isLibraryClass()) {
-      return false;
-    }
-    if (isEmulatedInterfaceOverride(invokedMethod)) {
-      return false;
-    }
-    if (isAlreadyDesugared(invoke, context)) {
-      return false;
-    }
-    if (appView
-            .options()
-            .getLibraryDesugaringOptions()
-            .getMachineDesugaredLibrarySpecification()
-            .getApiGenericConversion()
-            .get(invokedMethod.getReference())
-        != null) {
-      return true;
-    }
-    return appView
-        .options()
-        .getLibraryDesugaringOptions()
-        .getTypeRewriter()
-        .hasRewrittenTypeInSignature(invokedMethod.getProto());
+    DexClassAndMethod resolvedMethod =
+        invokeType.isSuper()
+            ? appInfoForDesugaring.lookupSuperTarget(
+                invokedMethod, context, appView, appInfoForDesugaring)
+            : appInfoForDesugaring
+                .resolveMethodLegacy(invokedMethod, isInterface)
+                .getResolutionPair();
+    return asLibraryMethodOrNull(resolvedMethod);
   }
 
   // The problem is that a method can resolve into a library method which is not present at runtime,
@@ -194,40 +152,11 @@
             .appInfoForDesugaring()
             .lookupMaximallySpecificMethod(invokedMethod.getHolder(), invokedMethod.getReference());
     return interfaceResult != null
-        && appView
-            .options()
-            .getLibraryDesugaringOptions()
-            .getMachineDesugaredLibrarySpecification()
+        && libraryDesugaringSpecification
             .getEmulatedInterfaces()
             .containsKey(interfaceResult.getHolderType());
   }
 
-  private boolean isAlreadyDesugared(CfInvoke invoke, ProgramMethod context) {
-    return Iterables.any(
-        precedingDesugarings, desugaring -> desugaring.compute(invoke, context).needsDesugaring());
-  }
-
-  public static DexMethod methodWithVivifiedTypeInSignature(
-      DexMethod originalMethod, DexType holder, AppView<?> appView) {
-    DexType[] newParameters = originalMethod.getParameters().getBacking().clone();
-    DesugaredLibraryTypeRewriter typeRewriter =
-        appView.options().getLibraryDesugaringOptions().getTypeRewriter();
-    int index = 0;
-    for (DexType param : originalMethod.getParameters()) {
-      if (typeRewriter.hasRewrittenType(param)) {
-        newParameters[index] = vivifiedTypeFor(param, appView);
-      }
-      index++;
-    }
-    DexType returnType = originalMethod.getReturnType();
-    DexType newReturnType =
-        typeRewriter.hasRewrittenType(returnType)
-            ? vivifiedTypeFor(returnType, appView)
-            : returnType;
-    DexProto newProto = appView.dexItemFactory().createProto(newReturnType, newParameters);
-    return appView.dexItemFactory().createMethod(holder, newProto, originalMethod.name);
-  }
-
   public void generateTrackingWarnings() {
     generateTrackDesugaredAPIWarnings(trackedAPIs, "", appView);
   }
@@ -247,81 +176,58 @@
     tracked.clear();
   }
 
-  public static DexType vivifiedTypeFor(DexType type, AppView<?> appView) {
-    DexType vivifiedType =
-        appView
-            .dexItemFactory()
-            .createSynthesizedType(
-                DescriptorUtils.javaTypeToDescriptor(VIVIFIED_PREFIX + type.toString()));
-    // We would need to ensure a classpath class for each type to remove this rewriteType call.
-    appView
-        .options()
-        .getLibraryDesugaringOptions()
-        .getTypeRewriter()
-        .rewriteType(vivifiedType, type);
-    return vivifiedType;
+  public DesugaredLibraryConversionCfProvider getConversionCfProvider() {
+    return wrapperSynthesizor.getConversionCfProvider();
   }
 
-  private Collection<CfInstruction> rewriteLibraryInvoke(
-      CfInvoke invoke,
-      MethodProcessingContext methodProcessingContext,
-      FreshLocalProvider freshLocalProvider,
-      LocalStackAllocator localStackAllocator,
-      CfInstructionDesugaringEventConsumer eventConsumer,
-      ProgramMethod context) {
-    DexMethod invokedMethod = invoke.getMethod();
-    if (trackedAPIs != null) {
-      trackedAPIs.add(invokedMethod);
+  public DexMethod getRetargetMethod(
+      DexMethod invokedMethod,
+      InvokeType invokeType,
+      boolean isInterface,
+      DesugaredLibraryAPIConverterEventConsumer eventConsumer,
+      ProgramMethod method,
+      MethodProcessingContext methodProcessingContext) {
+    if (shouldOutlineAPIConversion(invokedMethod, invokeType, isInterface, method)) {
+      return getConversionCfProvider()
+          .generateOutlinedAPIConversion(
+              invokedMethod,
+              invokeType,
+              isInterface,
+              eventConsumer,
+              method,
+              methodProcessingContext)
+          .getReference();
     }
-    if (shouldOutlineAPIConversion(invoke, context)) {
-      DexMethod outlinedAPIConversion =
-          wrapperSynthesizor
-              .getConversionCfProvider()
-              .generateOutlinedAPIConversion(
-                  invoke, eventConsumer, context, methodProcessingContext)
-              .getReference();
-      return Collections.singletonList(
-          new CfInvoke(Opcodes.INVOKESTATIC, outlinedAPIConversion, false));
-    }
-    return wrapperSynthesizor
-        .getConversionCfProvider()
-        .generateInlinedAPIConversion(
-            invoke,
-            methodProcessingContext,
-            freshLocalProvider,
-            localStackAllocator,
-            eventConsumer,
-            context);
+    return null;
   }
 
   // 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 (appView.options().testing.forceInlineApiConversions) {
+  // number of soft verification failures. We cannot outline API conversions through super invokes,
+  // to instance initializers and to non public methods.
+  public boolean shouldOutlineAPIConversion(
+      DexMethod invokedMethod, InvokeType invokeType, boolean isInterface, ProgramMethod context) {
+    if (trackedAPIs != null) {
+      trackedAPIs.add(invokedMethod);
+    }
+    if (appView.testing().forceInlineApiConversions) {
       return false;
     }
-    if (invoke.isInvokeSuper(context.getHolderType())) {
+    if (invokeType.isSuper()) {
       return false;
     }
-    if (invoke.getMethod().isInstanceInitializer(appView.dexItemFactory())) {
+    if (invokedMethod.isInstanceInitializer(appView.dexItemFactory())) {
       return false;
     }
-    DexClassAndMethod methodForDesugaring = getMethodForDesugaring(invoke, context);
+    DexClassAndMethod methodForDesugaring =
+        getMethodForDesugaring(invokedMethod, invokeType, isInterface, context);
     assert methodForDesugaring != null;
     // Specific apis that we never want to outline, namely, apis for stack introspection since it
     // confuses developers in debug mode.
-    if (appView
-        .options()
-        .getLibraryDesugaringOptions()
-        .getMachineDesugaredLibrarySpecification()
+    if (libraryDesugaringSpecification
         .getNeverOutlineApi()
         .contains(methodForDesugaring.getReference())) {
       return false;
     }
     return methodForDesugaring.getAccessFlags().isPublic();
   }
-
-
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java
index adda377..586b913 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion;
 
-import static com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature;
-import static com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter.vivifiedTypeFor;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.VivifiedTypeUtils.methodWithVivifiedTypeInSignature;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.VivifiedTypeUtils.vivifiedTypeFor;
 
 import com.android.tools.r8.cf.code.CfArrayLoad;
 import com.android.tools.r8.cf.code.CfArrayStore;
@@ -34,6 +34,7 @@
 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.code.InvokeType;
 import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
@@ -227,18 +228,17 @@
   }
 
   public ProgramMethod generateOutlinedAPIConversion(
-      CfInvoke invoke,
+      DexMethod method,
+      InvokeType invokeType,
+      boolean isInterface,
       DesugaredLibraryAPIConverterEventConsumer eventConsumer,
       ProgramMethod context,
       MethodProcessingContext methodProcessingContext) {
-    DexMethod method = invoke.getMethod();
-    DexProto newProto = factory.prependHolderToProtoIf(method, !invoke.isInvokeStatic());
+    DexProto newProto = factory.prependHolderToProtoIf(method, !invokeType.isStatic());
     DexMethod returnConversion =
-        computeReturnConversion(
-            method, false, eventConsumer, context, methodProcessingContext::createUniqueContext);
+        computeReturnConversion(method, false, eventConsumer, context, methodProcessingContext);
     DexMethod[] parameterConversions =
-        computeParameterConversions(
-            method, true, eventConsumer, context, methodProcessingContext::createUniqueContext);
+        computeParameterConversions(method, true, eventConsumer, context, methodProcessingContext);
     ProgramMethod outline =
         appView
             .getSyntheticItems()
@@ -259,10 +259,10 @@
                                         methodSignature.holder,
                                         convertedMethod(
                                             method, true, returnConversion, parameterConversions),
-                                        invoke.isInterface(),
+                                        isInterface,
                                         returnConversion,
                                         parameterConversions,
-                                        invoke.getOpcode())
+                                        invokeType.getCfOpcode())
                                     .generateCfCode()));
     eventConsumer.acceptAPIConversionOutline(outline, context);
     return outline;
@@ -270,49 +270,33 @@
 
   public Collection<CfInstruction> generateInlinedAPIConversion(
       CfInvoke invoke,
+      InvokeType invokeType,
       MethodProcessingContext methodProcessingContext,
       FreshLocalProvider freshLocalProvider,
       LocalStackAllocator localStackAllocator,
       CfInstructionDesugaringEventConsumer eventConsumer,
       ProgramMethod context) {
-
     DexMethod invokedMethod = invoke.getMethod();
     DexMethod returnConversion =
         computeReturnConversion(
-            invokedMethod,
-            false,
-            eventConsumer,
-            context,
-            methodProcessingContext::createUniqueContext);
+            invokedMethod, false, eventConsumer, context, methodProcessingContext);
     DexMethod[] parameterConversions =
         computeParameterConversions(
-            invokedMethod,
-            true,
-            eventConsumer,
-            context,
-            methodProcessingContext::createUniqueContext);
+            invokedMethod, true, eventConsumer, context, methodProcessingContext);
 
     ArrayList<CfInstruction> cfInstructions = new ArrayList<>();
     if (!invokedMethod.getParameters().isEmpty()) {
-      // 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;
-      }
-      // We cannot use the swap instruction if the last parameter is wide.
-      requireOutlinedParameterConversion |= invokedMethod.getParameters().getLast().isWideType();
-
-      if (requireOutlinedParameterConversion) {
+      DexMethod parameterConversionTarget =
+          getParameterConversionTarget(
+              parameterConversions, invokedMethod, false, eventConsumer, methodProcessingContext);
+      if (parameterConversionTarget != null) {
         addOutlineParameterConversionInstructions(
             parameterConversions,
+            parameterConversionTarget,
             cfInstructions,
-            methodProcessingContext,
             invokedMethod,
             freshLocalProvider,
-            localStackAllocator,
-            eventConsumer);
+            localStackAllocator);
       } else {
         addInlineParameterConversionInstructions(
             parameterConversions, cfInstructions, invokedMethod);
@@ -327,7 +311,7 @@
       assert returnConversion.getArity() == 1 || returnConversion.getArity() == 2;
       if (returnConversion.getArity() == 2) {
         // If there is a second parameter, pass the receiver.
-        if (!invoke.isInvokeSuper(context.getHolderType())) {
+        if (!invokeType.isSuper()) {
           appView
               .reporter()
               .error(
@@ -344,17 +328,27 @@
     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(
+  public DexMethod getParameterConversionTarget(
       DexMethod[] parameterConversions,
-      ArrayList<CfInstruction> cfInstructions,
-      MethodProcessingContext methodProcessingContext,
       DexMethod invokedMethod,
-      FreshLocalProvider freshLocalProvider,
-      LocalStackAllocator localStackAllocator,
-      CfInstructionDesugaringEventConsumer eventConsumer) {
-    localStackAllocator.allocateLocalStack(4);
+      boolean canSwapWide,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      MethodProcessingContext methodProcessingContext) {
+    // 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;
+    }
+    // We cannot use the swap instruction if the last parameter is wide.
+    if (!canSwapWide && invokedMethod.getParameters().getLast().isWideType()) {
+      requireOutlinedParameterConversion = true;
+    }
+    if (!requireOutlinedParameterConversion) {
+      return null;
+    }
+
     DexProto newProto =
         appView
             .dexItemFactory()
@@ -379,8 +373,20 @@
                                     methodSignature.holder, invokedMethod, parameterConversions)));
     eventConsumer.acceptAPIConversionOutline(
         parameterConversion, methodProcessingContext.getMethodContext());
-    cfInstructions.add(
-        new CfInvoke(Opcodes.INVOKESTATIC, parameterConversion.getReference(), false));
+    return parameterConversion.getReference();
+  }
+
+  // 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,
+      DexMethod parameterConversionTarget,
+      ArrayList<CfInstruction> cfInstructions,
+      DexMethod invokedMethod,
+      FreshLocalProvider freshLocalProvider,
+      LocalStackAllocator localStackAllocator) {
+    localStackAllocator.allocateLocalStack(4);
+    cfInstructions.add(new CfInvoke(Opcodes.INVOKESTATIC, parameterConversionTarget, false));
     int arrayLocal = freshLocalProvider.getFreshLocal(ValueType.OBJECT.requiredRegisters());
     cfInstructions.add(new CfStore(ValueType.OBJECT, arrayLocal));
     for (int i = 0; i < parameterConversions.length; i++) {
@@ -460,6 +466,20 @@
         .getResolvedProgramMethod();
   }
 
+  public DexMethod computeReturnConversion(
+      DexMethod invokedMethod,
+      boolean destIsVivified,
+      DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer,
+      ProgramMethod context,
+      MethodProcessingContext methodProcessingContext) {
+    return computeReturnConversion(
+        invokedMethod,
+        destIsVivified,
+        eventConsumer,
+        context,
+        methodProcessingContext::createUniqueContext);
+  }
+
   private DexMethod computeReturnConversion(
       DexMethod invokedMethod,
       boolean destIsVivified,
@@ -511,6 +531,20 @@
     return null;
   }
 
+  public DexMethod[] computeParameterConversions(
+      DexMethod invokedMethod,
+      boolean destIsVivified,
+      DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer,
+      ProgramMethod context,
+      MethodProcessingContext methodProcessingContext) {
+    return computeParameterConversions(
+        invokedMethod,
+        destIsVivified,
+        eventConsumer,
+        context,
+        methodProcessingContext::createUniqueContext);
+  }
+
   private DexMethod[] computeParameterConversions(
       DexMethod invokedMethod,
       boolean destIsVivified,
@@ -584,7 +618,7 @@
     return conversions == null ? null : conversions[parameterIndex];
   }
 
-  private DexMethod convertedMethod(
+  public DexMethod convertedMethod(
       DexMethod method,
       boolean parameterDestIsVivified,
       DexMethod returnConversion,
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 afe4481..7080794 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
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion;
 
-import static com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter.vivifiedTypeFor;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.VivifiedTypeUtils.vivifiedTypeFor;
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppView;
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 f060b91..35802f8 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
@@ -415,7 +415,7 @@
   }
 
   private DexType vivifiedTypeFor(DexType type) {
-    return DesugaredLibraryAPIConverter.vivifiedTypeFor(type, appView);
+    return VivifiedTypeUtils.vivifiedTypeFor(type, appView);
   }
 
   static class WrapperConversions {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/LirToLirDesugaredLibraryApiConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/LirToLirDesugaredLibraryApiConverter.java
new file mode 100644
index 0000000..69b87a2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/LirToLirDesugaredLibraryApiConverter.java
@@ -0,0 +1,59 @@
+// Copyright (c) 2025, 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.apiconversion;
+
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.lens.MethodLookupResult;
+import com.android.tools.r8.ir.code.InvokeType;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.R8LibraryDesugaringGraphLens;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryAPIConverterEventConsumer;
+import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
+
+public class LirToLirDesugaredLibraryApiConverter extends DesugaredLibraryAPIConverter {
+
+  private final DesugaredLibraryAPIConverterEventConsumer eventConsumer;
+
+  LirToLirDesugaredLibraryApiConverter(
+      AppView<?> appView,
+      DesugaredLibraryAPIConverterEventConsumer eventConsumer,
+      InterfaceMethodRewriter interfaceMethodRewriter) {
+    super(appView, interfaceMethodRewriter);
+    this.eventConsumer = eventConsumer;
+  }
+
+  public MethodLookupResult lookupMethod(
+      MethodLookupResult previous,
+      ProgramMethod context,
+      MethodProcessingContext methodProcessingContext,
+      R8LibraryDesugaringGraphLens lens) {
+    DexMethod method = previous.getReference();
+    InvokeType invokeType = previous.getType();
+    boolean isInterface = previous.isInterface().toBoolean();
+    if (!invokeNeedsDesugaring(method, invokeType, isInterface, context)) {
+      return previous;
+    }
+    DexMethod retargetMethod =
+        getRetargetMethod(
+            method, invokeType, isInterface, eventConsumer, context, methodProcessingContext);
+    if (retargetMethod != null) {
+      return MethodLookupResult.builder(lens, lens.getPrevious())
+          .setReference(retargetMethod)
+          .setReboundReference(retargetMethod)
+          .setIsInterface(false)
+          .setType(InvokeType.STATIC)
+          .build();
+    } else {
+      return MethodLookupResult.builder(lens, lens.getPrevious())
+          .setReference(previous.getReference())
+          .setReboundReference(previous.getReboundReference())
+          .setIsInterface(isInterface)
+          .setNeedsDesugaredLibraryApiConversion()
+          .setType(previous.getType())
+          .build();
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/VivifiedTypeUtils.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/VivifiedTypeUtils.java
new file mode 100644
index 0000000..11ab188
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/VivifiedTypeUtils.java
@@ -0,0 +1,57 @@
+// Copyright (c) 2025, 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.apiconversion;
+
+import com.android.tools.r8.graph.AppView;
+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.desugar.desugaredlibrary.DesugaredLibraryTypeRewriter;
+import com.android.tools.r8.utils.DescriptorUtils;
+
+public class VivifiedTypeUtils {
+
+  private static final String VIVIFIED_PREFIX = "$-vivified-$.";
+  public static final String DESCRIPTOR_VIVIFIED_PREFIX = "L$-vivified-$/";
+
+  public static boolean isVivifiedType(DexType type) {
+    return type.descriptor.toString().startsWith(DESCRIPTOR_VIVIFIED_PREFIX);
+  }
+
+  public static DexMethod methodWithVivifiedTypeInSignature(
+      DexMethod originalMethod, DexType holder, AppView<?> appView) {
+    DexType[] newParameters = originalMethod.getParameters().getBacking().clone();
+    DesugaredLibraryTypeRewriter typeRewriter =
+        appView.options().getLibraryDesugaringOptions().getTypeRewriter();
+    int index = 0;
+    for (DexType param : originalMethod.getParameters()) {
+      if (typeRewriter.hasRewrittenType(param)) {
+        newParameters[index] = vivifiedTypeFor(param, appView);
+      }
+      index++;
+    }
+    DexType returnType = originalMethod.getReturnType();
+    DexType newReturnType =
+        typeRewriter.hasRewrittenType(returnType)
+            ? vivifiedTypeFor(returnType, appView)
+            : returnType;
+    DexProto newProto = appView.dexItemFactory().createProto(newReturnType, newParameters);
+    return appView.dexItemFactory().createMethod(holder, newProto, originalMethod.name);
+  }
+
+  public static DexType vivifiedTypeFor(DexType type, AppView<?> appView) {
+    DexType vivifiedType =
+        appView
+            .dexItemFactory()
+            .createSynthesizedType(
+                DescriptorUtils.javaTypeToDescriptor(VIVIFIED_PREFIX + type.toString()));
+    // We would need to ensure a classpath class for each type to remove this rewriteType call.
+    appView
+        .options()
+        .getLibraryDesugaringOptions()
+        .getTypeRewriter()
+        .rewriteType(vivifiedType, type);
+    return vivifiedType;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/disabledesugarer/DesugaredLibraryDisableDesugarerHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/disabledesugarer/DesugaredLibraryDisableDesugarerHelper.java
index 0a2adad..66763a0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/disabledesugarer/DesugaredLibraryDisableDesugarerHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/disabledesugarer/DesugaredLibraryDisableDesugarerHelper.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.ir.desugar.desugaredlibrary.disabledesugarer;
 
-import static com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature;
-import static com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter.vivifiedTypeFor;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.VivifiedTypeUtils.methodWithVivifiedTypeInSignature;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.VivifiedTypeUtils.vivifiedTypeFor;
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index babfe6a..d98e17f 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -5,6 +5,7 @@
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult.isOverriding;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.VivifiedTypeUtils.isVivifiedType;
 import static com.android.tools.r8.utils.collections.ThrowingSet.isThrowingSet;
 
 import com.android.tools.r8.cf.CfVersion;
@@ -54,7 +55,6 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.InvokeType;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.naming.SeedMapper;
 import com.android.tools.r8.repackaging.RepackagingUtils;
 import com.android.tools.r8.shaking.KeepInfo.Joiner;
@@ -527,7 +527,7 @@
             || deadProtoTypes.contains(type)
             || getMissingClasses().contains(type)
             // TODO(b/150736225): Not sure how to remove these.
-            || DesugaredLibraryAPIConverter.isVivifiedType(type)
+            || isVivifiedType(type)
         : "Failed lookup of non-missing type: " + type;
     return definition;
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 12eecf3..20e6f76 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.graph.FieldAccessInfoImpl.MISSING_FIELD_ACCESS_INFO;
 import static com.android.tools.r8.ir.desugar.LambdaDescriptor.isLambdaMetafactoryMethod;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.VivifiedTypeUtils.methodWithVivifiedTypeInSignature;
 import static com.android.tools.r8.naming.IdentifierNameStringUtils.identifyIdentifier;
 import static com.android.tools.r8.naming.IdentifierNameStringUtils.isReflectionMethod;
 import static com.android.tools.r8.shaking.KeepInfo.Joiner.asClassJoinerOrNull;
@@ -3128,7 +3129,7 @@
               .getTypeRewriter()
               .hasRewrittenTypeInSignature(method.getProto())) {
         DexMethod methodToResolve =
-            DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature(
+            methodWithVivifiedTypeInSignature(
                 method.getReference(), method.getHolderType(), appView);
         assert methodToResolve != method.getReference();
         markLibraryOrClasspathOverrideLive(
diff --git a/src/main/java/com/android/tools/r8/shaking/MissingClasses.java b/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
index a036577..bfe978f 100644
--- a/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
+++ b/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.shaking;
 
-import static com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter.DESCRIPTOR_VIVIFIED_PREFIX;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.VivifiedTypeUtils.DESCRIPTOR_VIVIFIED_PREFIX;
 import static com.android.tools.r8.utils.collections.IdentityHashSetFromMap.newProgramDerivedContextSet;
 
 import com.android.tools.r8.androidapi.CovariantReturnTypeMethods;