Skeleton for lir-to-lir library desugaring

Bug: b/391572031
Change-Id: I9f6a485fbff5d5de21c6a5ab0cf861998df1d9ce
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index a39590c..4b63983 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -47,6 +47,7 @@
 import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAmender;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.PrefixRewritingNamingLens;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.R8LibraryDesugaring;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
 import com.android.tools.r8.ir.desugar.records.RecordFieldValuesRewriter;
 import com.android.tools.r8.ir.desugar.records.RecordInstructionDesugaring;
@@ -729,6 +730,8 @@
 
       new NonStartupInStartupOutliner(appView).runIfNecessary(executorService, timing);
 
+      R8LibraryDesugaring.runIfNecessary(appView, executorService, timing);
+
       if (appView.appInfo().hasLiveness()) {
         SyntheticFinalization.finalizeWithLiveness(appView.withLiveness(), executorService, timing);
       } else {
diff --git a/src/main/java/com/android/tools/r8/graph/lens/MethodLookupResult.java b/src/main/java/com/android/tools/r8/graph/lens/MethodLookupResult.java
index f58004e..23eb2e1 100644
--- a/src/main/java/com/android/tools/r8/graph/lens/MethodLookupResult.java
+++ b/src/main/java/com/android/tools/r8/graph/lens/MethodLookupResult.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
 import com.android.tools.r8.ir.code.InvokeType;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.R8LibraryDesugaringGraphLens;
 import com.android.tools.r8.optimize.bridgehoisting.BridgeHoistingLens;
 import com.android.tools.r8.utils.OptionalBool;
 
@@ -20,6 +21,7 @@
 public class MethodLookupResult extends MemberLookupResult<DexMethod> {
 
   private final OptionalBool isInterface;
+  private final boolean needsDesugaredLibraryApiConversion;
   private final InvokeType type;
   private final RewrittenPrototypeDescription prototypeChanges;
 
@@ -27,10 +29,12 @@
       DexMethod reference,
       DexMethod reboundReference,
       OptionalBool isInterface,
+      boolean needsDesugaredLibraryApiConversion,
       InvokeType type,
       RewrittenPrototypeDescription prototypeChanges) {
     super(reference, reboundReference);
     this.isInterface = isInterface;
+    this.needsDesugaredLibraryApiConversion = needsDesugaredLibraryApiConversion;
     this.type = type;
     this.prototypeChanges = prototypeChanges;
   }
@@ -43,6 +47,10 @@
     return isInterface;
   }
 
+  public boolean isNeedsDesugaredLibraryApiConversionSet() {
+    return needsDesugaredLibraryApiConversion;
+  }
+
   public InvokeType getType() {
     return type;
   }
@@ -62,6 +70,7 @@
             || lens.isEnumUnboxerLens()
             || lens.isNumberUnboxerLens()
             || lens instanceof BridgeHoistingLens
+            || lens instanceof R8LibraryDesugaringGraphLens
         : lens;
     return this;
   }
@@ -72,6 +81,7 @@
     private final GraphLens codeLens;
 
     private OptionalBool isInterface = OptionalBool.UNKNOWN;
+    private boolean needsDesugaredLibraryApiConversion = false;
     private RewrittenPrototypeDescription prototypeChanges = RewrittenPrototypeDescription.none();
     private InvokeType type;
 
@@ -89,6 +99,11 @@
       return this;
     }
 
+    public Builder setNeedsDesugaredLibraryApiConversion() {
+      this.needsDesugaredLibraryApiConversion = true;
+      return this;
+    }
+
     public Builder setPrototypeChanges(RewrittenPrototypeDescription prototypeChanges) {
       this.prototypeChanges = prototypeChanges;
       return this;
@@ -101,7 +116,12 @@
 
     public MethodLookupResult build() {
       return new MethodLookupResult(
-              reference, reboundReference, isInterface, type, prototypeChanges)
+              reference,
+              reboundReference,
+              isInterface,
+              needsDesugaredLibraryApiConversion,
+              type,
+              prototypeChanges)
           .verify(lens, codeLens);
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
index 9f294ee..b8ef523 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -119,6 +119,15 @@
         appView, profileCollectionAdditions, eventConsumer);
   }
 
