Lir-to-lir api outlining in R8 partial

Bug: b/411032206
Change-Id: If53a0100787f8b661f1488309f53f6c0aa4c4d1f
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiModelingOptions.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiModelingOptions.java
index 1c2f763..50c4862 100644
--- a/src/main/java/com/android/tools/r8/androidapi/AndroidApiModelingOptions.java
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiModelingOptions.java
@@ -117,6 +117,20 @@
     return isApiModelingEnabled() && options.isGeneratingDex() && enableOutliningOfMethods;
   }
 
+  public boolean isCfToCfApiOutliningEnabled() {
+    if (isOutliningOfMethodsEnabled()) {
+      // Enable cf-to-cf when running normal D8/R8. When running R8 partial, enable cf-to-cf
+      // desugaring when there is no library desugaring.
+      return options.partialSubCompilationConfiguration == null
+          || !options.getSubCompilationLibraryDesugaringOptions().isEnabled();
+    }
+    return false;
+  }
+
+  public boolean isLirToLirApiOutliningEnabled() {
+    return isOutliningOfMethodsEnabled() && !isCfToCfApiOutliningEnabled();
+  }
+
   public boolean isCheckAllApiReferencesAreSet() {
     return isApiModelingEnabled() && checkAllApiReferencesAreSet;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java
index 61e2697..0319e2a 100644
--- a/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMergerGraphLens;
 import com.android.tools.r8.ir.code.InvokeType;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.R8LibraryDesugaringGraphLens;
 import com.android.tools.r8.ir.optimize.CustomLensCodeRewriter;
 import com.android.tools.r8.ir.optimize.enums.EnumUnboxingLens;
 import com.android.tools.r8.optimize.MemberRebindingIdentityLens;
@@ -442,6 +443,14 @@
     return null;
   }
 
+  public boolean isLirToLirDesugaringLens() {
+    return false;
+  }
+
+  public R8LibraryDesugaringGraphLens asLirToLirDesugaringLens() {
+    return null;
+  }
+
   public boolean isNumberUnboxerLens() {
     return false;
   }
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 4d96a56..113dced 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
@@ -930,6 +930,11 @@
     }
 
     @Override
+    public void acceptOutlinedMethod(ProgramMethod outlinedMethod, ProgramMethod context) {
+      synthesizedMethods.add(outlinedMethod);
+    }
+
+    @Override
     public void acceptWrapperClasspathClass(DexClasspathClass clazz) {
       // Intentionally empty.
     }
@@ -949,11 +954,6 @@
     }
 
     @Override
