API for passing a human-readable ART profile as a startup profile

Change-Id: I5ea336d3551c91be6fb4c10fbb69231d16166c96
diff --git a/src/main/java/com/android/tools/r8/TextInputStream.java b/src/main/java/com/android/tools/r8/TextInputStream.java
new file mode 100644
index 0000000..433df09
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/TextInputStream.java
@@ -0,0 +1,16 @@
+// 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;
+
+import java.io.InputStream;
+import java.nio.charset.Charset;
+
+@Keep
+public interface TextInputStream {
+
+  InputStream getInputStream();
+
+  Charset getCharset();
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfile.java b/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfile.java
index a0d6204..44918e0 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfile.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/profile/StartupProfile.java
@@ -4,7 +4,10 @@
 
 package com.android.tools.r8.experimental.startup.profile;
 
+import com.android.tools.r8.TextInputStream;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.startup.HumanReadableARTProfileParserBuilder;
 import com.android.tools.r8.startup.StartupClassBuilder;
 import com.android.tools.r8.startup.StartupMethodBuilder;
 import com.android.tools.r8.startup.StartupProfileBuilder;
@@ -100,6 +103,13 @@
       return addStartupItem(syntheticStartupMethodBuilder.build());
     }
 
+    @Override
+    public StartupProfileBuilder addHumanReadableARTProfile(
+        TextInputStream textInputStream,
+        Consumer<HumanReadableARTProfileParserBuilder> parserBuilderConsumer) {
+      throw new Unimplemented();
+    }
+
     private Builder addStartupItem(StartupItem startupItem) {
       this.startupItemsBuilder.add(startupItem);
       return this;
diff --git a/src/main/java/com/android/tools/r8/startup/ARTProfileClassRuleInfo.java b/src/main/java/com/android/tools/r8/startup/ARTProfileClassRuleInfo.java
new file mode 100644
index 0000000..59b089c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/startup/ARTProfileClassRuleInfo.java
@@ -0,0 +1,10 @@
+// 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.startup;
+
+import com.android.tools.r8.Keep;
+
+@Keep
+public interface ARTProfileClassRuleInfo {}
diff --git a/src/main/java/com/android/tools/r8/startup/ARTProfileMethodRuleInfo.java b/src/main/java/com/android/tools/r8/startup/ARTProfileMethodRuleInfo.java
new file mode 100644
index 0000000..02bb118
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/startup/ARTProfileMethodRuleInfo.java
@@ -0,0 +1,20 @@
+// 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.startup;
+
+import com.android.tools.r8.Keep;
+
+@Keep
+public interface ARTProfileMethodRuleInfo {
+
+  /** Returns true if this method rule method rule is flagged as hot ('H'). */
+  boolean isHot();
+
+  /** Returns true if this method rule method rule is flagged as startup ('S'). */
+  boolean isStartup();
+
+  /** Returns true if this method rule method rule is flagged as post-startup ('P'). */
+  boolean isPostStartup();
+}
diff --git a/src/main/java/com/android/tools/r8/startup/ARTProfileRulePredicate.java b/src/main/java/com/android/tools/r8/startup/ARTProfileRulePredicate.java
new file mode 100644
index 0000000..b99ca35
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/startup/ARTProfileRulePredicate.java
@@ -0,0 +1,17 @@
+// 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.startup;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+
+@Keep
+public interface ARTProfileRulePredicate {
+
+  boolean testClassRule(ClassReference classReference, ARTProfileClassRuleInfo classRuleInfo);
+
+  boolean testMethodRule(MethodReference methodReference, ARTProfileMethodRuleInfo methodRuleInfo);
+}
diff --git a/src/main/java/com/android/tools/r8/startup/HumanReadableARTProfileParserBuilder.java b/src/main/java/com/android/tools/r8/startup/HumanReadableARTProfileParserBuilder.java
new file mode 100644
index 0000000..6f54ccc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/startup/HumanReadableARTProfileParserBuilder.java
@@ -0,0 +1,24 @@
+// 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.startup;
+
+import com.android.tools.r8.Keep;
+
+/**
+ * A builder for configuring a parser for the human-readable ART profile format.
+ *
+ * @see <a href="https://developer.android.com/topic/performance/baselineprofiles">ART Baseline
+ *     Profiles</a>
+ */
+@Keep
+public interface HumanReadableARTProfileParserBuilder {
+
+  /**
+   * Only include rules from the ART profile that satisfies the given {@param rulePredicate}.
+   *
+   * <p>By default, all rules from the ART profile are included.
+   */
+  HumanReadableARTProfileParserBuilder setRulePredicate(ARTProfileRulePredicate rulePredicate);
+}
diff --git a/src/main/java/com/android/tools/r8/startup/StartupProfileBuilder.java b/src/main/java/com/android/tools/r8/startup/StartupProfileBuilder.java
index d3626a1..85e3f15 100644
--- a/src/main/java/com/android/tools/r8/startup/StartupProfileBuilder.java
+++ b/src/main/java/com/android/tools/r8/startup/StartupProfileBuilder.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.startup;
 
 import com.android.tools.r8.Keep;
+import com.android.tools.r8.TextInputStream;
 import java.util.function.Consumer;
 
 /** Interface for providing a startup profile to the compiler. */
@@ -40,4 +41,15 @@
    */
   StartupProfileBuilder addSyntheticStartupMethod(
       Consumer<SyntheticStartupMethodBuilder> syntheticStartupMethodBuilderConsumer);
+
+  /**
+   * Adds the rules from the given human-readable ART profile to the startup profile and then closes
+   * the stream.
+   *
+   * @see <a href="https://developer.android.com/topic/performance/baselineprofiles">ART Baseline
+   *     Profiles</a>
+   */
+  StartupProfileBuilder addHumanReadableARTProfile(
+      TextInputStream textInputStream,
+      Consumer<HumanReadableARTProfileParserBuilder> parserBuilderConsumer);
 }
