[ApiModel] Add support for outlining methods in D8

Bug: 213552119
Change-Id: Iebbfe7a8a337710f4cf7ab3ea7e742ed8b468241
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 43868c3..f80403a 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -323,7 +323,7 @@
 
         finalizeApplication(appView, executor);
 
-        if (options.apiModelingOptions().enableStubbingOfClasses && !appView.options().debug) {
+        if (options.apiModelingOptions().enableStubbingOfClasses) {
           new ApiReferenceStubber(appView).run(executor);
         }
 
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 4fecb6d..6c0c957 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
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.ClassConverterResult;
 import com.android.tools.r8.ir.conversion.D8MethodProcessor;
+import com.android.tools.r8.ir.desugar.apimodel.ApiInvokeOutlinerDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.backports.BackportedMethodDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicClass;
 import com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicDesugaringEventConsumer;
@@ -57,7 +58,8 @@
         InterfaceMethodDesugaringEventConsumer,
         DesugaredLibraryRetargeterInstructionEventConsumer,
         DesugaredLibraryAPIConverterEventConsumer,
-        ClasspathEmulatedInterfaceSynthesizerEventConsumer {
+        ClasspathEmulatedInterfaceSynthesizerEventConsumer,
+        ApiInvokeOutlinerDesugaringEventConsumer {
 
   public static D8CfInstructionDesugaringEventConsumer createForD8(
       D8MethodProcessor methodProcessor) {
@@ -266,6 +268,11 @@
       assert synthesizedConstantDynamicClasses.isEmpty();
       return true;
     }
+
+    @Override
+    public void acceptOutlinedMethod(ProgramMethod outlinedMethod, ProgramMethod context) {
+      methodProcessor.scheduleDesugaredMethodForProcessing(outlinedMethod);
+    }
   }
 
   public static class R8CfInstructionDesugaringEventConsumer
@@ -459,5 +466,10 @@
       // Remove all '$deserializeLambda$' methods which are not supported by desugaring.
       LambdaDeserializationMethodRemover.run(appView, classesWithSerializableLambdas);
     }
+
+    @Override
+    public void acceptOutlinedMethod(ProgramMethod outlinedMethod, ProgramMethod context) {
+      // Intentionally empty. The method will be hit by tracing if required.
+    }
   }
 }
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 e642655..06fadd9 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
@@ -63,7 +63,9 @@
           methodProcessingContext.createUniqueContext(),
           instruction.asInvoke(),
           computedApiLevel,
-          dexItemFactory);
+          dexItemFactory,
+          eventConsumer,
+          context);
     }
     return null;
   }
@@ -146,14 +148,17 @@
   }
 
   private Collection<CfInstruction> desugarLibraryCall(
-      UniqueContext context,
+      UniqueContext uniqueContext,
       CfInvoke invoke,
       ComputedApiLevel computedApiLevel,
-      DexItemFactory factory) {
+      DexItemFactory factory,
+      ApiInvokeOutlinerDesugaringEventConsumer eventConsumer,
+      ProgramMethod context) {
     DexMethod method = invoke.getMethod();
-    ProgramMethod programMethod =
-        ensureOutlineMethod(context, method, computedApiLevel, factory, invoke);
-    return ImmutableList.of(new CfInvoke(INVOKESTATIC, programMethod.getReference(), false));
+    ProgramMethod outlinedMethod =
+        ensureOutlineMethod(uniqueContext, method, computedApiLevel, factory, invoke);
+    eventConsumer.acceptOutlinedMethod(outlinedMethod, context);
+    return ImmutableList.of(new CfInvoke(INVOKESTATIC, outlinedMethod.getReference(), false));
   }
 
   private ProgramMethod ensureOutlineMethod(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaringEventConsumer.java
new file mode 100644
index 0000000..873d5e5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaringEventConsumer.java
@@ -0,0 +1,12 @@
+// 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.desugar.apimodel;
+
+import com.android.tools.r8.graph.ProgramMethod;
+
+public interface ApiInvokeOutlinerDesugaringEventConsumer {
+
+  void acceptOutlinedMethod(ProgramMethod outlinedMethod, ProgramMethod context);
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineDuplicateMethodTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineDuplicateMethodTest.java
index 2e96c90..31f5bda 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineDuplicateMethodTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineDuplicateMethodTest.java
@@ -16,6 +16,7 @@
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
@@ -25,6 +26,7 @@
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.lang.reflect.Method;
@@ -62,6 +64,8 @@
         .apply(setMockApiLevelForClass(LibraryClass.class, classApiLevel))
         .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, classApiLevel))
         .apply(setMockApiLevelForMethod(addedOn23(), methodApiLevel))
