[ApiModel] Outline const class instructions

Bug: b/258270051
Change-Id: I0c6735309c1156dcdfd82379ca722d49ba5754bd
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
index e8019d9..6ade9b2 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
@@ -47,6 +47,16 @@
     this.ignoreCompatRules = ignoreCompatRules;
   }
 
+  @Override
+  public boolean isConstClass() {
+    return true;
+  }
+
+  @Override
+  public CfConstClass asConstClass() {
+    return this;
+  }
+
   public boolean ignoreCompatRules() {
     return ignoreCompatRules;
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index f21fc0c..9230c60 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -420,4 +420,12 @@
   public CfCheckCast asCheckCast() {
     return null;
   }
+
+  public boolean isConstClass() {
+    return false;
+  }
+
+  public CfConstClass asConstClass() {
+    return null;
+  }
 }
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 be663de..99eef8c 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
@@ -9,6 +9,7 @@
 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;
@@ -35,6 +36,7 @@
 import com.android.tools.r8.ir.desugar.FreshLocalProvider;
 import com.android.tools.r8.ir.desugar.LocalStackAllocator;
 import com.android.tools.r8.ir.synthetic.CheckCastSourceCode;
+import com.android.tools.r8.ir.synthetic.ConstClassSourceCode;
 import com.android.tools.r8.ir.synthetic.FieldAccessorBuilder;
 import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
 import com.android.tools.r8.ir.synthetic.InstanceOfSourceCode;
@@ -114,6 +116,8 @@
       reference = instruction.asCheckCast().getType();
     } else if (instruction.isInstanceOf()) {
       reference = instruction.asInstanceOf().getType();
+    } else if (instruction.isConstClass()) {
+      reference = instruction.asConstClass().getType();
     } else {
       return appView.computedMinApiLevel();
     }
@@ -147,7 +151,8 @@
     // Check for protected or package private access flags before outlining.
     if (firstLibraryClass.isInterface()
         || instruction.isCheckCast()
-        || instruction.isInstanceOf()) {
+        || instruction.isInstanceOf()
+        || instruction.isConstClass()) {
       return referenceApiLevel;
     } else {
       DexEncodedMember<?, ?> definition =
@@ -198,7 +203,8 @@
     assert instruction.isInvoke()
         || instruction.isFieldInstruction()
         || instruction.isCheckCast()
-        || instruction.isInstanceOf();
+        || instruction.isInstanceOf()
+        || instruction.isConstClass();
     ProgramMethod outlinedMethod =
         ensureOutlineMethod(uniqueContext, instruction, computedApiLevel, factory, context);
     eventConsumer.acceptOutlinedMethod(outlinedMethod, context);
@@ -234,6 +240,8 @@
                 setCodeForCheckCast(syntheticMethodBuilder, instruction.asCheckCast(), factory);
               } else if (instruction.isInstanceOf()) {
                 setCodeForInstanceOf(syntheticMethodBuilder, instruction.asInstanceOf(), factory);
+              } else if (instruction.isConstClass()) {
+                setCodeForConstClass(syntheticMethodBuilder, instruction.asConstClass(), factory);
               } else {
                 assert instruction.isCfInstruction();
                 setCodeForFieldInstruction(
@@ -332,6 +340,18 @@
                     .generateCfCode());
   }
 
+  private void setCodeForConstClass(
+      SyntheticMethodBuilder methodBuilder, CfConstClass instruction, DexItemFactory factory) {
+    DexClass target = appView.definitionFor(instruction.getType());
+    assert target != null;
+    methodBuilder
+        .setProto(factory.createProto(factory.classType))
+        .setCode(
+            m ->
+                ConstClassSourceCode.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/ConstClassSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/ConstClassSourceCode.java
new file mode 100644
index 0000000..b1c04e9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/ConstClassSourceCode.java
@@ -0,0 +1,39 @@
+// 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.CfConstClass;
+import com.android.tools.r8.cf.code.CfInstruction;
+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 a const class.
+public final class ConstClassSourceCode extends SyntheticCfCodeProvider {
+
+  private final DexType constClassType;
+
+  private ConstClassSourceCode(AppView<?> appView, DexType holder, DexType constClassType) {
+    super(appView, holder);
+    this.constClassType = constClassType;
+  }
+
+  public static ConstClassSourceCode create(
+      AppView<?> appView, DexType holder, DexType checkCastType) {
+    return new ConstClassSourceCode(appView, holder, checkCastType);
+  }
+
+  @Override
+  public CfCode generateCfCode() {
+    List<CfInstruction> instructions = new ArrayList<>();
+    instructions.add(new CfConstClass(constClassType));
+    instructions.add(new CfReturn(ValueType.OBJECT));
+    return standardCfCodeFromInstructions(instructions);
+  }
+}
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
index 57257ff..3f32d83 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/InstanceOfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/InstanceOfSourceCode.java
@@ -15,7 +15,7 @@
 import java.util.ArrayList;
 import java.util.List;
 
-// Source code representing a simple call to CheckCast.
+// Source code representing a simple call to instance-of.
 public final class InstanceOfSourceCode extends SyntheticCfCodeProvider {
 
   private final DexType instanceOfType;
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineConstClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineConstClassTest.java
index adb64ce..64a3180 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineConstClassTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineConstClassTest.java
@@ -110,8 +110,8 @@
 
   private void inspect(CodeInspector inspector) throws Exception {
     verifyThat(inspector, parameters, LibraryClass.class)
-        // TODO(b/258270051): We should outline const class references to new api levels.
-        .hasNotConstClassOutlinedFrom(Main.class.getMethod("main", String[].class));
+        .hasConstClassOutlinedFromUntil(
+            Main.class.getMethod("main", String[].class), classApiLevel);
   }
 
   private void checkOutput(SingleTestRunResult<?> runResult) {