diff --git a/src/test/java/com/android/tools/r8/compilerapi/startupprofile/StartupProfileApiTest.java b/src/test/java/com/android/tools/r8/compilerapi/startupprofile/StartupProfileApiTest.java
index b6572fc..2ea14dc 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/startupprofile/StartupProfileApiTest.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/startupprofile/StartupProfileApiTest.java
@@ -5,7 +5,10 @@
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.D8;
 import com.android.tools.r8.D8Command;
 import com.android.tools.r8.DexIndexedConsumer;
@@ -16,14 +19,24 @@
 import com.android.tools.r8.compilerapi.CompilerApiTest;
 import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.startup.ARTProfileClassRuleInfo;
+import com.android.tools.r8.startup.ARTProfileMethodRuleInfo;
+import com.android.tools.r8.startup.ARTProfileRulePredicate;
 import com.android.tools.r8.startup.StartupProfileBuilder;
 import com.android.tools.r8.startup.StartupProfileProvider;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
 import java.nio.file.Path;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.List;
 import java.util.function.BiConsumer;
 import org.junit.Test;
 
@@ -40,7 +53,7 @@
     return ApiTest.class;
   }
 
-  @Test
+  @Test(expected = CompilationFailedException.class)
   public void testD8ArrayApi() throws Exception {
     ApiTest test = new ApiTest(ApiTest.PARAMETERS);
     runTest(
@@ -49,7 +62,7 @@
             test.runD8(ApiTest::addStartupProfileProviderUsingArrayApi, programConsumer));
   }
 
-  @Test
+  @Test(expected = CompilationFailedException.class)
   public void testD8CollectionApi() throws Exception {
     ApiTest test = new ApiTest(ApiTest.PARAMETERS);
     runTest(
@@ -58,7 +71,7 @@
             test.runD8(ApiTest::addStartupProfileProviderUsingCollectionApi, programConsumer));
   }
 
-  @Test
+  @Test(expected = CompilationFailedException.class)
   public void testR8ArrayApi() throws Exception {
     ApiTest test = new ApiTest(ApiTest.PARAMETERS);
     runTest(
@@ -67,7 +80,7 @@
             test.runR8(ApiTest::addStartupProfileProviderUsingArrayApi, programConsumer));
   }
 
