Refactor api outliner logic to be independent of CfInstruction

This makes it straight-forward to implement api outlining lir-to-lir.

Bug: b/411032206
Change-Id: Ide11aff801bec5910376384719139e62bd18cca2
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 58a2b55..8885eaa 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
@@ -70,7 +70,7 @@
   private final NestBasedAccessDesugaring nestBasedAccessDesugaring;
   private final CfToCfDesugaredLibraryRetargeter desugaredLibraryRetargeter;
   private final CfToCfInterfaceMethodRewriter interfaceMethodRewriter;
-  private final CfToCfDesugaredLibraryApiConverter desugaredLibraryAPIConverter;
+  private final CfToCfDesugaredLibraryApiConverter desugaredLibraryApiConverter;
   private final CfToCfDesugaredLibraryDisableDesugarer disableDesugarer;
 
   private final CfInstructionDesugaring[][] asmOpcodeOrCompareToIdToDesugaringsMap;
@@ -86,17 +86,14 @@
         appView.enableWholeProgramOptimizations()
             ? new AlwaysThrowingInstructionDesugaring(appView.withClassHierarchy())
             : null;
-    if (alwaysThrowingInstructionDesugaring != null) {
-      desugarings.add(alwaysThrowingInstructionDesugaring);
-    }
-    if (appView.options().apiModelingOptions().isOutliningOfMethodsEnabled()) {
-      yieldingDesugarings.add(new ApiInvokeOutlinerDesugaring(appView, apiLevelCompute));
-    }
+    addIfNotNull(desugarings, alwaysThrowingInstructionDesugaring);
+    addIfNotNull(
+        yieldingDesugarings, ApiInvokeOutlinerDesugaring.createCfToCf(appView, apiLevelCompute));
     if (appView.options().desugarState.isOff()) {
       this.nestBasedAccessDesugaring = null;
       this.desugaredLibraryRetargeter = null;
       this.interfaceMethodRewriter = null;
-      this.desugaredLibraryAPIConverter = null;
+      this.desugaredLibraryApiConverter = null;
       this.disableDesugarer = null;
       desugarings.add(new InvokeSpecialToSelfDesugaring(appView));
       if (appView.options().isGeneratingDex()) {
@@ -116,24 +113,16 @@
     // NavType#fromArgType is not kept.
     CfToCfDesugaredLibraryLibRewriter desugaredLibRewriter =
         DesugaredLibraryLibRewriter.createCfToCf(appView);
-    if (desugaredLibRewriter != null) {
-      desugarings.add(desugaredLibRewriter);
-    }
+    addIfNotNull(desugarings, desugaredLibRewriter);
     desugaredLibraryRetargeter = DesugaredLibraryRetargeter.createCfToCf(appView);
-    if (desugaredLibraryRetargeter != null) {
-      desugarings.add(desugaredLibraryRetargeter);
-    }
+    addIfNotNull(desugarings, desugaredLibraryRetargeter);
     AutoCloseableRetargeter autoCloseableRetargeter =
         appView.options().shouldDesugarAutoCloseable()
             ? new AutoCloseableRetargeter(appView)
             : null;
-    if (autoCloseableRetargeter != null) {
-      desugarings.add(autoCloseableRetargeter);
-    }
+    addIfNotNull(desugarings, autoCloseableRetargeter);
     disableDesugarer = DesugaredLibraryDisableDesugarer.createCfToCf(appView);
-    if (disableDesugarer != null) {
-      desugarings.add(disableDesugarer);
-    }
+    addIfNotNull(desugarings, disableDesugarer);
     if (appView.options().enableTryWithResourcesDesugaring()) {
       desugarings.add(new TwrInstructionDesugaring(appView));
     }
@@ -142,9 +131,7 @@
       desugarings.add(typeSwitchDesugaring = new TypeSwitchDesugaring(appView));
     }
     RecordInstructionDesugaring recordRewriter = RecordInstructionDesugaring.create(appView);
-    if (recordRewriter != null) {
-      desugarings.add(recordRewriter);
-    }
+    addIfNotNull(desugarings, recordRewriter);
     StringConcatInstructionDesugaring stringConcatDesugaring =
         new StringConcatInstructionDesugaring(appView);
     desugarings.add(stringConcatDesugaring);
@@ -165,15 +152,13 @@
     } else if (appView.options().canHaveArtArrayCloneFromInterfaceMethodBug()) {
       desugarings.add(new OutlineArrayCloneFromInterfaceMethodDesugaring(appView));
     }