+  public static CfInstructionDesugaringEventConsumer createForR8LirToLirLibraryDesugaring(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      ProfileCollectionAdditions profileCollectionAdditions) {
+    CfInstructionDesugaringEventConsumer eventConsumer =
+        new R8LibraryDesugaringCfInstructionDesugaringEventConsumer();
+    return ProfileRewritingCfInstructionDesugaringEventConsumer.attach(
+        appView, profileCollectionAdditions, eventConsumer);
+  }
+
   public abstract List<ProgramMethod> finalizeDesugaring();
 
   public abstract boolean verifyNothingToFinalize();
@@ -851,4 +860,268 @@
       // Intentionally empty. The method will be hit by tracing if required.
     }
   }
+
+  public static class R8LibraryDesugaringCfInstructionDesugaringEventConsumer
+      extends CfInstructionDesugaringEventConsumer {
+
+    private R8LibraryDesugaringCfInstructionDesugaringEventConsumer() {}
+
+    @Override
+    public void acceptAPIConversionOutline(ProgramMethod method, ProgramMethod context) {
+      assert false;
+    }
+
+    @Override
+    public void acceptClasspathEmulatedInterface(DexClasspathClass clazz) {
+      assert false;
+    }
+
+    @Override
+    public void acceptCollectionConversion(ProgramMethod method, ProgramMethod context) {
+      assert false;
+    }
+
+    @Override
+    public void acceptConstantDynamicRewrittenBootstrapMethod(
+        ProgramMethod method, DexMethod oldSignature) {
+      assert false;
+    }
+
+    @Override
+    public void acceptCovariantRetargetMethod(ProgramMethod method, ProgramMethod context) {
+      assert false;
+    }
+
+    @Override
+    public void acceptDesugaredLibraryRetargeterDispatchClasspathClass(DexClasspathClass clazz) {
+      assert false;
+    }
+
+    @Override
+    public void acceptEnumConversionClasspathClass(DexClasspathClass clazz) {
+      assert false;
+    }
+
+    @Override
+    public void acceptGenericApiConversionStub(DexClasspathClass clazz) {
+      assert false;
+    }
+
+    @Override
+    public void acceptWrapperClasspathClass(DexClasspathClass clazz) {
+      assert false;
+    }
+
+    @Override
+    public List<ProgramMethod> finalizeDesugaring() {
+      assert false;
+      return Collections.emptyList();
+    }
+
+    @Override
+    public boolean verifyNothingToFinalize() {
+      assert false;
+      return true;
+    }
+
+    @Override
+    public void acceptOutlinedMethod(ProgramMethod outlinedMethod, ProgramMethod context) {
+      assert false;
+    }
+
+    @Override
+    public void acceptBackportedMethod(ProgramMethod backportedMethod, ProgramMethod context) {
+      assert false;
+    }
+
+    @Override
+    public void acceptBackportedClass(DexProgramClass backportedClass, ProgramMethod context) {
+      assert false;
+    }
+
+    @Override
+    public void acceptConstantDynamicClass(
+        ConstantDynamicClass constantDynamicClass, ProgramMethod context) {
+      assert false;
+    }
+
+    @Override
+    public void acceptAutoCloseableDispatchMethod(ProgramMethod method, ProgramDefinition context) {
+      assert false;
+    }
+
+    @Override
+    public void acceptAutoCloseableForwardingMethod(
+        ProgramMethod method, ProgramDefinition context) {
+      assert false;
+    }
+
+    @Override
+    public void acceptDesugaredLibraryBridge(ProgramMethod method, ProgramMethod context) {
+      assert false;
+    }
+
+    @Override
+    public void acceptInvokeSpecialBridgeInfo(InvokeSpecialBridgeInfo info) {
+      assert false;
+    }
+
+    @Override
+    public void acceptCompanionClassClinit(ProgramMethod method, ProgramMethod companionMethod) {
+      assert false;
+    }
+
+    @Override
+    public void acceptDefaultAsCompanionMethod(
+        ProgramMethod method, ProgramMethod companionMethod) {
+      assert false;
+    }
+
+    @Override
+    public void acceptPrivateAsCompanionMethod(
+        ProgramMethod method, ProgramMethod companionMethod) {
+      assert false;
+    }
+
+    @Override
+    public void acceptStaticAsCompanionMethod(ProgramMethod method, ProgramMethod companionMethod) {
+      assert false;
+    }
+
+    @Override
+    public void acceptInvokeStaticInterfaceOutliningMethod(
+        ProgramMethod method, ProgramMethod context) {
+      assert false;
+    }
+
+    @Override
+    public void acceptInvokeObjectCloneOutliningMethod(
+        ProgramMethod method, ProgramMethod context) {
+      assert false;
+    }
+
+    @Override
+    public void acceptLambdaClass(LambdaClass lambdaClass, ProgramMethod context) {
+      assert false;
+    }
+
+    @Override
+    public void acceptNestConstructorBridge(
+        ProgramMethod target,
+        ProgramMethod bridge,
+        DexClass argumentClass,
+        DexClassAndMethod context) {
+      assert false;
+    }
+
+    @Override
+    public void acceptNestFieldGetBridge(
+        ProgramField target, ProgramMethod bridge, DexClassAndMethod context) {
+      assert false;
+    }
+
+    @Override
+    public void acceptNestFieldPutBridge(
+        ProgramField target, ProgramMethod bridge, DexClassAndMethod context) {
+      assert false;
+    }
+
+    @Override
+    public void acceptNestMethodBridge(
+        ProgramMethod target, ProgramMethod bridge, DexClassAndMethod context) {
+      assert false;
+    }
+
+    @Override
+    public void acceptRecordClass(DexProgramClass recordTagClass) {
+      assert false;
+    }
+
+    @Override
+    public void acceptRecordClassContext(DexProgramClass recordTagClass, ProgramMethod context) {
+      assert false;
+    }
+
+    @Override
+    public void acceptRecordEqualsHelperMethod(ProgramMethod method, ProgramMethod context) {
+      assert false;
+    }
+
+    @Override
+    public void acceptRecordGetFieldsAsObjectsHelperMethod(
+        ProgramMethod method, ProgramMethod context) {
+      assert false;
+    }
+
+    @Override
+    public void acceptRecordHashCodeHelperMethod(ProgramMethod method, ProgramMethod context) {
+      assert false;
+    }
+
+    @Override
+    public void acceptRecordToStringHelperMethod(ProgramMethod method, ProgramMethod context) {
+      assert false;
+    }
+
+    @Override
+    public void acceptTwrCloseResourceMethod(ProgramMethod closeMethod, ProgramMethod context) {
+      assert false;
+    }
+
+    @Override
+    public void acceptTypeSwitchMethod(ProgramMethod typeSwitchMethod, ProgramMethod context) {
+      assert false;
+    }
+
+    @Override
+    public void acceptTypeSwitchClass(DexProgramClass typeSwitchClass, ProgramMethod context) {
+      assert false;
+    }
+
+    @Override
+    public void acceptVarHandleDesugaringClass(DexProgramClass clazz) {
+      assert false;
+    }
+
+    @Override
+    public void acceptVarHandleDesugaringClassContext(
+        DexProgramClass clazz, ProgramDefinition context) {
+      assert false;
+    }
+
+    @Override
+    public void acceptUtilityToStringIfNotNullMethod(ProgramMethod method, ProgramMethod context) {
+      assert false;
+    }
+
+    @Override
+    public void acceptUtilityThrowClassCastExceptionIfNotNullMethod(
+        ProgramMethod method, ProgramMethod context) {
+      assert false;
+    }
+
+    @Override
+    public void acceptUtilityThrowIllegalAccessErrorMethod(
+        ProgramMethod method, ProgramMethod context) {
+      assert false;
+    }
+
+    @Override
+    public void acceptUtilityThrowIncompatibleClassChangeErrorMethod(
+        ProgramMethod method, ProgramMethod context) {
+      assert false;
+    }
+
+    @Override
+    public void acceptUtilityThrowNoSuchMethodErrorMethod(
+        ProgramMethod method, ProgramMethod context) {
+      assert false;
+    }
+
+    @Override
+    public void acceptUtilityThrowRuntimeExceptionWithMessageMethod(
+        ProgramMethod method, ProgramMethod context) {
+      assert false;
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java
index 22b75bb..996a13a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.desugar;
 
+import com.android.tools.r8.errors.Unreachable;
+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.DexProgramClass;
@@ -36,6 +38,13 @@
     return empty();
   }
 
+  @SuppressWarnings("DoNotCallSuggester")
+  public static CfPostProcessingDesugaringCollection createForR8LirToLirLibraryDesugaring(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      InterfaceMethodProcessorFacade interfaceDesugaring) {
+    throw new Unreachable();
+  }
+
   static CfPostProcessingDesugaringCollection empty() {
     return EmptyCfPostProcessingDesugaringCollection.getInstance();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
index e1a3cd9..2c8e392 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.desugar;
 
+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;
@@ -63,6 +64,15 @@
         appView, profileCollectionAdditions, eventConsumer);
   }
 