-  @Test
+  @Test(expected = CompilationFailedException.class)
   public void testR8CollectionApi() throws Exception {
     ApiTest test = new ApiTest(ApiTest.PARAMETERS);
     runTest(
@@ -98,9 +111,36 @@
 
         @Override
         public void getStartupProfile(StartupProfileBuilder startupProfileBuilder) {
-          startupProfileBuilder.addStartupClass(
-              startupClassBuilder ->
-                  startupClassBuilder.setClassReference(Reference.classFromClass(getMockClass())));
+          // Create human-readable ART startup profile.
+          ClassReference mockClassReference = Reference.classFromClass(getMockClass());
+          ClosableByteArrayInputStream inputStream =
+              new ClosableByteArrayInputStream(mockClassReference.getDescriptor().getBytes());
+
+          // Create parser and parse ART profile.
+          List<ClassReference> seenClasses = new ArrayList<>();
+          startupProfileBuilder.addHumanReadableARTProfile(
+              new InputStreamReader(inputStream),
+              parserBuilder ->
+                  parserBuilder.setRulePredicate(
+                      new ARTProfileRulePredicate() {
+                        @Override
+                        public boolean testClassRule(
+                            ClassReference reference, ARTProfileClassRuleInfo classRuleInfo) {
+                          seenClasses.add(reference);
+                          return true;
+                        }
+
+                        @Override
+                        public boolean testMethodRule(
+                            MethodReference reference, ARTProfileMethodRuleInfo methodRuleInfo) {
+                          return true;
+                        }
+                      }));
+
+          // Verify rule predicate has been used and input stream is closed.
+          assertEquals(1, seenClasses.size());
+          assertEquals(mockClassReference, seenClasses.get(0));
+          assertTrue(inputStream.isClosed());
         }
 
         @Override
@@ -142,7 +182,7 @@
       R8.run(commandBuilder.build());
     }
 
-    @Test
+    @Test(expected = CompilationFailedException.class)
     public void testD8ArrayApi() throws Exception {
       runD8(ApiTest::addStartupProfileProviderUsingArrayApi, DexIndexedConsumer.emptyConsumer());
     }
@@ -154,7 +194,7 @@
       commandBuilder.addStartupProfileProviders(startupProfileProviders);
     }
 
-    @Test
+    @Test(expected = CompilationFailedException.class)
     public void testD8CollectionApi() throws Exception {
       runD8(
           ApiTest::addStartupProfileProviderUsingCollectionApi, DexIndexedConsumer.emptyConsumer());
@@ -167,7 +207,7 @@
       commandBuilder.addStartupProfileProviders(startupProfileProviders);
     }
 
-    @Test
+    @Test(expected = CompilationFailedException.class)
     public void testR8ArrayApi() throws Exception {
       runR8(ApiTest::addStartupProfileProviderUsingArrayApi, DexIndexedConsumer.emptyConsumer());
     }
@@ -179,7 +219,7 @@
       commandBuilder.addStartupProfileProviders(startupProfileProviders);
     }
 
-    @Test
+    @Test(expected = CompilationFailedException.class)
     public void testR8CollectionApi() throws Exception {
       runR8(
           ApiTest::addStartupProfileProviderUsingCollectionApi, DexIndexedConsumer.emptyConsumer());
@@ -191,5 +231,24 @@
           Collections.singleton(startupProfileProvider);
       commandBuilder.addStartupProfileProviders(startupProfileProviders);
     }
+
+    private static class ClosableByteArrayInputStream extends ByteArrayInputStream {
+
+      private boolean closed;
+
+      public ClosableByteArrayInputStream(byte[] buf) {
+        super(buf);
+      }
+
+      @Override
+      public void close() throws IOException {
+        super.close();
+        closed = true;
+      }
+
+      public boolean isClosed() {
+        return closed;
+      }
+    }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java b/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java
index a9ca8c5..988d188 100644
--- a/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java
+++ b/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java
@@ -13,10 +13,13 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestCompilerBuilder;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TextInputStream;
 import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.experimental.startup.instrumentation.StartupInstrumentationOptions;
 import com.android.tools.r8.experimental.startup.profile.StartupProfileParser;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.startup.HumanReadableARTProfileParserBuilder;
 import com.android.tools.r8.startup.StartupClassBuilder;
 import com.android.tools.r8.startup.StartupMethodBuilder;
 import com.android.tools.r8.startup.StartupProfileBuilder;
@@ -80,6 +83,13 @@
         startupItemConsumer.accept(syntheticStartupMethodBuilder.build());
         return this;
       }
+
+      @Override
+      public StartupProfileBuilder addHumanReadableARTProfile(
+          TextInputStream textInputStream,
+          Consumer<HumanReadableARTProfileParserBuilder> parserBuilderConsumer) {
+        throw new Unimplemented();
+      }
     };
   }
 
diff --git a/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1 b/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1
index 4753c76..f85515d 100644
--- a/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1
+++ b/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1
@@ -1 +1 @@
-46ac3bedbdba8732e48d13fcc94967f43ae1d91b
\ No newline at end of file
+c750aa168121238836d3ef622949611f3d97f66a
\ No newline at end of file