-    desugaredLibraryAPIConverter =
+    desugaredLibraryApiConverter =
         DesugaredLibraryAPIConverter.createForCfToCf(
             appView,
             SetUtils.newImmutableSetExcludingNullItems(
                 interfaceMethodRewriter, desugaredLibraryRetargeter, backportedMethodRewriter),
             interfaceMethodRewriter);
-    if (desugaredLibraryAPIConverter != null) {
-      desugarings.add(desugaredLibraryAPIConverter);
-    }
+    addIfNotNull(desugarings, desugaredLibraryApiConverter);
     desugarings.add(new ConstantDynamicInstructionDesugaring(appView));
     desugarings.add(new InvokeSpecialToSelfDesugaring(appView));
     if (appView.options().isGeneratingClassFiles()) {
@@ -187,17 +172,20 @@
     if (backportedMethodRewriter.hasBackports()) {
       desugarings.add(backportedMethodRewriter);
     }
-    if (nestBasedAccessDesugaring != null) {
-      desugarings.add(nestBasedAccessDesugaring);
-    }
+    addIfNotNull(desugarings, nestBasedAccessDesugaring);
     VarHandleDesugaring varHandleDesugaring = VarHandleDesugaring.create(appView);
-    if (varHandleDesugaring != null) {
-      desugarings.add(varHandleDesugaring);
-    }
+    addIfNotNull(desugarings, varHandleDesugaring);
     yieldingDesugarings.add(new UnrepresentableInDexInstructionRemover(appView));
     asmOpcodeOrCompareToIdToDesugaringsMap = createAsmOpcodeOrCompareToIdToDesugaringsMap();
   }
 