-    public void acceptOutlinedMethod(ProgramMethod outlinedMethod, ProgramMethod context) {
-      assert false;
-    }
-
-    @Override
     public void acceptBackportedMethod(ProgramMethod backportedMethod, ProgramMethod context) {
       assert false;
     }
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 c4dd8d0..3bd73cf 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
@@ -42,7 +42,7 @@
  * 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 {
+public abstract class ApiInvokeOutlinerDesugaring {
 
   private final AppView<?> appView;
   private final AndroidApiLevelCompute apiLevelCompute;
@@ -59,13 +59,23 @@
 
   public static CfToCfApiInvokeOutlinerDesugaring createCfToCf(
       AppView<?> appView, AndroidApiLevelCompute apiLevelCompute) {
-    if (appView.options().apiModelingOptions().isOutliningOfMethodsEnabled()) {
+    if (appView.options().apiModelingOptions().isCfToCfApiOutliningEnabled()) {
       return new CfToCfApiInvokeOutlinerDesugaring(appView, apiLevelCompute);
     }
     return null;
   }
 
-  RetargetMethodSupplier getRetargetMethodSupplier(
+  public static LirToLirApiInvokeOutlinerDesugaring createLirToLir(
+      AppView<?> appView,
+      AndroidApiLevelCompute apiLevelCompute,
+      CfInstructionDesugaringEventConsumer eventConsumer) {
+    if (appView.options().apiModelingOptions().isLirToLirApiOutliningEnabled()) {
+      return new LirToLirApiInvokeOutlinerDesugaring(appView, apiLevelCompute, eventConsumer);
+    }
+    return null;
+  }
+
+  public RetargetMethodSupplier getRetargetMethodSupplier(
       InstructionKind instructionKind, DexReference reference, ProgramMethod context) {
     ComputedApiLevel computedApiLevel =
         getComputedApiLevelInstructionOnHolderWithMinApi(instructionKind, reference, context);
@@ -276,36 +286,24 @@
   }
 
   private void setCodeForCheckCast(SyntheticMethodBuilder methodBuilder, DexType type) {
-    DexClass target = appView.definitionFor(type);
-    assert target != null;
     methodBuilder
-        .setProto(factory.createProto(target.getType(), objectParams))
+        .setProto(factory.createProto(type, objectParams))
         .setCode(
-            m ->
-                CheckCastSourceCode.create(appView, m.getHolderType(), target.getType())
-                    .generateCfCode());
+            m -> CheckCastSourceCode.create(appView, m.getHolderType(), type).generateCfCode());
   }
 
   private void setCodeForInstanceOf(SyntheticMethodBuilder methodBuilder, DexType type) {
-    DexClass target = appView.definitionFor(type);
-    assert target != null;
     methodBuilder
         .setProto(factory.createProto(factory.booleanType, objectParams))
         .setCode(
-            m ->
-                InstanceOfSourceCode.create(appView, m.getHolderType(), target.getType())
-                    .generateCfCode());
+            m -> InstanceOfSourceCode.create(appView, m.getHolderType(), type).generateCfCode());
   }
 
   private void setCodeForConstClass(SyntheticMethodBuilder methodBuilder, DexType type) {
-    DexClass target = appView.definitionFor(type);
-    assert target != null;
     methodBuilder
         .setProto(factory.createProto(factory.classType))
         .setCode(
-            m ->
-                ConstClassSourceCode.create(appView, m.getHolderType(), target.getType())
-                    .generateCfCode());
+            m -> ConstClassSourceCode.create(appView, m.getHolderType(), type).generateCfCode());
   }
 
   private boolean verifyLibraryHolderAndInvoke(
@@ -315,7 +313,7 @@
         || libraryApiMethodDefinition.isVirtualMethod() == isVirtualInvoke;
   }
 
-  enum InstructionKind {
+  public enum InstructionKind {
     CHECKCAST,
     CONSTCLASS,
     IGET,
@@ -327,11 +325,11 @@
     SGET,
     SPUT;
 
-    boolean isFieldInstruction() {
+    public boolean isFieldInstruction() {
       return this == IGET || this == IPUT || this == SGET || this == SPUT;
     }
 
-    boolean isInvoke() {
+    public boolean isInvoke() {
       return this == INVOKEINTERFACE || this == INVOKESTATIC || this == INVOKEVIRTUAL;
     }
 
@@ -342,7 +340,7 @@
     }
   }
 
-  interface RetargetMethodSupplier {
+  public interface RetargetMethodSupplier {
 
     DexMethod getRetargetMethod(
         CfInstructionDesugaringEventConsumer eventConsumer,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/apimodel/LirToLirApiInvokeOutlinerDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/LirToLirApiInvokeOutlinerDesugaring.java
new file mode 100644
index 0000000..69b909a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/LirToLirApiInvokeOutlinerDesugaring.java
@@ -0,0 +1,73 @@
+// 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.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.graph.AppView;
+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.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 LirToLirApiInvokeOutlinerDesugaring extends ApiInvokeOutlinerDesugaring {
+
+  private final CfInstructionDesugaringEventConsumer eventConsumer;
+
+  LirToLirApiInvokeOutlinerDesugaring(
+      AppView<?> appView,
+      AndroidApiLevelCompute apiLevelCompute,
+      CfInstructionDesugaringEventConsumer eventConsumer) {
+    super(appView, apiLevelCompute);
+    this.eventConsumer = eventConsumer;
+  }
+
+  public MethodLookupResult lookupMethod(
+      MethodLookupResult previous,
+      ProgramMethod context,
+      MethodProcessingContext methodProcessingContext,
+      R8LibraryDesugaringGraphLens lens) {
+    InstructionKind instructionKind;
+    switch (previous.getType()) {
+      case INTERFACE:
+        instructionKind = InstructionKind.INVOKEINTERFACE;
+        break;
+      case STATIC:
+        instructionKind = InstructionKind.INVOKESTATIC;
+        break;
+      case VIRTUAL:
+        instructionKind = InstructionKind.INVOKEVIRTUAL;
+        break;
+      default:
+        return previous;
+    }
+    DexMethod retargetMethod =
+        getRetargetMethod(
+            instructionKind, previous.getReference(), context, methodProcessingContext);
+    if (retargetMethod != null) {
+      return MethodLookupResult.builder(lens, lens.getPrevious())
+          .setReference(retargetMethod)
+          .setReboundReference(retargetMethod)
+          .setIsInterface(false)
+          .setType(InvokeType.STATIC)
+          .build();
+    }
+    return previous;
+  }
+
+  public DexMethod getRetargetMethod(
+      InstructionKind instructionKind,
+      DexReference reference,
+      ProgramMethod context,
+      MethodProcessingContext methodProcessingContext) {
+    RetargetMethodSupplier retargetMethodSupplier =
+        getRetargetMethodSupplier(instructionKind, reference, context);
+    return retargetMethodSupplier != null
+        ? retargetMethodSupplier.getRetargetMethod(eventConsumer, methodProcessingContext)
+        : null;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/R8LibraryDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/R8LibraryDesugaring.java
index bcda2e1..2cdb601 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/R8LibraryDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/R8LibraryDesugaring.java
@@ -18,6 +18,8 @@
 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.apimodel.ApiInvokeOutlinerDesugaring;
+import com.android.tools.r8.ir.desugar.apimodel.LirToLirApiInvokeOutlinerDesugaring;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.LirToLirDesugaredLibraryApiConverter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.disabledesugarer.DesugaredLibraryDisableDesugarer;
@@ -60,6 +62,8 @@
         && options.getLibraryDesugaringOptions().isLirToLirLibraryDesugaringEnabled()
         && options.isGeneratingDex()) {
       new R8LibraryDesugaring(appView).run(executorService, timing);
+    } else {
+      assert !appView.options().apiModelingOptions().isLirToLirApiOutliningEnabled();
     }
   }
 
@@ -101,6 +105,9 @@
     CfInstructionDesugaringEventConsumer eventConsumer =
         CfInstructionDesugaringEventConsumer.createForR8LibraryDesugaring(
             appView, profileCollectionAdditions, synthesizedMethods);
+    LirToLirApiInvokeOutlinerDesugaring apiOutliner =
+        ApiInvokeOutlinerDesugaring.createLirToLir(
+            appView, appView.apiLevelCompute(), eventConsumer);
     LirToLirDesugaredLibraryDisableDesugarer desugaredLibraryDisableDesugarer =
         DesugaredLibraryDisableDesugarer.createLirToLir(appView);
     LirToLirDesugaredLibraryLibRewriter desugaredLibraryLibRewriter =
@@ -125,6 +132,7 @@
                   R8LibraryDesugaringGraphLens libraryDesugaringGraphLens =
                       new R8LibraryDesugaringGraphLens(
                           appView,
+                          apiOutliner,
                           desugaredLibraryApiConverter,
                           desugaredLibraryDisableDesugarer,
                           desugaredLibraryLibRewriter,
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 6fb3ba5..89a0460 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
@@ -6,7 +6,9 @@
 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.DexField;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.lens.DefaultNonIdentityGraphLens;
@@ -27,10 +29,14 @@
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.Opcodes;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.apimodel.ApiInvokeOutlinerDesugaring;
+import com.android.tools.r8.ir.desugar.apimodel.ApiInvokeOutlinerDesugaring.InstructionKind;
+import com.android.tools.r8.ir.desugar.apimodel.LirToLirApiInvokeOutlinerDesugaring;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryConversionCfProvider;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.LirToLirDesugaredLibraryApiConverter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.disabledesugarer.LirToLirDesugaredLibraryDisableDesugarer;
@@ -46,6 +52,7 @@
 // TODO(b/391572031): Apply type instruction rewriting from CfToCfDesugaredLibraryDisableDesugarer.
 public class R8LibraryDesugaringGraphLens extends DefaultNonIdentityGraphLens {
 
+  private final LirToLirApiInvokeOutlinerDesugaring apiOutliner;
   private final LirToLirDesugaredLibraryApiConverter desugaredLibraryApiConverter;
   private final LirToLirDesugaredLibraryDisableDesugarer desugaredLibraryDisableDesugarer;
   private final LirToLirDesugaredLibraryLibRewriter desugaredLibraryLibRewriter;
@@ -58,6 +65,7 @@
 
   public R8LibraryDesugaringGraphLens(
       AppView<? extends AppInfoWithClassHierarchy> appView,
+      LirToLirApiInvokeOutlinerDesugaring apiOutliner,
       LirToLirDesugaredLibraryApiConverter desugaredLibraryApiConverter,
       LirToLirDesugaredLibraryDisableDesugarer desugaredLibraryDisableDesugarer,
       LirToLirDesugaredLibraryLibRewriter desugaredLibraryLibRewriter,
@@ -67,6 +75,7 @@
       ProgramMethod method,
       MethodProcessingContext methodProcessingContext) {
     super(appView);
+    this.apiOutliner = apiOutliner;
     this.desugaredLibraryApiConverter = desugaredLibraryApiConverter;
     this.desugaredLibraryDisableDesugarer = desugaredLibraryDisableDesugarer;
     this.desugaredLibraryLibRewriter = desugaredLibraryLibRewriter;
@@ -77,6 +86,45 @@
     this.methodProcessingContext = methodProcessingContext;
   }
 
+  public boolean needsApiOutlining(
+      ApiInvokeOutlinerDesugaring.InstructionKind instructionKind,
+      DexReference reference,
+      ProgramMethod context) {
+    return apiOutliner != null
+        && apiOutliner.getRetargetMethodSupplier(instructionKind, reference, context) != null
+        && !needsLibraryDesugaring(instructionKind, reference);
+  }
+
+  private DexMethod lookupApiOutline(
+      ApiInvokeOutlinerDesugaring.InstructionKind instructionKind,
+      DexReference reference,
+      ProgramMethod context) {
+    return apiOutliner != null && !needsLibraryDesugaring(instructionKind, reference)
+        ? apiOutliner.getRetargetMethod(
+            instructionKind, reference, context, methodProcessingContext)
+        : null;
+  }
+
+  private boolean needsLibraryDesugaring(
+      ApiInvokeOutlinerDesugaring.InstructionKind instructionKind, DexReference reference) {
+    if (instructionKind.isFieldInstruction()) {
+      DexField field = reference.asDexField();
+      DexField lookupResult = lookupField(field, appView.codeLens());
+      return lookupResult.isNotIdenticalTo(field);
+    }
+    return false;
+  }
+
+  @Override
+  public boolean isLirToLirDesugaringLens() {
+    return true;
+  }
+
+  @Override
+  public R8LibraryDesugaringGraphLens asLirToLirDesugaringLens() {
+    return this;
+  }
+
   @Override
   public boolean hasCustomLensCodeRewriter() {
     return true;
@@ -149,6 +197,10 @@
           previous, method, methodProcessingContext, this);
     }
 
+    if (apiOutliner != null) {
+      return apiOutliner.lookupMethod(previous, method, methodProcessingContext, this);
+    }
+
     return previous;
   }
 
@@ -162,26 +214,84 @@
         NonIdentityGraphLens lens) {
       boolean changed = false;
       BasicBlockIterator blocks = code.listIterator();
-      GraphLens codeLens = code.context().getDefinition().getCode().getCodeLens(appView);
+      ProgramMethod context = code.context();
+      GraphLens codeLens = 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;
+          Instruction instruction = instructions.next();
+          DexMethod apiOutline = null;
+          switch (instruction.opcode()) {
+            case Opcodes.CHECK_CAST:
+              apiOutline =
+                  lookupApiOutline(
+                      InstructionKind.CHECKCAST, instruction.asCheckCast().getType(), context);
+              break;
+            case Opcodes.CONST_CLASS:
+              apiOutline =
+                  lookupApiOutline(
+                      InstructionKind.CONSTCLASS, instruction.asConstClass().getType(), context);
+              break;
+            case Opcodes.INSTANCE_GET:
+              apiOutline =
+                  lookupApiOutline(
+                      InstructionKind.IGET, instruction.asInstanceGet().getField(), context);
+              break;
+            case Opcodes.INSTANCE_OF:
+              apiOutline =
+                  lookupApiOutline(
+                      InstructionKind.INSTANCEOF, instruction.asInstanceOf().type(), context);
+              break;
+            case Opcodes.INSTANCE_PUT:
+              apiOutline =
+                  lookupApiOutline(
+                      InstructionKind.INSTANCEOF, instruction.asInstancePut().getField(), context);
+              break;
+            case Opcodes.INVOKE_DIRECT:
+            case Opcodes.INVOKE_INTERFACE:
+            case Opcodes.INVOKE_STATIC:
+            case Opcodes.INVOKE_SUPER:
+            case Opcodes.INVOKE_VIRTUAL:
+              {
+                InvokeMethod invoke = instruction.asInvokeMethod();
+                MethodLookupResult lookupResult =
+                    lookupMethod(
+                        invoke.getInvokedMethod(),
+                        context.getReference(),
+                        invoke.getType(),
+                        codeLens,
+                        invoke.getInterfaceBit());
+                if (lookupResult.isNeedsDesugaredLibraryApiConversionSet()) {
+                  rewriteInvoke(code, blocks, instructions, invoke);
+                  changed = true;
+                }
+                break;
+              }
+            case Opcodes.STATIC_GET:
+              apiOutline =
+                  lookupApiOutline(
+                      InstructionKind.SGET, instruction.asStaticGet().getField(), context);
+              break;
+            case Opcodes.STATIC_PUT:
+              apiOutline =
+                  lookupApiOutline(
+                      InstructionKind.SPUT, instruction.asStaticPut().getField(), context);
+              break;
+            default:
+              break;
           }
-          MethodLookupResult lookupResult =
-              lookupMethod(
-                  invoke.getInvokedMethod(),
-                  code.context().getReference(),
-                  invoke.getType(),
-                  codeLens,
-                  invoke.getInterfaceBit());
-          if (lookupResult.isNeedsDesugaredLibraryApiConversionSet()) {
-            rewriteInvoke(code, blocks, instructions, invoke);
+          if (apiOutline != null) {
+            instructions.replaceCurrentInstruction(
+                InvokeStatic.builder()
+                    .setArguments(instruction.inValues())
+                    .setMethod(apiOutline)
+                    .setOutValue(instruction.outValue())
+                    .setIsInterface(false)
+                    .setPosition(instruction)
+                    .build());
+            changed = true;
           }
-          changed = true;
         }
       }
       assert changed;
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 ae31471..b951514 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirLensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirLensCodeRewriter.java
@@ -32,7 +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.desugar.apimodel.ApiInvokeOutlinerDesugaring.InstructionKind;
 import com.android.tools.r8.ir.optimize.AffectedValues;
 import com.android.tools.r8.ir.optimize.DeadCodeRemover;
 import com.android.tools.r8.lightir.LirBuilder.NameComputationPayload;
@@ -43,6 +43,7 @@
 import com.android.tools.r8.utils.timing.Timing;
 import com.android.tools.r8.verticalclassmerging.VerticalClassMergerGraphLens;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
 import java.util.ArrayList;
@@ -54,7 +55,9 @@
 
 public class LirLensCodeRewriter<EV> extends LirParsedInstructionCallback<EV> {
 
-  private static final Set<DexMethod> NO_INVOKES_TO_REWRITE = ImmutableSet.of();
+  private static final Set<DexField> NO_FIELD_INSTRUCTIONS_TO_REWRITE = ImmutableSet.of();
+  private static final Set<DexMethod> NO_INVOKE_INSTRUCTIONS_TO_REWRITE = ImmutableSet.of();
+  private static final Set<DexType> NO_TYPE_INSTRUCTIONS_TO_REWRITE = ImmutableSet.of();
 
   private final AppView<?> appView;
   private final ProgramMethod context;
@@ -66,9 +69,12 @@
   private final boolean isNonStartupInStartupOutlinerLens;
 
   private int numberOfInvokeOpcodeChanges = 0;
-  private Set<DexMethod> invokesToRewrite = NO_INVOKES_TO_REWRITE;
+  private Set<DexMethod> invokeInstructionsToRewrite = NO_INVOKE_INSTRUCTIONS_TO_REWRITE;
   private Map<LirConstant, LirConstant> constantPoolMapping = null;
 
+  private Set<DexField> fieldInstructionsToRewrite = NO_FIELD_INSTRUCTIONS_TO_REWRITE;
+  private Set<DexType> typeInstructionsToRewrite = NO_TYPE_INSTRUCTIONS_TO_REWRITE;
+
   private boolean hasNonTrivialRewritings = false;
 
   public LirLensCodeRewriter(
@@ -181,7 +187,7 @@
         }
       }
     }
-    if (graphLens instanceof R8LibraryDesugaringGraphLens) {
+    if (graphLens.isLirToLirDesugaringLens()) {
       if (result.isNeedsDesugaredLibraryApiConversionSet()) {
         return true;
       }
@@ -192,6 +198,13 @@
     return false;
   }
 
+  private void addFieldInstructionToRewrite(DexField field) {
+    if (fieldInstructionsToRewrite == NO_FIELD_INSTRUCTIONS_TO_REWRITE) {
+      fieldInstructionsToRewrite = Sets.newIdentityHashSet();
+    }
+    fieldInstructionsToRewrite.add(field);
+  }
+
   private void addRewrittenMethodMapping(DexMethod method, DexMethod rewrittenMethod) {
     getOrCreateConstantPoolMapping()
         .compute(
@@ -203,15 +216,22 @@
                 // Two invokes with the same symbolic method reference but different invoke types
                 // are rewritten to two different symbolic method references. Record that the
                 // invokes need to be processed.
-                if (invokesToRewrite == NO_INVOKES_TO_REWRITE) {
-                  invokesToRewrite = new HashSet<>();
+                if (invokeInstructionsToRewrite == NO_INVOKE_INSTRUCTIONS_TO_REWRITE) {
+                  invokeInstructionsToRewrite = new HashSet<>();
                 }
-                invokesToRewrite.add(method);
+                invokeInstructionsToRewrite.add(method);
                 return method;
               }
             });
   }
 
+  private void addTypeInstructionToRewrite(DexType type) {
+    if (typeInstructionsToRewrite == NO_TYPE_INSTRUCTIONS_TO_REWRITE) {
+      typeInstructionsToRewrite = Sets.newIdentityHashSet();
+    }
+    typeInstructionsToRewrite.add(type);
+  }
+
   private void addRewrittenMapping(LirConstant item, LirConstant rewrittenItem) {
     if (item == rewrittenItem) {
       return;
@@ -239,6 +259,46 @@
   }
 
   @Override
+  public void onCheckCast(DexType type, EV value, boolean ignoreCompatRules) {
+    if (graphLens.isLirToLirDesugaringLens()
+        && graphLens
+            .asLirToLirDesugaringLens()
+            .needsApiOutlining(InstructionKind.CHECKCAST, type, context)) {
+      addTypeInstructionToRewrite(type);
+    }
+  }
+
+  @Override
+  public void onSafeCheckCast(DexType type, EV value) {
+    if (graphLens.isLirToLirDesugaringLens()
+        && graphLens
+            .asLirToLirDesugaringLens()
+            .needsApiOutlining(InstructionKind.CHECKCAST, type, context)) {
+      addTypeInstructionToRewrite(type);
+    }
+  }
+
+  @Override
+  public void onConstClass(DexType type, boolean ignoreCompatRules) {
+    if (graphLens.isLirToLirDesugaringLens()
+        && graphLens
+            .asLirToLirDesugaringLens()
+            .needsApiOutlining(InstructionKind.CONSTCLASS, type, context)) {
+      addTypeInstructionToRewrite(type);
+    }
+  }
+
+  @Override
+  public void onInstanceOf(DexType type, EV value) {
+    if (graphLens.isLirToLirDesugaringLens()
+        && graphLens
+            .asLirToLirDesugaringLens()
+            .needsApiOutlining(InstructionKind.INSTANCEOF, type, context)) {
+      addTypeInstructionToRewrite(type);
+    }
+  }
+
+  @Override
   public void onDexItemBasedConstString(
       DexReference item, NameComputationInfo<?> nameComputationInfo) {
     addRewrittenMapping(item, graphLens.getRenamedReference(item, codeLens));
@@ -246,56 +306,83 @@
 
   @Override
   public void onInstanceGet(DexField field, EV object) {
-    onFieldGet(field);
+    if (setHasPotentialNonTrivialFieldGetRewriting(field)) {
+      return;
+    }
+    if (graphLens.isLirToLirDesugaringLens()
+        && graphLens
+            .asLirToLirDesugaringLens()
+            .needsApiOutlining(InstructionKind.IGET, field, context)) {
+      addFieldInstructionToRewrite(field);
+    }
   }
 
   @Override
   public void onStaticGet(DexField field) {
-    onFieldGet(field);
-  }
-
-  private void onFieldGet(DexField field) {
-    if (hasPotentialNonTrivialFieldGetRewriting(field)) {
-      hasNonTrivialRewritings = true;
+    if (setHasPotentialNonTrivialFieldGetRewriting(field)) {
+      return;
+    }
+    if (graphLens.isLirToLirDesugaringLens()
+        && graphLens
+            .asLirToLirDesugaringLens()
+            .needsApiOutlining(InstructionKind.SGET, field, context)) {
+      addFieldInstructionToRewrite(field);
     }
   }
 
-  private boolean hasPotentialNonTrivialFieldGetRewriting(DexField field) {
+  private boolean setHasPotentialNonTrivialFieldGetRewriting(DexField field) {
     HorizontalClassMergerGraphLens horizontalClassMergerLens =
         graphLens.asHorizontalClassMergerGraphLens();
     if (horizontalClassMergerLens != null) {
       FieldLookupResult result = horizontalClassMergerLens.lookupFieldResult(field, codeLens);
-      return result.hasReadCastType();
+      if (result.hasReadCastType()) {
+        hasNonTrivialRewritings = true;
+        return true;
+      }
     }
     return false;
   }
 
   @Override
   public void onInstancePut(DexField field, EV object, EV value) {
-    onFieldPut(field);
+    if (setHasPotentialNonTrivialFieldPutRewriting(field)) {
+      return;
+    }
+    if (graphLens.isLirToLirDesugaringLens()
+        && graphLens
+            .asLirToLirDesugaringLens()
+            .needsApiOutlining(InstructionKind.IPUT, field, context)) {
+      addFieldInstructionToRewrite(field);
+    }
   }
 
   @Override
   public void onStaticPut(DexField field, EV value) {
-    onFieldPut(field);
-  }
-
-  private void onFieldPut(DexField field) {
-    if (hasPotentialNonTrivialFieldPutRewriting(field)) {
-      hasNonTrivialRewritings = true;
+    if (setHasPotentialNonTrivialFieldPutRewriting(field)) {
+      return;
+    }
+    if (graphLens.isLirToLirDesugaringLens()
+        && graphLens
+            .asLirToLirDesugaringLens()
+            .needsApiOutlining(InstructionKind.SPUT, field, context)) {
+      addFieldInstructionToRewrite(field);
     }
   }
 
-  private boolean hasPotentialNonTrivialFieldPutRewriting(DexField field) {
+  private boolean setHasPotentialNonTrivialFieldPutRewriting(DexField field) {
     HorizontalClassMergerGraphLens horizontalClassMergerLens =
         graphLens.asHorizontalClassMergerGraphLens();
     if (horizontalClassMergerLens != null) {
       FieldLookupResult result = horizontalClassMergerLens.lookupFieldResult(field, codeLens);
-      return result.hasWriteCastType();
+      if (result.hasWriteCastType()) {
+        hasNonTrivialRewritings = true;
+        return true;
+      }
     }
     VerticalClassMergerGraphLens verticalClassMergerLens = graphLens.asVerticalClassMergerLens();
     if (verticalClassMergerLens != null
         && verticalClassMergerLens.hasInterfaceBeenMergedIntoClass(field.getType())) {
+      hasNonTrivialRewritings = true;
       return true;
     }
     return false;
@@ -328,7 +415,7 @@
 
   private boolean isInvokeThatMaybeRequiresRewriting(int opcode) {
     assert LirOpcodeUtils.isInvokeMethod(opcode);
-    if (!invokesToRewrite.isEmpty()) {
+    if (!invokeInstructionsToRewrite.isEmpty()) {
       return true;
     }
     if (codeLens.isIdentityLens() && LirOpcodeUtils.isInvokeMethod(opcode)) {
@@ -362,7 +449,7 @@
         return true;
       }
     }
-    if (graphLens instanceof R8LibraryDesugaringGraphLens) {
+    if (graphLens.isLirToLirDesugaringLens()) {
       return LirOpcodeUtils.isInvokeMethod(opcode);
     }
     return false;
@@ -381,7 +468,9 @@
     }
     assert !hasNonTrivialRewritings;
     LirCode<EV> rewritten = rewriteConstantPoolAndScanForTypeChanges(getCode());
-    if (hasNonTrivialRewritings) {
+    if (hasNonTrivialRewritings
+        || fieldInstructionsToRewrite != NO_FIELD_INSTRUCTIONS_TO_REWRITE
+        || typeInstructionsToRewrite != NO_TYPE_INSTRUCTIONS_TO_REWRITE) {
       return rewriteWithLensCodeRewriter();
     }
     rewritten = rewriteInstructionsWithInvokeTypeChanges(rewritten);
@@ -481,9 +570,11 @@
     boolean hasDexItemBasedConstString = false;
     boolean hasFieldReference = false;
     boolean hasPotentialRewrittenMethod = false;
+    boolean hasTypeReference = false;
     for (LirConstant constant : code.getConstantPool()) {
       if (constant instanceof DexType) {
         onTypeReference((DexType) constant);
+        hasTypeReference = true;
       } else if (constant instanceof DexField) {
         onFieldReference((DexField) constant);
         hasFieldReference = true;
@@ -505,11 +596,15 @@
 
     // If there are potential method rewritings then we need to iterate the instructions as the
     // rewriting is instruction-sensitive (i.e., may be dependent on the invoke type).
-    boolean hasPotentialNonTrivialFieldAccessRewriting =
-        hasFieldReference && graphLens.isClassMergerLens();
+    boolean hasPotentialRewrittenFieldInstruction =
+        hasFieldReference
+            && (graphLens.isClassMergerLens() || graphLens.isLirToLirDesugaringLens());
+    boolean hasPotentialRewrittenTypeInstruction =
+        hasTypeReference && graphLens.isLirToLirDesugaringLens();
     if (hasDexItemBasedConstString
-        || hasPotentialNonTrivialFieldAccessRewriting
-        || hasPotentialRewrittenMethod) {
+        || hasPotentialRewrittenFieldInstruction
+        || hasPotentialRewrittenMethod
+        || hasPotentialRewrittenTypeInstruction) {
       for (LirInstructionView view : code) {
         view.accept(this);
         if (hasNonTrivialRewritings) {
@@ -527,7 +622,7 @@
   }
 
   private LirCode<EV> rewriteInstructionsWithInvokeTypeChanges(LirCode<EV> code) {
-    if (numberOfInvokeOpcodeChanges == 0 && invokesToRewrite.isEmpty()) {
+    if (numberOfInvokeOpcodeChanges == 0 && invokeInstructionsToRewrite.isEmpty()) {
       return code;
     }
     // Build a small map from method refs to index in case the type-dependent methods are already
@@ -573,7 +668,7 @@
       boolean newIsInterface = lookupIsInterface(method, opcode, result);
       InvokeType newType = result.getType();
       int newOpcode = newType.getLirOpcode(newIsInterface);
-      if (newOpcode != opcode || invokesToRewrite.contains(method)) {
+      if (newOpcode != opcode || invokeInstructionsToRewrite.contains(method)) {
         constantIndex =
             methodIndices.computeIfAbsent(
                 result.getReference(),