+  public static CfPostProcessingDesugaringEventConsumer createForR8LirToLirLibraryDesugaring(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      ProfileCollectionAdditions profileCollectionAdditions) {
+    CfPostProcessingDesugaringEventConsumer eventConsumer =
+        new R8LibraryDesugaringPostProcessingDesugaringEventConsumer();
+    return ProfileRewritingCfPostProcessingDesugaringEventConsumer.attach(
+        appView, profileCollectionAdditions, eventConsumer);
+  }
+
   public abstract Set<DexMethod> getNewlyLiveMethods();
 
   public abstract void finalizeDesugaring() throws ExecutionException;
@@ -309,4 +319,109 @@
       additions.addLiveMethod(method);
     }
   }
+
+  public static class R8LibraryDesugaringPostProcessingDesugaringEventConsumer
+      extends CfPostProcessingDesugaringEventConsumer {
+
+    private R8LibraryDesugaringPostProcessingDesugaringEventConsumer() {}
+
+    @Override
+    public void acceptAPIConversionCallback(
+        ProgramMethod callbackMethod, ProgramMethod convertedMethod) {
+      assert false;
+    }
+
+    @Override
+    public void acceptDesugaredLibraryRetargeterDispatchClasspathClass(DexClasspathClass clazz) {
+      assert false;
+    }
+
+    @Override
+    public void acceptDesugaredLibraryRetargeterForwardingMethod(
+        ProgramMethod method, EmulatedDispatchMethodDescriptor descriptor) {
+      assert false;
+    }
+
+    @Override
+    public void acceptEmulatedInterfaceMarkerInterface(
+        DexProgramClass clazz, DexClasspathClass newInterface) {
+      assert false;
+    }
+
+    @Override
+    public void acceptInterfaceInjection(DexProgramClass clazz, DexClass newInterface) {
+      assert false;
+    }
+
+    @Override
+    public void acceptInterfaceMethodDesugaringForwardingMethod(
+        ProgramMethod method, DexClassAndMethod baseMethod) {
+      assert false;
+    }
+
+    @Override
+    public void acceptWrapperClasspathClass(DexClasspathClass clazz) {
+      assert false;
+    }
+
+    @Override
+    public Set<DexMethod> getNewlyLiveMethods() {
+      assert false;
+      return Collections.emptySet();
+    }
+
+    @Override
+    public void finalizeDesugaring() {
+      assert false;
+    }
+
+    @Override
+    public void warnMissingInterface(
+        DexProgramClass context, DexType missing, InterfaceDesugaringSyntheticHelper helper) {
+      assert false;
+    }
+
+    @Override
+    public void acceptCollectionConversion(ProgramMethod arrayConversion, ProgramMethod context) {
+      assert false;
+    }
+
+    @Override
+    public void acceptEnumConversionClasspathClass(DexClasspathClass clazz) {
+      assert false;
+    }
+
+    @Override
+    public void acceptGenericApiConversionStub(DexClasspathClass dexClasspathClass) {
+      assert false;
+    }
+
+    @Override
+    public void acceptAutoCloseableDispatchMethod(ProgramMethod method, ProgramDefinition context) {
+      assert false;
+    }
+
+    @Override
+    public void acceptAutoCloseableForwardingMethod(
+        ProgramMethod method, ProgramDefinition context) {
+      assert false;
+    }
+
+    @Override
+    public void acceptAutoCloseableInterfaceInjection(
+        DexProgramClass clazz, DexClass newInterface) {
+      assert false;
+    }
+
+    @Override
+    public void acceptCovariantRetargetMethod(ProgramMethod method, ProgramMethod context) {
+      assert false;
+    }
+
+    @Override
+    public void acceptThrowingMethod(
+        ProgramMethod method, DexType errorType, FailedResolutionResult resolutionResult) {
+      assert false;
+    }
+  }
 }
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 b350cb3..bfc67df 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
@@ -70,10 +70,18 @@
     return machineDesugaredLibrarySpecification.isLibraryCompilation();
   }
 
