Integrate InterfaceMethodRewriter in lir-to-lir library desugaring

This splits the InterfaceMethodRewriter into a CfToCfInterfaceMethodRewriter and a LirToLirInterfaceMethodRewriter.

The LirToLirInterfaceMethodRewriter is integrated in R8LibraryDesugaringGraphLens.

Bug: b/391572031
Change-Id: Ieea80902510512ef55ccc79aecfbb11f8ec7a549
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 70abff3..f7f9349 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
@@ -29,6 +29,7 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryRetargeter;
 import com.android.tools.r8.ir.desugar.icce.AlwaysThrowingInstructionDesugaring;
 import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialToSelfDesugaring;
+import com.android.tools.r8.ir.desugar.itf.CfToCfInterfaceMethodRewriter;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodProcessorFacade;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
 import com.android.tools.r8.ir.desugar.itf.InterfaceProcessor;
@@ -68,7 +69,7 @@
 
   private final NestBasedAccessDesugaring nestBasedAccessDesugaring;
   private final CfToCfDesugaredLibraryRetargeter desugaredLibraryRetargeter;
-  private final InterfaceMethodRewriter interfaceMethodRewriter;
+  private final CfToCfInterfaceMethodRewriter interfaceMethodRewriter;
   private final CfToCfDesugaredLibraryApiConverter desugaredLibraryAPIConverter;
   private final CfToCfDesugaredLibraryDisableDesugarer disableDesugarer;
 
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 2b3e3cc..7b5dabd 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
@@ -27,6 +27,7 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.LirToLirDesugaredLibraryRetargeter;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodProcessorFacade;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
+import com.android.tools.r8.ir.desugar.itf.LirToLirInterfaceMethodRewriter;
 import com.android.tools.r8.ir.optimize.DeadCodeRemover;
 import com.android.tools.r8.profile.rewriting.ProfileCollectionAdditions;
 import com.android.tools.r8.utils.InternalOptions;
@@ -104,8 +105,8 @@
         DesugaredLibraryLibRewriter.createLirToLir(appView, eventConsumer);
     LirToLirDesugaredLibraryRetargeter desugaredLibraryRetargeter =
         LirToLirDesugaredLibraryRetargeter.createLirToLir(appView, eventConsumer);
-    // TODO(b/391572031): Implement lir-to-lir interface method rewriting.
-    InterfaceMethodRewriter interfaceMethodRewriter = null;
+    LirToLirInterfaceMethodRewriter interfaceMethodRewriter =
+        InterfaceMethodRewriter.createLirToLir(appView, eventConsumer);
     LirToLirDesugaredLibraryApiConverter desugaredLibraryApiConverter =
         DesugaredLibraryAPIConverter.createForLirToLir(
             appView, eventConsumer, interfaceMethodRewriter);
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 71c5a80..6fb3ba5 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
@@ -36,7 +36,7 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.disabledesugarer.LirToLirDesugaredLibraryDisableDesugarer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.LirToLirDesugaredLibraryLibRewriter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.LirToLirDesugaredLibraryRetargeter;
-import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
+import com.android.tools.r8.ir.desugar.itf.LirToLirInterfaceMethodRewriter;
 import com.android.tools.r8.ir.optimize.CustomLensCodeRewriter;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -50,9 +50,7 @@
   private final LirToLirDesugaredLibraryDisableDesugarer desugaredLibraryDisableDesugarer;
   private final LirToLirDesugaredLibraryLibRewriter desugaredLibraryLibRewriter;
   private final LirToLirDesugaredLibraryRetargeter desugaredLibraryRetargeter;
-
-  @SuppressWarnings("UnusedVariable")
-  private final InterfaceMethodRewriter interfaceMethodRewriter;
+  private final LirToLirInterfaceMethodRewriter interfaceMethodRewriter;
 
   private final CfInstructionDesugaringEventConsumer eventConsumer;
   private final ProgramMethod method;
@@ -64,7 +62,7 @@
       LirToLirDesugaredLibraryDisableDesugarer desugaredLibraryDisableDesugarer,
       LirToLirDesugaredLibraryLibRewriter desugaredLibraryLibRewriter,
       LirToLirDesugaredLibraryRetargeter desugaredLibraryRetargeter,