+        // TODO(b/213552119): Remove when enabled by default.
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .apply(ApiModelingTestHelper::enableOutliningOfMethods)
         .apply(ApiModelingTestHelper::disableStubbingOfClasses);
   }
@@ -72,19 +76,34 @@
   }
 
   @Test
-  public void testD8() throws Exception {
+  public void testD8Debug() throws Exception {
     // TODO(b/197078995): Make this work on 12+.
     assumeTrue(
         parameters.isDexRuntime()
             && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
     testForD8()
+        .setMode(CompilationMode.DEBUG)
         .apply(this::setupTestBuilder)
         .compile()
         .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
         .apply(this::checkOutput)
-        // TODO(b/213552119): Assert that we did not outline any methods.
-        .inspect(ApiModelingTestHelper::assertNoSynthesizedClasses);
+        .inspect(this::inspect);
+  }
+
+  @Test
+  public void testD8Release() throws Exception {
+    assumeFalse(
+        parameters.isCfRuntime()
+            || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
+    testForD8()
+        .setMode(CompilationMode.RELEASE)
+        .apply(this::setupTestBuilder)
+        .compile()
+        .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput)
+        .inspect(this::inspect);
   }
 
   @Test
@@ -101,47 +120,44 @@
         .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
         .apply(this::checkOutput)
-        .inspect(
-            inspector -> {
-              int classCount =
-                  parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(methodApiLevel)
-                      ? 4
-                      : 3;
-              assertEquals(classCount, inspector.allClasses().size());
-              Method testMethod = TestClass.class.getDeclaredMethod("test");
-              verifyThat(inspector, parameters, addedOn23())
-                  .isOutlinedFromUntil(testMethod, methodApiLevel);
-              if (parameters.isDexRuntime()
-                  && parameters.getApiLevel().isLessThan(methodApiLevel)) {
-                // Verify that we invoke the synthesized outline, addedOn23, twice.
-                Optional<FoundMethodSubject> synthesizedAddedOn23 =
-                    inspector.allClasses().stream()
-                        .flatMap(clazz -> clazz.allMethods().stream())
-                        .filter(
-                            methodSubject ->
-                                methodSubject.isSynthetic()
-                                    && invokesMethodWithName("addedOn23").matches(methodSubject))
-                        .findFirst();
-                assertTrue(synthesizedAddedOn23.isPresent());
-                MethodSubject testMethodSubject = inspector.method(testMethod);
-                assertThat(testMethodSubject, isPresent());
-                assertEquals(
-                    2,
-                    testMethodSubject
-                        .streamInstructions()
-                        .filter(
-                            instructionSubject -> {
-                              if (!instructionSubject.isInvoke()) {
-                                return false;
-                              }
-                              return instructionSubject
-                                  .getMethod()
-                                  .asMethodReference()
-                                  .equals(synthesizedAddedOn23.get().asMethodReference());
-                            })
-                        .count());
-              }
-            });
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) throws Exception {
+    int classCount =
+        parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(methodApiLevel) ? 4 : 3;
+    assertEquals(classCount, inspector.allClasses().size());
+    Method testMethod = TestClass.class.getDeclaredMethod("test");
+    verifyThat(inspector, parameters, addedOn23()).isOutlinedFromUntil(testMethod, methodApiLevel);
+    if (parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(methodApiLevel)) {
+      // Verify that we invoke the synthesized outline, addedOn23, twice.
+      Optional<FoundMethodSubject> synthesizedAddedOn23 =
+          inspector.allClasses().stream()
+              .flatMap(clazz -> clazz.allMethods().stream())
+              .filter(
+                  methodSubject ->
+                      methodSubject.isSynthetic()
+                          && invokesMethodWithName("addedOn23").matches(methodSubject))
+              .findFirst();
+      assertTrue(synthesizedAddedOn23.isPresent());
+      MethodSubject testMethodSubject = inspector.method(testMethod);
+      assertThat(testMethodSubject, isPresent());
+      assertEquals(
+          2,
+          testMethodSubject
+              .streamInstructions()
+              .filter(
+                  instructionSubject -> {
+                    if (!instructionSubject.isInvoke()) {
+                      return false;
+                    }
+                    return instructionSubject
+                        .getMethod()
+                        .asMethodReference()
+                        .equals(synthesizedAddedOn23.get().asMethodReference());
+                  })
+              .count());
+    }
   }
 
   private void checkOutput(SingleTestRunResult<?> runResult) {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineHorizontalMergingTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineHorizontalMergingTest.java
index 497e0fd..d00efb9 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineHorizontalMergingTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineHorizontalMergingTest.java
@@ -13,6 +13,7 @@
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
@@ -22,6 +23,7 @@
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -72,6 +74,8 @@
         .apply(
             setMockApiLevelForMethod(
                 OtherLibraryClass.class.getMethod("addedOn27"), secondMethodApiLevel))
+        // TODO(b/213552119): Remove when enabled by default.
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .apply(ApiModelingTestHelper::enableOutliningOfMethods)
         .apply(ApiModelingTestHelper::disableStubbingOfClasses);
   }
@@ -85,12 +89,13 @@
   }
 
   @Test