+  public boolean isEnabled() {
+    return !machineDesugaredLibrarySpecification.isEmpty();
+  }
+
   public boolean isL8() {
     return !synthesizedClassPrefix.isEmpty();
   }
 
+  public boolean isR8LirToLirLibraryDesugaringEnabled() {
+    return options.partialSubCompilationConfiguration != null;
+  }
+
   public void resetDesugaredLibrarySpecificationForTesting() {
     loadMachineDesugaredLibrarySpecification = null;
     machineDesugaredLibrarySpecification = DesugaredLibrarySpecification.empty();
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
new file mode 100644
index 0000000..915fe93
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/R8LibraryDesugaring.java
@@ -0,0 +1,173 @@
+// 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;
+
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.contexts.CompilationContext.ProcessorContext;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.conversion.IRFinalizer;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.ir.conversion.LirConverter;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions;
+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.itf.InterfaceMethodProcessorFacade;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover;
+import com.android.tools.r8.profile.rewriting.ProfileCollectionAdditions;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import com.android.tools.r8.utils.collections.ProgramMethodSet.ConcurrentProgramMethodSet;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+public class R8LibraryDesugaring {
+
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
+  private final InternalOptions options;
+
+  public R8LibraryDesugaring(AppView<? extends AppInfoWithClassHierarchy> appView) {
+    this.appView = appView;
+    this.options = appView.options();
+  }
+
+  public static void runIfNecessary(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      ExecutorService executorService,
+      Timing timing)
+      throws ExecutionException {
+    InternalOptions options = appView.options();
+    if (options.isDesugaring()
+        && options.getLibraryDesugaringOptions().isEnabled()
+        && options.getLibraryDesugaringOptions().isR8LirToLirLibraryDesugaringEnabled()
+        && options.isGeneratingDex()) {
+      new R8LibraryDesugaring(appView).run(executorService, timing);
+    }
+  }
+
+  private void run(ExecutorService executorService, Timing timing) throws ExecutionException {
+    // Bring the LIR up-to-date.
+    LirConverter.rewriteLirWithLens(appView, timing, executorService);
+
+    // In R8 partial, commit the D8 classes to the app so that they will also be subject to library
+    // desugaring.
+    partialSubCompilationSetup();
+
+    // Apply library desugaring.
+    ProfileCollectionAdditions profileCollectionAdditions =
+        ProfileCollectionAdditions.create(appView);
+    ConcurrentProgramMethodSet synthesizedMethods = ProgramMethodSet.createConcurrent();
+    runInstructionDesugaring(profileCollectionAdditions, synthesizedMethods, executorService);
+    runPostProcessingDesugaring(
+        profileCollectionAdditions, synthesizedMethods, executorService, timing);
+
+    // Commit profile updates and convert synthesized methods to DEX.
+    profileCollectionAdditions.commit(appView);
+    processSynthesizedMethods(synthesizedMethods, executorService);
+
+    // In R8 partial, uncommit the D8 classes from the app so that they will not be subject to whole
+    // program optimizations.
+    partialSubCompilationTearDown();
+
+    assert !appView.getSyntheticItems().hasPendingSyntheticClasses();
+  }
+
+  @SuppressWarnings("UnusedVariable")
+  private void runInstructionDesugaring(
+      ProfileCollectionAdditions profileCollectionAdditions,
+      ConcurrentProgramMethodSet synthesizedMethods,
+      ExecutorService executorService)
+      throws ExecutionException {
+    ProcessorContext processorContext = appView.createProcessorContext();
+    CfInstructionDesugaringEventConsumer eventConsumer =
+        CfInstructionDesugaringEventConsumer.createForR8LirToLirLibraryDesugaring(
+            appView, profileCollectionAdditions);
+    LensCodeRewriterUtils emptyRewriterUtils = LensCodeRewriterUtils.empty();
+    ThreadUtils.processItems(
+        appView.appInfo().classes(),
+        clazz ->
+            clazz.forEachProgramMethodMatching(
+                method -> method.hasCode() && method.getCode().isLirCode(),
+                method -> {
+                  MethodProcessingContext methodProcessingContext =
+                      processorContext.createMethodProcessingContext(method);
+                  R8LibraryDesugaringGraphLens libraryDesugaringGraphLens =
+                      new R8LibraryDesugaringGraphLens(
+                          appView, eventConsumer, method, methodProcessingContext);
+                  LirConverter.rewriteLirMethodWithLens(
+                      method, appView, libraryDesugaringGraphLens, emptyRewriterUtils);
+                }),
+        options.getThreadingModule(),
+        executorService);
+    appView.dexItemFactory().clearTypeElementsCache();
+
+    // Move the pending methods and mark them live and ready for tracing.
+    List<ProgramMethod> needsProcessing = eventConsumer.finalizeDesugaring();
+    assert needsProcessing.isEmpty();
+
+    // TODO(b/391572031): Generate desugared library API tracking warnings.
+
+    // Commit pending synthetics.
+    appView.rebuildAppInfo();
+  }
+
+  @SuppressWarnings("UnusedVariable")
+  private void runPostProcessingDesugaring(
+      ProfileCollectionAdditions profileCollectionAdditions,
+      ConcurrentProgramMethodSet synthesizedMethods,
+      ExecutorService executorService,
+      Timing timing)
+      throws ExecutionException {
+    CfPostProcessingDesugaringEventConsumer eventConsumer =
+        CfPostProcessingDesugaringEventConsumer.createForR8LirToLirLibraryDesugaring(
+            appView, profileCollectionAdditions);
+    InterfaceMethodProcessorFacade interfaceDesugaring =
+        InterfaceMethodProcessorFacade.createForR8LirToLirLibraryDesugaring();
+    CfPostProcessingDesugaringCollection.createForR8LirToLirLibraryDesugaring(
+            appView, interfaceDesugaring)
+        .postProcessingDesugaring(
+            appView.appInfo().classes(), eventConsumer, executorService, timing);
+
+    // Commit pending synthetics.
+    appView.rebuildAppInfo();
+  }
+
+  private void processSynthesizedMethods(
+      ProgramMethodSet synthesizedMethods, ExecutorService executorService)
+      throws ExecutionException {
+    ThreadUtils.processItems(
+        synthesizedMethods,
+        method -> {
+          IRCode code = method.buildIR(appView, MethodConversionOptions.forLirPhase(appView));
+          DeadCodeRemover deadCodeRemover = new DeadCodeRemover(appView);
+          IRFinalizer<?> finalizer =
+              code.getConversionOptions().getFinalizer(deadCodeRemover, appView);
+          Code lirCode =
+              finalizer.finalizeCode(code, BytecodeMetadataProvider.empty(), Timing.empty());
+          method.setCode(lirCode, appView);
+        },
+        options.getThreadingModule(),
+        executorService);
+  }
+
+  private void partialSubCompilationSetup() {
+    if (options.partialSubCompilationConfiguration != null) {
+      options.partialSubCompilationConfiguration.asR8().commitDexingOutputClasses(appView);
+    }
+  }
+
+  private void partialSubCompilationTearDown() {
+    if (options.partialSubCompilationConfiguration != null) {
+      options.partialSubCompilationConfiguration.asR8().uncommitDexingOutputClasses(appView);
+    }
+  }
+}
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
new file mode 100644
index 0000000..25a7ae1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/R8LibraryDesugaringGraphLens.java
@@ -0,0 +1,120 @@
+// 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;
+
+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.DexMethod;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.lens.DefaultNonIdentityGraphLens;
+import com.android.tools.r8.graph.lens.FieldLookupResult;
+import com.android.tools.r8.graph.lens.GraphLens;
+import com.android.tools.r8.graph.lens.MethodLookupResult;
+import com.android.tools.r8.graph.lens.NonIdentityGraphLens;
+import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockInstructionListIterator;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InvokeMethod;
+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.optimize.CustomLensCodeRewriter;
+import java.util.Collections;
+import java.util.Set;
+
+public class R8LibraryDesugaringGraphLens extends DefaultNonIdentityGraphLens {
+
+  @SuppressWarnings("UnusedVariable")
+  private final CfInstructionDesugaringEventConsumer eventConsumer;
+
+  @SuppressWarnings("UnusedVariable")
+  private final ProgramMethod method;
+
+  @SuppressWarnings("UnusedVariable")
+  private final MethodProcessingContext methodProcessingContext;
+
+  public R8LibraryDesugaringGraphLens(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      ProgramMethod method,
+      MethodProcessingContext methodProcessingContext) {
+    super(appView);
+    this.eventConsumer = eventConsumer;
+    this.method = method;
+    this.methodProcessingContext = methodProcessingContext;
+  }
+
+  @Override
+  public boolean hasCustomLensCodeRewriter() {
+    return true;
+  }
+
+  @Override
+  public CustomLensCodeRewriter getCustomLensCodeRewriter() {
+    return new R8LibraryDesugaringLensCodeRewriter();
+  }
+
+  @Override
+  protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previous) {
+    // TODO(b/391572031): Implement field access desugaring.
+    return previous;
+  }
+
+  @Override
+  protected MethodLookupResult internalDescribeLookupMethod(
+      MethodLookupResult previous, DexMethod context, GraphLens codeLens) {
+    // TODO(b/391572031): Implement invoke desugaring.
+    assert previous.getPrototypeChanges().isEmpty();
+    return previous;
+  }
+
+  private class R8LibraryDesugaringLensCodeRewriter implements CustomLensCodeRewriter {
+
+    @Override
+    public Set<Phi> rewriteCode(
+        IRCode code,
+        MethodProcessor methodProcessor,
+        RewrittenPrototypeDescription prototypeChanges,
+        NonIdentityGraphLens lens) {
+      boolean changed = false;
+      BasicBlockIterator blocks = code.listIterator();
+      GraphLens codeLens = code.context().getDefinition().getCode().getCodeLens(appView);
+      while (blocks.hasNext()) {
+        BasicBlock block = blocks.next();
+        BasicBlockInstructionListIterator instructions = block.listIterator();
+        while (instructions.hasNext()) {
+          InvokeMethod invoke = instructions.next().asInvokeMethod();
+          if (invoke == null) {
+            continue;
+          }
+          MethodLookupResult lookupResult =
+              lookupMethod(
+                  invoke.getInvokedMethod(),
+                  code.context().getReference(),
+                  invoke.getType(),
+                  codeLens,
+                  invoke.getInterfaceBit());
+          if (lookupResult.isNeedsDesugaredLibraryApiConversionSet()) {
+            rewriteInvoke(code, blocks, instructions, invoke);
+          }
+          changed = true;
+        }
+      }
+      assert changed;
+      return Collections.emptySet();
+    }
+
+    @SuppressWarnings("UnusedVariable")
+    private void rewriteInvoke(
+        IRCode code,
+        BasicBlockIterator blocks,
+        BasicBlockInstructionListIterator instructions,
+        InvokeMethod invoke) {
+      // TODO(b/): Implement IR-to-IR invoke desugaring.
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodProcessorFacade.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodProcessorFacade.java
index 7e29629..fe8e567 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodProcessorFacade.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodProcessorFacade.java
@@ -6,6 +6,7 @@
 
 import static com.google.common.base.Predicates.alwaysTrue;
 
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -44,6 +45,11 @@
     this.classProcessor = new ClassProcessor(appView, isLiveMethod, desugaringMode);
   }
 
+  @SuppressWarnings("DoNotCallSuggester")
+  public static InterfaceMethodProcessorFacade createForR8LirToLirLibraryDesugaring() {
+    throw new Unreachable();
+  }
+
   private boolean shouldProcess(DexProgramClass clazz) {
     return !appView.isAlreadyLibraryDesugared(clazz) && !clazz.originatesFromDexResource();
   }
diff --git a/src/main/java/com/android/tools/r8/lightir/LirLensCodeRewriter.java b/src/main/java/com/android/tools/r8/lightir/LirLensCodeRewriter.java
index eb4ec46..34718c2 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirLensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirLensCodeRewriter.java
@@ -32,6 +32,7 @@
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.R8LibraryDesugaringGraphLens;
 import com.android.tools.r8.ir.optimize.AffectedValues;
 import com.android.tools.r8.ir.optimize.DeadCodeRemover;
 import com.android.tools.r8.lightir.LirBuilder.NameComputationPayload;
@@ -180,6 +181,13 @@
         }
       }
     }