+  private static void addIfNotNull(
+      Collection<CfInstructionDesugaring> collection, CfInstructionDesugaring desugaring) {
+    if (desugaring != null) {
+      collection.add(desugaring);
+    }
+  }
+
   private CfInstructionDesugaring[][] createAsmOpcodeOrCompareToIdToDesugaringsMap() {
     Int2ReferenceMap<List<CfInstructionDesugaring>> map = new Int2ReferenceOpenHashMap<>();
     for (CfInstructionDesugaring desugaring : Iterables.concat(desugarings, yieldingDesugarings)) {
@@ -546,8 +534,8 @@
 
   @Override
   public void withDesugaredLibraryAPIConverter(Consumer<DesugaredLibraryAPIConverter> consumer) {
-    if (desugaredLibraryAPIConverter != null) {
-      consumer.accept(desugaredLibraryAPIConverter);
+    if (desugaredLibraryApiConverter != null) {
+      consumer.accept(desugaredLibraryApiConverter);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
index e7fc2d4..c4dd8d0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
@@ -6,20 +6,11 @@
 
 import static com.android.tools.r8.utils.AndroidApiLevelUtils.isApiLevelLessThanOrEqualToG;
 import static com.android.tools.r8.utils.AndroidApiLevelUtils.isOutlinedAtSameOrLowerLevel;
-import static org.objectweb.asm.Opcodes.INVOKESTATIC;
 
 import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
 import com.android.tools.r8.androidapi.ComputedApiLevel;
-import com.android.tools.r8.cf.code.CfCheckCast;
-import com.android.tools.r8.cf.code.CfConstClass;
-import com.android.tools.r8.cf.code.CfFieldInstruction;
-import com.android.tools.r8.cf.code.CfInstanceOf;
-import com.android.tools.r8.cf.code.CfInstruction;
-import com.android.tools.r8.cf.code.CfInvoke;
-import com.android.tools.r8.cf.code.CfOpcodeUtils;
-import com.android.tools.r8.contexts.CompilationContext.UniqueContext;
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMember;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -33,8 +24,7 @@
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
-import com.android.tools.r8.ir.desugar.DesugarDescription;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.synthetic.CheckCastSourceCode;
 import com.android.tools.r8.ir.synthetic.ConstClassSourceCode;
 import com.android.tools.r8.ir.synthetic.FieldAccessorBuilder;
@@ -44,79 +34,55 @@
 import com.android.tools.r8.utils.AndroidApiLevelUtils;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.TraversalContinuation;
-import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import java.util.function.Function;
-import java.util.function.IntConsumer;
-import org.objectweb.asm.Opcodes;
 
 /**
  * This desugaring will outline calls to library methods that are introduced after the min-api
  * level. For classes introduced after the min-api level see ApiReferenceStubber.
  */
-public class ApiInvokeOutlinerDesugaring implements CfInstructionDesugaring {
+public class ApiInvokeOutlinerDesugaring {
 
   private final AppView<?> appView;
   private final AndroidApiLevelCompute apiLevelCompute;
+  private final DexItemFactory factory;
 
   private final DexTypeList objectParams;
 
-  public ApiInvokeOutlinerDesugaring(AppView<?> appView, AndroidApiLevelCompute apiLevelCompute) {
+  ApiInvokeOutlinerDesugaring(AppView<?> appView, AndroidApiLevelCompute apiLevelCompute) {
     this.appView = appView;
     this.apiLevelCompute = apiLevelCompute;
-    this.objectParams = DexTypeList.create(new DexType[] {appView.dexItemFactory().objectType});
+    this.factory = appView.dexItemFactory();
+    this.objectParams = DexTypeList.create(new DexType[] {factory.objectType});
   }
 
-  @Override
-  public void acceptRelevantAsmOpcodes(IntConsumer consumer) {
-    CfOpcodeUtils.acceptCfFieldInstructionOpcodes(consumer);
-    CfOpcodeUtils.acceptCfInvokeOpcodes(
-        opcode -> {
-          if (opcode != Opcodes.INVOKESPECIAL) {
-            consumer.accept(opcode);
-          }
-        });
-    consumer.accept(Opcodes.CHECKCAST);
-    consumer.accept(Opcodes.INSTANCEOF);
-  }
-
-  @Override
-  public void acceptRelevantCompareToIds(IntConsumer consumer) {
-    consumer.accept(CfCompareHelper.CONST_CLASS_COMPARE_ID);
-  }
-
-  @Override
-  public DesugarDescription compute(CfInstruction instruction, ProgramMethod context) {
-    ComputedApiLevel computedApiLevel =
-        getComputedApiLevelInstructionOnHolderWithMinApi(instruction, context);
-    if (appView.computedMinApiLevel().isGreaterThanOrEqualTo(computedApiLevel)) {
-      return DesugarDescription.nothing();
+  public static CfToCfApiInvokeOutlinerDesugaring createCfToCf(
+      AppView<?> appView, AndroidApiLevelCompute apiLevelCompute) {
+    if (appView.options().apiModelingOptions().isOutliningOfMethodsEnabled()) {
+      return new CfToCfApiInvokeOutlinerDesugaring(appView, apiLevelCompute);
     }
-    return DesugarDescription.builder()
-        .setDesugarRewrite(
-            (position,
-                freshLocalProvider,
-                localStackAllocator,
-                desugaringInfo,
-                eventConsumer,
-                context1,
-                methodProcessingContext,
-                desugaringCollection,
-                dexItemFactory) ->
-                desugarLibraryCall(
-                    methodProcessingContext.createUniqueContext(),
-                    instruction,
-                    computedApiLevel,
-                    dexItemFactory,
-                    eventConsumer,
-                    context))
-        .build();
+    return null;
+  }
+
+  RetargetMethodSupplier getRetargetMethodSupplier(
+      InstructionKind instructionKind, DexReference reference, ProgramMethod context) {
+    ComputedApiLevel computedApiLevel =
+        getComputedApiLevelInstructionOnHolderWithMinApi(instructionKind, reference, context);
+    if (appView.computedMinApiLevel().isGreaterThanOrEqualTo(computedApiLevel)) {
+      return null;
+    }
+    return (eventConsumer, methodProcessingContext) -> {
+      ProgramMethod outlinedMethod =
+          ensureOutlineMethod(
+              instructionKind, reference, computedApiLevel, context, methodProcessingContext);
+      eventConsumer.acceptOutlinedMethod(outlinedMethod, context);
+      return outlinedMethod.getReference();
+    };
   }
 
   private ComputedApiLevel getComputedApiLevelInstructionOnHolderWithMinApi(
-      CfInstruction instruction, ProgramMethod context) {
+      InstructionKind instructionKind, DexReference reference, ProgramMethod context) {
     // Some backports will forward to the method/field they backport. For such synthetics run
     // outlining. Other synthetics should not need it. And explicitly not API outlines, as that
     // would cause infinite outlining.
@@ -126,7 +92,6 @@
             .isSyntheticOfKind(context.getHolderType(), k -> k.BACKPORT_WITH_FORWARDING)) {
       return appView.computedMinApiLevel();
     }
-    DexReference reference = getReferenceFromInstruction(instruction);
     if (reference == null || !reference.getContextType().isClassType()) {
       return appView.computedMinApiLevel();
     }
@@ -159,10 +124,7 @@
       return appView.computedMinApiLevel();
     }
     // Check for protected or package private access flags before outlining.
-    if (firstLibraryClass.isInterface()
-        || instruction.isCheckCast()
-        || instruction.isInstanceOf()
-        || instruction.isConstClass()) {
+    if (firstLibraryClass.isInterface() || instructionKind.isTypeInstruction()) {
       return referenceApiLevel;
     } else {
       DexEncodedMember<?, ?> definition =
@@ -177,22 +139,6 @@
     }
   }
 
-  private DexReference getReferenceFromInstruction(CfInstruction instruction) {
-    if (instruction.isFieldInstruction()) {
-      return instruction.asFieldInstruction().getField();
-    } else if (instruction.isCheckCast()) {
-      return instruction.asCheckCast().getType();
-    } else if (instruction.isInstanceOf()) {
-      return instruction.asInstanceOf().getType();
-    } else if (instruction.isConstClass()) {
-      return instruction.asConstClass().getType();
-    } else if (instruction.isInvoke() && !instruction.asInvoke().isInvokeSpecial()) {
-      return instruction.asInvoke().getMethod();
-    } else {
-      return null;
-    }
-  }
-
   private DexEncodedMember<?, ?> simpleLookupInClassHierarchy(
       DexLibraryClass holder, Function<DexClass, DexEncodedMember<?, ?>> lookup) {
     DexEncodedMember<?, ?> result = lookup.apply(holder);
@@ -214,32 +160,12 @@
     return traversalResult.isBreak() ? traversalResult.asBreak().getValue() : null;
   }
 
-  private Collection<CfInstruction> desugarLibraryCall(
-      UniqueContext uniqueContext,
-      CfInstruction instruction,
-      ComputedApiLevel computedApiLevel,
-      DexItemFactory factory,
-      ApiInvokeOutlinerDesugaringEventConsumer eventConsumer,
-      ProgramMethod context) {
-    assert instruction.isInvoke()
-            || instruction.isFieldInstruction()
-            || instruction.isCheckCast()
-            || instruction.isInstanceOf()
-            || instruction.isConstClass()
-        : instruction;
-    ProgramMethod outlinedMethod =
-        ensureOutlineMethod(uniqueContext, instruction, computedApiLevel, factory, context);
-    eventConsumer.acceptOutlinedMethod(outlinedMethod, context);
-    return ImmutableList.of(new CfInvoke(INVOKESTATIC, outlinedMethod.getReference(), false));
-  }
-
   private ProgramMethod ensureOutlineMethod(
-      UniqueContext context,
-      CfInstruction instruction,
+      InstructionKind instructionKind,
+      DexReference reference,
       ComputedApiLevel apiLevel,
-      DexItemFactory factory,
-      ProgramMethod programContext) {
-    DexReference reference = getReferenceFromInstruction(instruction);
+      ProgramMethod programContext,
+      MethodProcessingContext methodProcessingContext) {
     assert reference != null;
     DexClass holder = appView.definitionFor(reference.getContextType());
     assert holder != null;
@@ -253,7 +179,7 @@
                 holder.isPublic()
                     ? kinds.API_MODEL_OUTLINE
                     : kinds.API_MODEL_OUTLINE_WITHOUT_GLOBAL_MERGING,
-            context,
+            methodProcessingContext.createUniqueContext(),
             appView,
             syntheticMethodBuilder -> {
               syntheticMethodBuilder
@@ -266,31 +192,32 @@
                           .build())
                   .setApiLevelForDefinition(apiLevel)
                   .setApiLevelForCode(apiLevel);
-              if (instruction.isInvoke()) {
-                setCodeForInvoke(syntheticMethodBuilder, instruction.asInvoke(), factory);
-              } else if (instruction.isCheckCast()) {
-                setCodeForCheckCast(syntheticMethodBuilder, instruction.asCheckCast(), factory);
-              } else if (instruction.isInstanceOf()) {
-                setCodeForInstanceOf(syntheticMethodBuilder, instruction.asInstanceOf(), factory);
-              } else if (instruction.isConstClass()) {
-                setCodeForConstClass(syntheticMethodBuilder, instruction.asConstClass(), factory);
+              if (instructionKind.isInvoke()) {
+                setCodeForInvoke(syntheticMethodBuilder, instructionKind, reference.asDexMethod());
+              } else if (instructionKind == InstructionKind.CHECKCAST) {
+                setCodeForCheckCast(syntheticMethodBuilder, reference.asDexType());
+              } else if (instructionKind == InstructionKind.INSTANCEOF) {
+                setCodeForInstanceOf(syntheticMethodBuilder, reference.asDexType());
+              } else if (instructionKind == InstructionKind.CONSTCLASS) {
+                setCodeForConstClass(syntheticMethodBuilder, reference.asDexType());
               } else {
-                assert instruction.isCfInstruction();
+                assert instructionKind.isFieldInstruction();
                 setCodeForFieldInstruction(
                     syntheticMethodBuilder,
-                    instruction.asFieldInstruction(),
-                    factory,
+                    instructionKind,
+                    reference.asDexField(),
                     programContext);
               }
             });
   }
 
   private void setCodeForInvoke(
-      SyntheticMethodBuilder methodBuilder, CfInvoke invoke, DexItemFactory factory) {
-    DexMethod method = invoke.getMethod();
+      SyntheticMethodBuilder methodBuilder, InstructionKind instructionKind, DexMethod method) {
     DexClass libraryHolder = appView.definitionFor(method.getHolderType());
     assert libraryHolder != null;
-    boolean isVirtualMethod = invoke.isInvokeVirtual() || invoke.isInvokeInterface();
+    boolean isVirtualMethod =
+        instructionKind == InstructionKind.INVOKEINTERFACE
+            || instructionKind == InstructionKind.INVOKEVIRTUAL;
     assert verifyLibraryHolderAndInvoke(libraryHolder, method, isVirtualMethod);
     DexProto proto = factory.prependHolderToProtoIf(method, isVirtualMethod);
     methodBuilder
@@ -313,22 +240,24 @@
 
   private void setCodeForFieldInstruction(
       SyntheticMethodBuilder methodBuilder,
-      CfFieldInstruction fieldInstruction,
-      DexItemFactory factory,
+      InstructionKind instructionKind,
+      DexField field,
       ProgramMethod programContext) {
-    DexField field = fieldInstruction.getField();
     DexClass libraryHolder = appView.definitionFor(field.getHolderType());
     assert libraryHolder != null;
     boolean isInstance =
-        fieldInstruction.isInstanceFieldPut() || fieldInstruction.isInstanceFieldGet();
+        instructionKind == InstructionKind.IGET || instructionKind == InstructionKind.IPUT;
     // Outlined field references will return a value if getter and only takes arguments if
     // instance or if put or two arguments if both.
-    DexType returnType = fieldInstruction.isFieldGet() ? field.getType() : factory.voidType;
+    boolean isGet =
+        instructionKind == InstructionKind.IGET || instructionKind == InstructionKind.SGET;
+    DexType returnType = isGet ? field.getType() : factory.voidType;
     List<DexType> parameters = new ArrayList<>();
     if (isInstance) {
       parameters.add(libraryHolder.getType());
     }
-    if (fieldInstruction.isFieldPut()) {
+    boolean isPut = !isGet;
+    if (isPut) {
       parameters.add(field.getType());
     }
     methodBuilder
@@ -341,16 +270,13 @@
                         thenConsumer -> thenConsumer.setInstanceField(field),
                         elseConsumer -> elseConsumer.setStaticField(field))
                     .applyIf(
-                        fieldInstruction.isFieldGet(),
-                        FieldAccessorBuilder::setGetter,
-                        FieldAccessorBuilder::setSetter)
+                        isGet, FieldAccessorBuilder::setGetter, FieldAccessorBuilder::setSetter)
                     .setSourceMethod(programContext.getReference())
                     .build());
   }
 
-  private void setCodeForCheckCast(
-      SyntheticMethodBuilder methodBuilder, CfCheckCast instruction, DexItemFactory factory) {
-    DexClass target = appView.definitionFor(instruction.getType());
+  private void setCodeForCheckCast(SyntheticMethodBuilder methodBuilder, DexType type) {
+    DexClass target = appView.definitionFor(type);
     assert target != null;
     methodBuilder
         .setProto(factory.createProto(target.getType(), objectParams))
@@ -360,9 +286,8 @@
                     .generateCfCode());
   }
 
-  private void setCodeForInstanceOf(
-      SyntheticMethodBuilder methodBuilder, CfInstanceOf instruction, DexItemFactory factory) {
-    DexClass target = appView.definitionFor(instruction.getType());
+  private void setCodeForInstanceOf(SyntheticMethodBuilder methodBuilder, DexType type) {
+    DexClass target = appView.definitionFor(type);
     assert target != null;
     methodBuilder
         .setProto(factory.createProto(factory.booleanType, objectParams))
@@ -372,9 +297,8 @@
                     .generateCfCode());
   }
 
-  private void setCodeForConstClass(
-      SyntheticMethodBuilder methodBuilder, CfConstClass instruction, DexItemFactory factory) {
-    DexClass target = appView.definitionFor(instruction.getType());
+  private void setCodeForConstClass(SyntheticMethodBuilder methodBuilder, DexType type) {
+    DexClass target = appView.definitionFor(type);
     assert target != null;
     methodBuilder
         .setProto(factory.createProto(factory.classType))
@@ -390,4 +314,38 @@
     return libraryApiMethodDefinition == null
         || libraryApiMethodDefinition.isVirtualMethod() == isVirtualInvoke;
   }
+
+  enum InstructionKind {
+    CHECKCAST,
+    CONSTCLASS,
+    IGET,
+    IPUT,
+    INSTANCEOF,
+    INVOKEINTERFACE,
+    INVOKESTATIC,
+    INVOKEVIRTUAL,
+    SGET,
+    SPUT;
+
+    boolean isFieldInstruction() {
+      return this == IGET || this == IPUT || this == SGET || this == SPUT;
+    }
+
+    boolean isInvoke() {
+      return this == INVOKEINTERFACE || this == INVOKESTATIC || this == INVOKEVIRTUAL;
+    }
+
+    boolean isTypeInstruction() {
+      return this == InstructionKind.CHECKCAST
+          || this == InstructionKind.CONSTCLASS
+          || this == InstructionKind.INSTANCEOF;
+    }
+  }
+
+  interface RetargetMethodSupplier {
+
+    DexMethod getRetargetMethod(
+        CfInstructionDesugaringEventConsumer eventConsumer,
+        MethodProcessingContext methodProcessingContext);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/apimodel/CfToCfApiInvokeOutlinerDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/CfToCfApiInvokeOutlinerDesugaring.java
new file mode 100644
index 0000000..c4f9bbe
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/CfToCfApiInvokeOutlinerDesugaring.java
@@ -0,0 +1,121 @@
+// 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.apimodel;
+
+import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
+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.graph.AppView;
+import com.android.tools.r8.graph.CfCompareHelper;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
+import com.android.tools.r8.ir.desugar.DesugarDescription;
+import java.util.Collections;
+import java.util.function.IntConsumer;
+import org.objectweb.asm.Opcodes;
+
+public class CfToCfApiInvokeOutlinerDesugaring extends ApiInvokeOutlinerDesugaring
+    implements CfInstructionDesugaring {
+
+  CfToCfApiInvokeOutlinerDesugaring(AppView<?> appView, AndroidApiLevelCompute apiLevelCompute) {
+    super(appView, apiLevelCompute);
+  }
+
+  @Override
+  public void acceptRelevantAsmOpcodes(IntConsumer consumer) {
+    CfOpcodeUtils.acceptCfFieldInstructionOpcodes(consumer);
+    CfOpcodeUtils.acceptCfInvokeOpcodes(
+        opcode -> {
+          if (opcode != Opcodes.INVOKESPECIAL) {
+            consumer.accept(opcode);
+          }
+        });
+    consumer.accept(Opcodes.CHECKCAST);
+    consumer.accept(Opcodes.INSTANCEOF);
+  }
+
+  @Override
+  public void acceptRelevantCompareToIds(IntConsumer consumer) {
+    consumer.accept(CfCompareHelper.CONST_CLASS_COMPARE_ID);
+  }
+
+  @Override
+  public DesugarDescription compute(CfInstruction instruction, ProgramMethod context) {
+    InstructionKind instructionKind;
+    DexReference reference;
+    if (instruction.hasAsmOpcode()) {
+      switch (instruction.getAsmOpcode()) {
+        case Opcodes.CHECKCAST:
+          instructionKind = InstructionKind.CHECKCAST;
+          reference = instruction.asCheckCast().getType();
+          break;
+        case Opcodes.GETFIELD:
+          instructionKind = InstructionKind.IGET;
+          reference = instruction.asFieldInstruction().getField();
+          break;
+        case Opcodes.GETSTATIC:
+          instructionKind = InstructionKind.SGET;
+          reference = instruction.asFieldInstruction().getField();
+          break;
+        case Opcodes.INSTANCEOF:
+          instructionKind = InstructionKind.INSTANCEOF;
+          reference = instruction.asInstanceOf().getType();
+          break;
+        case Opcodes.INVOKEINTERFACE:
+          instructionKind = InstructionKind.INVOKEINTERFACE;
+          reference = instruction.asInvoke().getMethod();
+          break;
+        case Opcodes.INVOKESTATIC:
+          instructionKind = InstructionKind.INVOKESTATIC;
+          reference = instruction.asInvoke().getMethod();
+          break;
+        case Opcodes.INVOKEVIRTUAL:
+          instructionKind = InstructionKind.INVOKEVIRTUAL;
+          reference = instruction.asInvoke().getMethod();
+          break;
+        case Opcodes.PUTFIELD:
+          instructionKind = InstructionKind.IPUT;
+          reference = instruction.asFieldInstruction().getField();
+          break;
+        case Opcodes.PUTSTATIC:
+          instructionKind = InstructionKind.SPUT;
+          reference = instruction.asFieldInstruction().getField();
+          break;
+        default:
+          return DesugarDescription.nothing();
+      }
+    } else if (instruction.isConstClass()) {
+      instructionKind = InstructionKind.CONSTCLASS;
+      reference = instruction.asConstClass().getType();
+    } else {
+      return DesugarDescription.nothing();
+    }
+    RetargetMethodSupplier retargetMethodSupplier =
+        getRetargetMethodSupplier(instructionKind, reference, context);
+    if (retargetMethodSupplier != null) {
+      return DesugarDescription.builder()
+          .setDesugarRewrite(
+              (position,
+                  freshLocalProvider,
+                  localStackAllocator,
+                  desugaringInfo,
+                  eventConsumer,
+                  context1,
+                  methodProcessingContext,
+                  desugaringCollection,
+                  dexItemFactory) -> {
+                DexMethod retargetMethod =
+                    retargetMethodSupplier.getRetargetMethod(
+                        eventConsumer, methodProcessingContext);
+                return Collections.singletonList(
+                    new CfInvoke(Opcodes.INVOKESTATIC, retargetMethod, false));
+              })
+          .build();
+    }
+    return DesugarDescription.nothing();
+  }
+}