-      InterfaceMethodRewriter interfaceMethodRewriter,
+      LirToLirInterfaceMethodRewriter interfaceMethodRewriter,
       CfInstructionDesugaringEventConsumer eventConsumer,
       ProgramMethod method,
       MethodProcessingContext methodProcessingContext) {
@@ -112,7 +110,6 @@
   @Override
   protected MethodLookupResult internalDescribeLookupMethod(
       MethodLookupResult previous, DexMethod context, GraphLens codeLens) {
-    // TODO(b/391572031): Implement invoke desugaring.
     assert previous.getPrototypeChanges().isEmpty();
 
     if (desugaredLibraryLibRewriter != null) {
@@ -139,6 +136,14 @@
       }
     }
 
+    if (interfaceMethodRewriter != null) {
+      MethodLookupResult result =
+          interfaceMethodRewriter.lookupMethod(previous, method, methodProcessingContext, this);
+      if (result != previous) {
+        return result;
+      }
+    }
+
     if (desugaredLibraryApiConverter != null) {
       return desugaredLibraryApiConverter.lookupMethod(
           previous, method, methodProcessingContext, this);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/CfToCfInterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/CfToCfInterfaceMethodRewriter.java
new file mode 100644
index 0000000..265003d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/CfToCfInterfaceMethodRewriter.java
@@ -0,0 +1,146 @@
+// 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.itf;
+
+import static com.android.tools.r8.ir.desugar.itf.InterfaceMethodDesugaringMode.LIBRARY_DESUGARING_N_PLUS;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfInvokeDynamic;
+import com.android.tools.r8.cf.code.CfOpcodeUtils;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.DexValue;
+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.ProgramAdditions;
+import com.google.common.collect.Iterables;
+import java.util.Set;
+import java.util.function.IntConsumer;
+
+public class CfToCfInterfaceMethodRewriter extends InterfaceMethodRewriter
+    implements CfInstructionDesugaring {
+
+  // This is used to filter out double desugaring.
+  private final Set<CfInstructionDesugaring> precedingDesugaringsForInvoke;
+  private final Set<CfInstructionDesugaring> precedingDesugaringsForInvokeDynamic;
+
+  CfToCfInterfaceMethodRewriter(
+      AppView<?> appView,
+      InterfaceMethodDesugaringMode desugaringMode,
+      Set<CfInstructionDesugaring> precedingDesugaringsForInvoke,
+      Set<CfInstructionDesugaring> precedingDesugaringsForInvokeDynamic) {
+    super(appView, desugaringMode);
+    this.precedingDesugaringsForInvoke = precedingDesugaringsForInvoke;
+    this.precedingDesugaringsForInvokeDynamic = precedingDesugaringsForInvokeDynamic;
+  }
+
+  @Override
+  public void acceptRelevantAsmOpcodes(IntConsumer consumer) {
+    CfOpcodeUtils.acceptCfInvokeOpcodes(consumer);
+  }
+
+  @Override
+  public boolean hasPreciseNeedsDesugaring() {
+    return false;
+  }
+
+  /**
+   * If the method is not required to be desugared, scanning is used to upgrade when required the
+   * class file version, as well as reporting missing type.
+   */
+  @Override
+  public void prepare(
+      ProgramMethod method,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      ProgramAdditions programAdditions) {
+    if (desugaringMode == LIBRARY_DESUGARING_N_PLUS) {
+      return;
+    }
+    if (isSyntheticMethodThatShouldNotBeDoubleProcessed(method)) {
+      leavingStaticInvokeToInterface(method);
+      return;
+    }
+    CfCode code = method.getDefinition().getCode().asCfCode();
+    for (CfInstruction instruction : code.getInstructions()) {
+      if (instruction.isInvokeDynamic()
+          && !isAlreadyDesugared(instruction.asInvokeDynamic(), method)) {
+        reportInterfaceMethodHandleCallSite(instruction.asInvokeDynamic().getCallSite(), method);
+      }
+      compute(instruction, method).scan();
+    }
+  }
+
+  @Override
+  public DesugarDescription compute(CfInstruction instruction, ProgramMethod context) {
+    CfInvoke invoke = instruction.asInvoke();
+    // Interface desugaring is only interested in invokes that are not desugared by preceeding
+    // desugarings.
+    if (invoke != null && !isAlreadyDesugared(invoke, context)) {
+      return compute(
+          invoke.getMethod(),
+          invoke.getInvokeTypePreDesugar(
+              appView, context.getDefinition().getCode().getCodeLens(appView), context),
+          invoke.isInterface(),
+          context);
+    }
+    return DesugarDescription.nothing();
+  }
+
+  private DesugarDescription compute(
+      DexMethod invokedMethod, InvokeType invokeType, boolean isInterface, ProgramMethod context) {
+    RetargetMethodSupplier retargetMethodSupplier =
+        getRetargetMethodSupplier(invokedMethod, invokeType, isInterface, context);
+    return retargetMethodSupplier != RetargetMethodSupplier.none()
+        ? retargetMethodSupplier.toDesugarDescription(context)
+        : DesugarDescription.nothing();
+  }
+
+  private boolean isAlreadyDesugared(CfInvoke invoke, ProgramMethod context) {
+    return Iterables.any(
+        precedingDesugaringsForInvoke,
+        desugaring -> desugaring.compute(invoke, context).needsDesugaring());
+  }
+
+  private boolean isAlreadyDesugared(CfInvokeDynamic invoke, ProgramMethod context) {
+    return Iterables.any(
+        precedingDesugaringsForInvokeDynamic,
+        desugaring -> desugaring.compute(invoke, context).needsDesugaring());
+  }
+
+  private void reportInterfaceMethodHandleCallSite(DexCallSite callSite, ProgramMethod context) {
+    // Check that static interface methods are not referenced from invoke-custom instructions via
+    // method handles.
+    reportStaticInterfaceMethodHandle(context, callSite.bootstrapMethod);
+    for (DexValue arg : callSite.bootstrapArgs) {
+      if (arg.isDexValueMethodHandle()) {
+        reportStaticInterfaceMethodHandle(context, arg.asDexValueMethodHandle().value);
+      }
+    }
+  }
+
+  private void reportStaticInterfaceMethodHandle(ProgramMethod context, DexMethodHandle handle) {
+    if (handle.type.isInvokeStatic()) {
+      DexClass holderClass = appView.definitionFor(handle.asMethod().holder);
+      // NOTE: If the class definition is missing we can't check. Let it be handled as any other
+      // missing call target.
+      if (holderClass == null) {
+        warnMissingType(context, handle.asMethod().holder);
+      } else if (holderClass.isInterface()) {
+        throw new Unimplemented(
+            "Desugaring of static interface method handle in `"
+                + context.toSourceString()
+                + "` is not yet supported.");
+      }
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
index 56b60db..942a895 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
@@ -7,27 +7,19 @@
 import static com.android.tools.r8.ir.desugar.itf.InterfaceMethodDesugaringMode.LIBRARY_DESUGARING_N_PLUS;
 
 import com.android.tools.r8.cf.CfVersion;
-import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
-import com.android.tools.r8.cf.code.CfInvokeDynamic;
-import com.android.tools.r8.cf.code.CfOpcodeUtils;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
@@ -37,7 +29,6 @@
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.DesugarDescription;
 import com.android.tools.r8.ir.desugar.DesugarDescription.ScanCallback;
-import com.android.tools.r8.ir.desugar.ProgramAdditions;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.DerivedMethod;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.EmulatedDispatchMethodDescriptor;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
@@ -49,13 +40,11 @@
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.android.tools.r8.utils.structural.Ordered;
-import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import java.util.Collections;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.function.IntConsumer;
 import java.util.function.Predicate;
 
 //
@@ -84,14 +73,14 @@
 //       set of default interface methods missing and add them, the created methods
 //       forward the call to an appropriate method in interface companion class.
 //
-public final class InterfaceMethodRewriter implements CfInstructionDesugaring {
+public abstract class InterfaceMethodRewriter {
 
-  private final AppView<?> appView;
+  final AppView<?> appView;
   private final InternalOptions options;
   final DexItemFactory factory;
   // If this is true, this will desugar only default methods from emulated interfaces present in
   // the emulated interface descriptor.
-  private final InterfaceMethodDesugaringMode desugaringMode;
+  final InterfaceMethodDesugaringMode desugaringMode;
   private final InterfaceDesugaringSyntheticHelper helper;
   // The emulatedMethod set is there to avoid doing the emulated look-up too often.
   private final Set<DexString> emulatedMethods = Sets.newIdentityHashSet();
@@ -102,22 +91,12 @@
   // Caches default interface method info for already processed interfaces.
   private final Map<DexType, DefaultMethodsHelper.Collection> cache = new ConcurrentHashMap<>();
 
-  // This is used to filter out double desugaring.
-  private final Set<CfInstructionDesugaring> precedingDesugaringsForInvoke;
-  private final Set<CfInstructionDesugaring> precedingDesugaringsForInvokeDynamic;
-
-  private InterfaceMethodRewriter(
-      AppView<?> appView,
-      InterfaceMethodDesugaringMode desugaringMode,
-      Set<CfInstructionDesugaring> precedingDesugaringsForInvoke,
-      Set<CfInstructionDesugaring> precedingDesugaringsForInvokeDynamic) {
+  InterfaceMethodRewriter(AppView<?> appView, InterfaceMethodDesugaringMode desugaringMode) {
     assert desugaringMode.isSome();
     assert desugaringMode == LIBRARY_DESUGARING_N_PLUS
         || appView.options().isInterfaceMethodDesugaringEnabled();
     this.appView = appView;
     this.desugaringMode = desugaringMode;
-    this.precedingDesugaringsForInvoke = precedingDesugaringsForInvoke;
-    this.precedingDesugaringsForInvokeDynamic = precedingDesugaringsForInvokeDynamic;
     this.options = appView.options();
     this.factory = appView.dexItemFactory();
     this.helper = new InterfaceDesugaringSyntheticHelper(appView);
@@ -126,7 +105,7 @@
     }
   }
 
-  public static InterfaceMethodRewriter createCfToCf(
+  public static CfToCfInterfaceMethodRewriter createCfToCf(
       AppView<?> appView,
       Set<CfInstructionDesugaring> precedingDesugaringsForInvoke,
       Set<CfInstructionDesugaring> precedingDesugaringsForInvokeDynamic) {
@@ -139,13 +118,22 @@
         precedingDesugaringsForInvokeDynamic);
   }
 
-  private static InterfaceMethodRewriter create(
+  public static LirToLirInterfaceMethodRewriter createLirToLir(
+      AppView<?> appView, CfInstructionDesugaringEventConsumer eventConsumer) {
+    InterfaceMethodDesugaringMode desugaringMode =
+        InterfaceMethodDesugaringMode.createLirToLir(appView.options());
+    return desugaringMode.isSome()
+        ? new LirToLirInterfaceMethodRewriter(appView, desugaringMode, eventConsumer)
+        : null;
+  }
+
+  private static CfToCfInterfaceMethodRewriter create(
       AppView<?> appView,
       InterfaceMethodDesugaringMode desugaringMode,
       Set<CfInstructionDesugaring> precedingDesugaringsForInvoke,
       Set<CfInstructionDesugaring> precedingDesugaringsForInvokeDynamic) {
     return desugaringMode.isSome()
-        ? new InterfaceMethodRewriter(
+        ? new CfToCfInterfaceMethodRewriter(
             appView,
             desugaringMode,
             precedingDesugaringsForInvoke,
@@ -213,80 +201,7 @@
     }
   }
 
-  private boolean isAlreadyDesugared(CfInvoke invoke, ProgramMethod context) {
-    return Iterables.any(
-        precedingDesugaringsForInvoke,
-        desugaring -> desugaring.compute(invoke, context).needsDesugaring());
-  }
-
-  private boolean isAlreadyDesugared(CfInvokeDynamic invoke, ProgramMethod context) {
-    return Iterables.any(
-        precedingDesugaringsForInvokeDynamic,
-        desugaring -> desugaring.compute(invoke, context).needsDesugaring());
-  }
-
-  @Override
-  public void acceptRelevantAsmOpcodes(IntConsumer consumer) {
-    CfOpcodeUtils.acceptCfInvokeOpcodes(consumer);
-  }
-
-  @Override
-  public boolean hasPreciseNeedsDesugaring() {
-    return false;
-  }
-
-  /**
-   * If the method is not required to be desugared, scanning is used to upgrade when required the
-   * class file version, as well as reporting missing type.
-   */
-  @Override
-  public void prepare(
-      ProgramMethod method,
-      CfInstructionDesugaringEventConsumer eventConsumer,
-      ProgramAdditions programAdditions) {
-    if (desugaringMode == LIBRARY_DESUGARING_N_PLUS) {
-      return;
-    }
-    if (isSyntheticMethodThatShouldNotBeDoubleProcessed(method)) {
-      leavingStaticInvokeToInterface(method);
-      return;
-    }
-    CfCode code = method.getDefinition().getCode().asCfCode();
-    for (CfInstruction instruction : code.getInstructions()) {
-      if (instruction.isInvokeDynamic()
-          && !isAlreadyDesugared(instruction.asInvokeDynamic(), method)) {
-        reportInterfaceMethodHandleCallSite(instruction.asInvokeDynamic().getCallSite(), method);
-      }
-      compute(instruction, method).scan();
-    }
-  }
-
-  @Override
-  public DesugarDescription compute(CfInstruction instruction, ProgramMethod context) {
-    CfInvoke invoke = instruction.asInvoke();
-    // Interface desugaring is only interested in invokes that are not desugared by preceeding
-    // desugarings.
-    if (invoke != null && !isAlreadyDesugared(invoke, context)) {
-      return compute(
-          invoke.getMethod(),
-          invoke.getInvokeTypePreDesugar(
-              appView, context.getDefinition().getCode().getCodeLens(appView), context),
-          invoke.isInterface(),
-          context);
-    }
-    return DesugarDescription.nothing();
-  }
-
-  private DesugarDescription compute(
-      DexMethod invokedMethod, InvokeType invokeType, boolean isInterface, ProgramMethod context) {
-    RetargetMethodSupplier retargetMethodSupplier =
-        getRetargetMethodSupplier(invokedMethod, invokeType, isInterface, context);
-    return retargetMethodSupplier != RetargetMethodSupplier.none()
-        ? retargetMethodSupplier.toDesugarDescription(context)
-        : DesugarDescription.nothing();
-  }
-
-  private RetargetMethodSupplier getRetargetMethodSupplier(
+  RetargetMethodSupplier getRetargetMethodSupplier(
       DexMethod invokedMethod, InvokeType invokeType, boolean isInterface, ProgramMethod context) {
     if (isSyntheticMethodThatShouldNotBeDoubleProcessed(context)) {
       return RetargetMethodSupplier.none();
@@ -640,7 +555,7 @@
     };
   }
 
-  private void leavingStaticInvokeToInterface(ProgramMethod method) {
+  void leavingStaticInvokeToInterface(ProgramMethod method) {
     // When leaving static interface method invokes possibly upgrade the class file
     // version, but don't go above the initial class file version. If the input was
     // 1.7 or below, this will make a VerificationError on the input a VerificationError
@@ -669,21 +584,10 @@
     }
   }
 
-  private boolean isSyntheticMethodThatShouldNotBeDoubleProcessed(ProgramMethod method) {
+  boolean isSyntheticMethodThatShouldNotBeDoubleProcessed(ProgramMethod method) {
     return appView.getSyntheticItems().isSyntheticMethodThatShouldNotBeDoubleProcessed(method);
   }
 
-  private void reportInterfaceMethodHandleCallSite(DexCallSite callSite, ProgramMethod context) {
-    // Check that static interface methods are not referenced from invoke-custom instructions via
-    // method handles.
-    reportStaticInterfaceMethodHandle(context, callSite.bootstrapMethod);
-    for (DexValue arg : callSite.bootstrapArgs) {
-      if (arg.isDexValueMethodHandle()) {
-        reportStaticInterfaceMethodHandle(context, arg.asDexValueMethodHandle().value);
-      }
-    }
-  }
-
   @SuppressWarnings("ReferenceEquality")
   private RetargetMethodSupplier rewriteInvokeSuper(
       DexClass holder,
@@ -856,22 +760,6 @@
     return clazz.isLibraryClass() && !helper.isInDesugaredLibrary(clazz);
   }
 
-  private void reportStaticInterfaceMethodHandle(ProgramMethod context, DexMethodHandle handle) {
-    if (handle.type.isInvokeStatic()) {
-      DexClass holderClass = appView.definitionFor(handle.asMethod().holder);
-      // NOTE: If the class definition is missing we can't check. Let it be handled as any other
-      // missing call target.
-      if (holderClass == null) {
-        warnMissingType(context, handle.asMethod().holder);
-      } else if (holderClass.isInterface()) {
-        throw new Unimplemented(
-            "Desugaring of static interface method handle in `"
-                + context.toSourceString()
-                + "` is not yet supported.");
-      }
-    }
-  }
-
   // It is possible that referenced method actually points to an interface which does
   // not define this default methods, but inherits it. We are making our best effort
   // to find an appropriate method, but still use the original one in case we fail.
@@ -965,7 +853,7 @@
     return helper.wrapInCollection();
   }
 
-  private void warnMissingType(ProgramMethod context, DexType missing) {
+  void warnMissingType(ProgramMethod context, DexType missing) {
     // Companion/Emulated interface/Conversion classes for desugared library won't be missing,
     // they are in the desugared library.
     if (helper.shouldIgnoreFromReports(missing)) {
@@ -977,7 +865,7 @@
     options.warningMissingTypeForDesugar(origin, position, missing, method);
   }
 
-  private interface RetargetMethodSupplier {
+  interface RetargetMethodSupplier {
 
     static RetargetMethodSupplier none() {
       return null;
@@ -986,7 +874,7 @@
     DesugarDescription toDesugarDescription(ProgramMethod context);
   }
 
-  private abstract static class StaticRetargetMethodSupplier implements RetargetMethodSupplier {
+  abstract static class StaticRetargetMethodSupplier implements RetargetMethodSupplier {
 
     public abstract DexMethod getRetargetMethod(
         CfInstructionDesugaringEventConsumer eventConsumer,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/LirToLirInterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/LirToLirInterfaceMethodRewriter.java
new file mode 100644
index 0000000..102fe66
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/LirToLirInterfaceMethodRewriter.java
@@ -0,0 +1,51 @@
+// 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.itf;
+
+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.CfInstructionDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.R8LibraryDesugaringGraphLens;
+
+public class LirToLirInterfaceMethodRewriter extends InterfaceMethodRewriter {
+
+  private final CfInstructionDesugaringEventConsumer eventConsumer;
+
+  LirToLirInterfaceMethodRewriter(
+      AppView<?> appView,
+      InterfaceMethodDesugaringMode desugaringMode,
+      CfInstructionDesugaringEventConsumer eventConsumer) {
+    super(appView, desugaringMode);
+    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();
+    RetargetMethodSupplier retargetMethodSupplier =
+        getRetargetMethodSupplier(method, invokeType, isInterface, context);
+    // TODO(b/391572031): Add support for non-StaticRetargetMethodSupplier.
+    if (retargetMethodSupplier instanceof StaticRetargetMethodSupplier) {
+      StaticRetargetMethodSupplier staticRetargetMethodSupplier =
+          (StaticRetargetMethodSupplier) retargetMethodSupplier;
+      DexMethod retargetMethod =
+          staticRetargetMethodSupplier.getRetargetMethod(eventConsumer, methodProcessingContext);
+      return MethodLookupResult.builder(lens, lens.getPrevious())
+          .setReference(retargetMethod)
+          .setType(InvokeType.STATIC)
+          .setIsInterface(false)
+          .build();
+    }
+    return previous;
+  }
+}