+    if (graphLens instanceof R8LibraryDesugaringGraphLens) {
+      if (result.isNeedsDesugaredLibraryApiConversionSet()) {
+        return true;
+      }
+    } else {
+      assert !result.isNeedsDesugaredLibraryApiConversionSet();
+    }
     assert result.getPrototypeChanges().isEmpty();
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/partial/R8PartialSubCompilationConfiguration.java b/src/main/java/com/android/tools/r8/partial/R8PartialSubCompilationConfiguration.java
index 36a4f6b..5e82a63 100644
--- a/src/main/java/com/android/tools/r8/partial/R8PartialSubCompilationConfiguration.java
+++ b/src/main/java/com/android/tools/r8/partial/R8PartialSubCompilationConfiguration.java
@@ -248,6 +248,23 @@
       assert amendMissingClasses(appView);
     }
 
+    public void uncommitDexingOutputClasses(AppView<? extends AppInfoWithClassHierarchy> appView) {
+      List<DexClasspathClass> newClasspathClasses =
+          ListUtils.sort(
+              DexClasspathClass.toClasspathClasses(dexingOutputClasses.values()).values(),
+              Comparator.comparing(DexClass::getType));
+      DirectMappedDexApplication newApp =
+          appView
+              .app()
+              .asDirect()
+              .builder()
+              .removeProgramClasses(clazz -> dexingOutputClasses.containsKey(clazz.getType()))
+              .addClasspathClasses(newClasspathClasses)
+              .build();
+      appView.rebuildAppInfo(newApp);
+      assert amendMissingClasses(appView);
+    }
+
     public boolean hasD8DefinitionFor(DexReference reference) {
       if (reference.isDexType()) {
         return dexingOutputClasses.containsKey(reference.asDexType());