-  public void testD8() throws Exception {
+  public void testD8Debug() throws Exception {
     // TODO(b/197078995): Make this work on 12+.
     assumeTrue(
         parameters.isDexRuntime()
             && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
     testForD8(parameters.getBackend())
+        .setMode(CompilationMode.DEBUG)
         .apply(this::setupTestBuilder)
         .compile()
         .applyIf(
@@ -98,8 +103,49 @@
             b -> b.addBootClasspathClasses(LibraryClass.class, OtherLibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
         .apply(this::checkOutput)
-        // TODO(b/213552119): Assert that we did not outline any methods.
-        .inspect(ApiModelingTestHelper::assertNoSynthesizedClasses);
+        .inspect(
+            inspector -> {
+              // TODO(b/187675788): Update when horizontal merging is enabled for D8.
+              if (parameters.getApiLevel().isLessThan(firstMethodApiLevel)) {
+                // We have generated 4 outlines two having api level 23 and two having api level 27.
+                assertEquals(7, inspector.allClasses().size());
+              } else if (parameters.getApiLevel().isLessThan(secondMethodApiLevel)) {
+                assertEquals(5, inspector.allClasses().size());
+              } else {
+                // No outlining on this api level.
+                assertEquals(3, inspector.allClasses().size());
+              }
+            });
+  }
+
+  @Test
+  public void testD8Release() throws Exception {
+    // TODO(b/197078995): Make this work on 12+.
+    assumeFalse(
+        parameters.isCfRuntime()
+            || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
+    testForD8(parameters.getBackend())
+        .setMode(CompilationMode.RELEASE)
+        .apply(this::setupTestBuilder)
+        .compile()
+        .applyIf(
+            addToBootClasspath(),
+            b -> b.addBootClasspathClasses(LibraryClass.class, OtherLibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput)
+        .inspect(
+            inspector -> {
+              // TODO(b/187675788): Update when horizontal merging is enabled for D8.
+              if (parameters.getApiLevel().isLessThan(firstMethodApiLevel)) {
+                // We have generated 4 outlines two having api level 23 and two having api level 27.
+                assertEquals(7, inspector.allClasses().size());
+              } else if (parameters.getApiLevel().isLessThan(secondMethodApiLevel)) {
+                assertEquals(5, inspector.allClasses().size());
+              } else {
+                // No outlining on this api level.
+                assertEquals(3, inspector.allClasses().size());
+              }
+            });
   }
 
   @Test
@@ -118,64 +164,64 @@
             b -> b.addBootClasspathClasses(LibraryClass.class, OtherLibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
         .apply(this::checkOutput)
-        .inspect(
-            inspector -> {
-              // No need to check further on CF.
-              List<FoundMethodSubject> outlinedAddedOn23 =
-                  inspector.allClasses().stream()
-                      .flatMap(clazz -> clazz.allMethods().stream())
-                      .filter(
-                          methodSubject ->
-                              methodSubject.isSynthetic()
-                                  && invokesMethodWithName("addedOn23").matches(methodSubject))
-                      .collect(Collectors.toList());
-              List<FoundMethodSubject> outlinedAddedOn27 =
-                  inspector.allClasses().stream()
-                      .flatMap(clazz -> clazz.allMethods().stream())
-                      .filter(
-                          methodSubject ->
-                              methodSubject.isSynthetic()
-                                  && invokesMethodWithName("addedOn27").matches(methodSubject))
-                      .collect(Collectors.toList());
-              if (parameters.isCfRuntime()) {
-                assertTrue(outlinedAddedOn23.isEmpty());
-                assertTrue(outlinedAddedOn27.isEmpty());
-                assertEquals(3, inspector.allClasses().size());
-              } else if (parameters.getApiLevel().isLessThan(firstMethodApiLevel)) {
-                // We have generated 4 outlines two having api level 23 and two having api level 27.
-                // Check that the levels are horizontally merged.
-                assertEquals(5, inspector.allClasses().size());
-                assertEquals(2, outlinedAddedOn23.size());
-                assertTrue(
-                    outlinedAddedOn23.stream()
-                        .allMatch(
-                            outline ->
-                                outline.getMethod().getHolderType()
-                                    == outlinedAddedOn23.get(0).getMethod().getHolderType()));
-                assertEquals(2, outlinedAddedOn27.size());
-                assertTrue(
-                    outlinedAddedOn27.stream()
-                        .allMatch(
-                            outline ->
-                                outline.getMethod().getHolderType()
-                                    == outlinedAddedOn27.get(0).getMethod().getHolderType()));
-              } else if (parameters.getApiLevel().isLessThan(secondMethodApiLevel)) {
-                assertTrue(outlinedAddedOn23.isEmpty());
-                assertEquals(4, inspector.allClasses().size());
-                assertEquals(2, outlinedAddedOn27.size());
-                assertTrue(
-                    outlinedAddedOn27.stream()
-                        .allMatch(
-                            outline ->
-                                outline.getMethod().getHolderType()
-                                    == outlinedAddedOn27.get(0).getMethod().getHolderType()));
-              } else {
-                // No outlining on this api level.
-                assertTrue(outlinedAddedOn23.isEmpty());
-                assertTrue(outlinedAddedOn27.isEmpty());
-                assertEquals(3, inspector.allClasses().size());
-              }
-            });
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    List<FoundMethodSubject> outlinedAddedOn23 =
+        inspector.allClasses().stream()
+            .flatMap(clazz -> clazz.allMethods().stream())
+            .filter(
+                methodSubject ->
+                    methodSubject.isSynthetic()
+                        && invokesMethodWithName("addedOn23").matches(methodSubject))
+            .collect(Collectors.toList());
+    List<FoundMethodSubject> outlinedAddedOn27 =
+        inspector.allClasses().stream()
+            .flatMap(clazz -> clazz.allMethods().stream())
+            .filter(
+                methodSubject ->
+                    methodSubject.isSynthetic()
+                        && invokesMethodWithName("addedOn27").matches(methodSubject))
+            .collect(Collectors.toList());
+    if (parameters.isCfRuntime()) {
+      assertTrue(outlinedAddedOn23.isEmpty());
+      assertTrue(outlinedAddedOn27.isEmpty());
+      assertEquals(3, inspector.allClasses().size());
+    } else if (parameters.getApiLevel().isLessThan(firstMethodApiLevel)) {
+      // We have generated 4 outlines two having api level 23 and two having api level 27.
+      // Check that the levels are horizontally merged.
+      assertEquals(5, inspector.allClasses().size());
+      assertEquals(2, outlinedAddedOn23.size());
+      assertTrue(
+          outlinedAddedOn23.stream()
+              .allMatch(
+                  outline ->
+                      outline.getMethod().getHolderType()
+                          == outlinedAddedOn23.get(0).getMethod().getHolderType()));
+      assertEquals(2, outlinedAddedOn27.size());
+      assertTrue(
+          outlinedAddedOn27.stream()
+              .allMatch(
+                  outline ->
+                      outline.getMethod().getHolderType()
+                          == outlinedAddedOn27.get(0).getMethod().getHolderType()));
+    } else if (parameters.getApiLevel().isLessThan(secondMethodApiLevel)) {
+      assertTrue(outlinedAddedOn23.isEmpty());
+      assertEquals(4, inspector.allClasses().size());
+      assertEquals(2, outlinedAddedOn27.size());
+      assertTrue(
+          outlinedAddedOn27.stream()
+              .allMatch(
+                  outline ->
+                      outline.getMethod().getHolderType()
+                          == outlinedAddedOn27.get(0).getMethod().getHolderType()));
+    } else {
+      // No outlining on this api level.
+      assertTrue(outlinedAddedOn23.isEmpty());
+      assertTrue(outlinedAddedOn27.isEmpty());
+      assertEquals(3, inspector.allClasses().size());
+    }
   }
 
   private void checkOutput(SingleTestRunResult<?> runResult) {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodAndStubClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodAndStubClassTest.java
index 89c2f76..2411e94 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodAndStubClassTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodAndStubClassTest.java
@@ -11,6 +11,7 @@
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestCompilerBuilder;
@@ -20,6 +21,7 @@
 import com.android.tools.r8.apimodel.ApiModelMockClassTest.TestClass;
 import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.lang.reflect.Method;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -51,6 +53,8 @@
         .addDefaultRuntimeLibrary(parameters)
         .setMinApi(parameters.getApiLevel())
         .addAndroidBuildVersion()
+        // TODO(b/213552119): Remove when enabled by default.
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .apply(ApiModelingTestHelper::enableStubbingOfClasses)
         .apply(ApiModelingTestHelper::enableOutliningOfMethods)
         .apply(setMockApiLevelForClass(LibraryClass.class, libraryClassLevel))
@@ -64,19 +68,35 @@
   }
 
   @Test
-  public void testD8() throws Exception {
+  public void testD8Debug() throws Exception {
     // TODO(b/197078995): Make this work on 12+.
     assumeTrue(
         parameters.isDexRuntime()
             && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
     testForD8(parameters.getBackend())
+        .setMode(CompilationMode.DEBUG)
         .apply(this::setupTestBuilder)
         .compile()
         .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
         .apply(this::checkOutput)
-        // TODO(b/213552119): Assert that we did not outline any methods.
-        .inspect(ApiModelingTestHelper::assertNoSynthesizedClasses);
+        .inspect(this::inspect);
+  }
+
+  @Test
+  public void testD8Release() throws Exception {
+    // TODO(b/197078995): Make this work on 12+.
+    assumeFalse(
+        parameters.isCfRuntime()
+            || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
+    testForD8(parameters.getBackend())
+        .setMode(CompilationMode.RELEASE)
+        .apply(this::setupTestBuilder)
+        .compile()
+        .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput)
+        .inspect(this::inspect);
   }
 
   @Test
@@ -92,13 +112,14 @@
         .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
         .apply(this::checkOutput)
-        .inspect(
-            inspector -> {
-              verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(libraryClassLevel);
-              verifyThat(inspector, parameters, apiMethod())
-                  .isOutlinedFromUntil(
-                      Main.class.getDeclaredMethod("main", String[].class), libraryMethodLevel);
-            });
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) throws Exception {
+    verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(libraryClassLevel);
+    verifyThat(inspector, parameters, apiMethod())
+        .isOutlinedFromUntil(
+            Main.class.getDeclaredMethod("main", String[].class), libraryMethodLevel);
   }
 
   private void checkOutput(SingleTestRunResult<?> runResult) {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodMissingClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodMissingClassTest.java
index 8ee8ad4..cf440c9 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodMissingClassTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodMissingClassTest.java
@@ -16,6 +16,7 @@
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
@@ -25,6 +26,7 @@
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.lang.reflect.Method;
@@ -69,6 +71,8 @@
                 LibraryClass.class, initialLibraryMockLevel))
         .apply(setMockApiLevelForMethod(addedOn23(), initialLibraryMockLevel))
         .apply(setMockApiLevelForMethod(addedOn27(), finalLibraryMethodLevel))
+        // TODO(b/213552119): Remove when enabled by default.
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .apply(ApiModelingTestHelper::enableOutliningOfMethods)
         .apply(ApiModelingTestHelper::disableStubbingOfClasses);
   }
@@ -82,19 +86,34 @@
   }
 
   @Test
-  public void testD8() throws Exception {
+  public void testD8Debug() throws Exception {
     // TODO(b/197078995): Make this work on 12+.
     assumeTrue(
         parameters.isDexRuntime()
             && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
     testForD8(parameters.getBackend())
+        .setMode(CompilationMode.DEBUG)
         .apply(this::setupTestBuilder)
         .compile()
         .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
         .apply(this::checkOutput)
-        // TODO(b/213552119): Assert that we did not outline any methods.
-        .inspect(ApiModelingTestHelper::assertNoSynthesizedClasses);
+        .inspect(this::inspect);
+  }
+
+  @Test
+  public void testD8Release() throws Exception {
+    assumeFalse(
+        parameters.isCfRuntime()
+            || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
+    testForD8(parameters.getBackend())
+        .setMode(CompilationMode.RELEASE)
+        .apply(this::setupTestBuilder)
+        .compile()
+        .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput)
+        .inspect(this::inspect);
   }
 
   @Test
@@ -111,40 +130,37 @@
         .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
         .apply(this::checkOutput)
-        .inspect(
-            inspector -> {
-              // No need to check further on CF.
-              if (parameters.isCfRuntime()) {
-                assertEquals(3, inspector.allClasses().size());
-                return;
-              }
-              Method testMethod = TestClass.class.getDeclaredMethod("test");
-              MethodSubject testMethodSubject = inspector.method(testMethod);
-              assertThat(testMethodSubject, isPresent());
-              Optional<FoundMethodSubject> synthesizedMissingNotReferenced =
-                  inspector.allClasses().stream()
-                      .flatMap(clazz -> clazz.allMethods().stream())
-                      .filter(
-                          methodSubject ->
-                              methodSubject.isSynthetic()
-                                  && invokesMethodWithName("missingNotReferenced")
-                                      .matches(methodSubject))
-                      .findFirst();
-              assertFalse(synthesizedMissingNotReferenced.isPresent());
-              verifyThat(inspector, parameters, addedOn23()).isNotOutlinedFrom(testMethod);
-              verifyThat(inspector, parameters, addedOn27())
-                  .isOutlinedFromUntil(testMethod, finalLibraryMethodLevel);
-              verifyThat(
-                      inspector,
-                      parameters,
-                      LibraryClass.class.getDeclaredMethod("missingAndReferenced"))
-                  .isNotOutlinedFrom(testMethod);
-              if (parameters.getApiLevel().isLessThan(finalLibraryMethodLevel)) {
-                assertEquals(4, inspector.allClasses().size());
-              } else {
-                assertEquals(3, inspector.allClasses().size());
-              }
-            });
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) throws Exception {
+    // No need to check further on CF.
+    if (parameters.isCfRuntime()) {
+      assertEquals(3, inspector.allClasses().size());
+      return;
+    }
+    Method testMethod = TestClass.class.getDeclaredMethod("test");
+    MethodSubject testMethodSubject = inspector.method(testMethod);
+    assertThat(testMethodSubject, isPresent());
+    Optional<FoundMethodSubject> synthesizedMissingNotReferenced =
+        inspector.allClasses().stream()
+            .flatMap(clazz -> clazz.allMethods().stream())
+            .filter(
+                methodSubject ->
+                    methodSubject.isSynthetic()
+                        && invokesMethodWithName("missingNotReferenced").matches(methodSubject))
+            .findFirst();
+    assertFalse(synthesizedMissingNotReferenced.isPresent());
+    verifyThat(inspector, parameters, addedOn23()).isNotOutlinedFrom(testMethod);
+    verifyThat(inspector, parameters, addedOn27())
+        .isOutlinedFromUntil(testMethod, finalLibraryMethodLevel);
+    verifyThat(inspector, parameters, LibraryClass.class.getDeclaredMethod("missingAndReferenced"))
+        .isNotOutlinedFrom(testMethod);
+    if (parameters.getApiLevel().isLessThan(finalLibraryMethodLevel)) {
+      assertEquals(4, inspector.allClasses().size());
+    } else {
+      assertEquals(3, inspector.allClasses().size());
+    }
   }
 
   private void checkOutput(SingleTestRunResult<?> runResult) {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodProtectedTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodProtectedTest.java
index 4428a33..d63e01b 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodProtectedTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodProtectedTest.java
@@ -10,6 +10,7 @@
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.SingleTestRunResult;
@@ -73,6 +74,8 @@
                 .transform())
         .setMinApi(AndroidApiLevel.B)
         .addAndroidBuildVersion(runApiLevel())
+        // TODO(b/213552119): Remove when enabled by default.
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .apply(setMockApiLevelForClass(LibraryClass.class, classApiLevel))
         .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, classApiLevel))
         .apply(
@@ -89,12 +92,28 @@
   }
 
   @Test
-  public void testD8() throws Exception {
+  public void testD8Debug() throws Exception {
     // TODO(b/197078995): Make this work on 12+.
     assumeTrue(
         parameters.isDexRuntime()
             && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
     testForD8()
+        .setMode(CompilationMode.DEBUG)
+        .apply(this::setupTestBuilder)
+        .compile()
+        .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testD8Release() throws Exception {
+    // TODO(b/197078995): Make this work on 12+.
+    assumeFalse(
+        parameters.isCfRuntime()
+            || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
+    testForD8()
+        .setMode(CompilationMode.RELEASE)
         .apply(this::setupTestBuilder)
         .compile()
         .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodUnknownTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodUnknownTest.java
index 2461448..940864c 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodUnknownTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodUnknownTest.java
@@ -8,6 +8,7 @@
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestCompilerBuilder;
@@ -42,6 +43,8 @@
         .setMinApi(parameters.getApiLevel())
         .addAndroidBuildVersion()
         .apply(setMockApiLevelForClass(LibraryClass.class, classApiLevel))
+        // TODO(b/213552119): Remove when enabled by default.
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .apply(ApiModelingTestHelper::enableOutliningOfMethods)
         .apply(ApiModelingTestHelper::disableStubbingOfClasses);
   }
@@ -52,12 +55,30 @@
   }
 
   @Test
-  public void testD8() throws Exception {
+  public void testD8Debug() throws Exception {
     // TODO(b/197078995): Make this work on 12+.
     assumeTrue(
         parameters.isDexRuntime()
             && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
     testForD8()
+        .setMode(CompilationMode.DEBUG)
+        .apply(this::setupTestBuilder)
+        .compile()
+        // Assert that we did not outline any methods.
+        .inspect(ApiModelingTestHelper::assertNoSynthesizedClasses)
+        .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testD8Release() throws Exception {
+    // TODO(b/197078995): Make this work on 12+.
+    assumeFalse(
+        parameters.isCfRuntime()
+            || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
+    testForD8()
+        .setMode(CompilationMode.RELEASE)
         .apply(this::setupTestBuilder)
         .compile()
         // Assert that we did not outline any methods.
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlinePackagePrivateTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlinePackagePrivateTest.java
index 9e8115d..94ae936 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlinePackagePrivateTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlinePackagePrivateTest.java
@@ -9,6 +9,7 @@
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.D8TestCompileResult;
 import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
@@ -83,12 +84,31 @@
         .apply(
             setMockApiLevelForMethod(
                 LibraryClass.class.getDeclaredMethod("addedOn10"), methodApiLevel))
+        // TODO(b/213552119): Remove when enabled by default.
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .apply(ApiModelingTestHelper::enableOutliningOfMethods)
         .apply(ApiModelingTestHelper::disableStubbingOfClasses);
   }
 
   @Test
-  public void testD8() throws Exception {
+  public void testD8Debug() throws Exception {
+    // TODO(b/197078995): Make this work on 12+.
+    assumeTrue(
+        parameters.isDexRuntime()
+            && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
+    testForD8()
+        .setMode(CompilationMode.DEBUG)
+        .apply(this::setupTestBuilder)
+        .compile()
+        // Assert that we did not outline any methods.
+        .inspect(ApiModelingTestHelper::assertNoSynthesizedClasses)
+        .applyIf(willInvokeLibraryMethods(), b -> b.addBootClasspathClasses(LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkResultOnBootClassPath);
+  }
+
+  @Test
+  public void testD8Release() throws Exception {
     // TODO(b/197078995): Make this work on 12+.
     assumeTrue(
         parameters.isDexRuntime()