[ApiModel] Outline instance-of instructions to new library classes

Bug: b/258270051
Change-Id: If11f3309062fe74cfd78a46f5183bc45c2bf9607
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 82ada16..4c3fc63 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
@@ -10,6 +10,7 @@
 import com.android.tools.r8.androidapi.ComputedApiLevel;
 import com.android.tools.r8.cf.code.CfCheckCast;
 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.contexts.CompilationContext.MethodProcessingContext;
@@ -36,6 +37,7 @@
 import com.android.tools.r8.ir.synthetic.CheckCastSourceCode;
 import com.android.tools.r8.ir.synthetic.FieldAccessorBuilder;
 import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
+import com.android.tools.r8.ir.synthetic.InstanceOfSourceCode;
 import com.android.tools.r8.synthesis.SyntheticMethodBuilder;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.TraversalContinuation;
@@ -108,6 +110,8 @@
       reference = instruction.asFieldInstruction().getField();
     } else if (instruction.isCheckCast()) {
       reference = instruction.asCheckCast().getType();
+    } else if (instruction.isInstanceOf()) {
+      reference = instruction.asInstanceOf().getType();
     } else {
       return appView.computedMinApiLevel();
     }
@@ -126,7 +130,7 @@
       return appView.computedMinApiLevel();
     }
     // Check for protected or package private access flags before outlining.
-    if (holder.isInterface() || instruction.isCheckCast()) {
+    if (holder.isInterface() || instruction.isCheckCast() || instruction.isInstanceOf()) {
       return referenceApiLevel;
     } else {
       DexEncodedMember<?, ?> definition =
@@ -174,7 +178,10 @@
       DexItemFactory factory,
       ApiInvokeOutlinerDesugaringEventConsumer eventConsumer,
       ProgramMethod context) {
-    assert instruction.isInvoke() || instruction.isFieldInstruction() || instruction.isCheckCast();
+    assert instruction.isInvoke()
+        || instruction.isFieldInstruction()
+        || instruction.isCheckCast()
+        || instruction.isInstanceOf();
     ProgramMethod outlinedMethod =
         ensureOutlineMethod(uniqueContext, instruction, computedApiLevel, factory, context);
     eventConsumer.acceptOutlinedMethod(outlinedMethod, context);
@@ -208,6 +215,8 @@
                 setCodeForInvoke(syntheticMethodBuilder, instruction.asInvoke(), factory);
               } else if (instruction.isCheckCast()) {
                 setCodeForCheckCast(syntheticMethodBuilder, instruction.asCheckCast(), factory);
+              } else if (instruction.isInstanceOf()) {
+                setCodeForInstanceOf(syntheticMethodBuilder, instruction.asInstanceOf(), factory);
               } else {
                 assert instruction.isCfInstruction();
                 setCodeForFieldInstruction(
@@ -294,6 +303,18 @@
                     .generateCfCode());
   }
 
+  private void setCodeForInstanceOf(
+      SyntheticMethodBuilder methodBuilder, CfInstanceOf instruction, DexItemFactory factory) {
+    DexClass target = appView.definitionFor(instruction.getType());
+    assert target != null;
+    methodBuilder
+        .setProto(factory.createProto(factory.booleanType, objectParams))
+        .setCode(
+            m ->
+                InstanceOfSourceCode.create(appView, m.getHolderType(), target.getType())
+                    .generateCfCode());
+  }
+
   private boolean verifyLibraryHolderAndInvoke(
       DexClass libraryHolder, DexMethod apiMethod, boolean isVirtualInvoke) {
     DexEncodedMethod libraryApiMethodDefinition = libraryHolder.lookupMethod(apiMethod);
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/InstanceOfSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/InstanceOfSourceCode.java
new file mode 100644
index 0000000..57257ff
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/InstanceOfSourceCode.java
@@ -0,0 +1,41 @@
+// Copyright (c) 2022, 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.synthetic;
+
+import com.android.tools.r8.cf.code.CfInstanceOf;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.ValueType;
+import java.util.ArrayList;
+import java.util.List;
+
+// Source code representing a simple call to CheckCast.
+public final class InstanceOfSourceCode extends SyntheticCfCodeProvider {
+
+  private final DexType instanceOfType;
+
+  private InstanceOfSourceCode(AppView<?> appView, DexType holder, DexType instanceOfType) {
+    super(appView, holder);
+    this.instanceOfType = instanceOfType;
+  }
+
+  public static InstanceOfSourceCode create(
+      AppView<?> appView, DexType holder, DexType checkCastType) {
+    return new InstanceOfSourceCode(appView, holder, checkCastType);
+  }
+
+  @Override
+  public CfCode generateCfCode() {
+    List<CfInstruction> instructions = new ArrayList<>();
+    instructions.add(new CfLoad(ValueType.OBJECT, 0));
+    instructions.add(new CfInstanceOf(instanceOfType));
+    instructions.add(new CfReturn(ValueType.INT));
+    return standardCfCodeFromInstructions(instructions);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ComputeApiLevelUseRegistry.java b/src/main/java/com/android/tools/r8/shaking/ComputeApiLevelUseRegistry.java
index e9857a0..662f9f4 100644
--- a/src/main/java/com/android/tools/r8/shaking/ComputeApiLevelUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/ComputeApiLevelUseRegistry.java
@@ -133,7 +133,7 @@
 
   @Override
   public void registerInstanceOf(DexType type) {
-    // InstanceOf are not causing soft verification issues
+    setMaxApiReferenceLevel(type);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineInstanceOfTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineInstanceOfTest.java
index ad6b725..63e03a6 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineInstanceOfTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineInstanceOfTest.java
@@ -126,8 +126,8 @@
 
   private void inspect(CodeInspector inspector) throws Exception {
     verifyThat(inspector, parameters, LibraryClass.class)
-        // TODO(b/258270051): We should outline the call on lower api levels.
-        .hasNotInstanceOfOutlinedFrom(Main.class.getMethod("main", String[].class));
+        .hasInstanceOfOutlinedFromUntil(
+            Main.class.getMethod("main", String[].class), classApiLevel);
   }
 
   private void checkOutput(SingleTestRunResult<?> runResult) {