Add support for accepting art profile in CLI
Change-Id: I82864f1f096969d382d1daeb822071762add289e
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java b/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
index 1ba0b22..7599bb6 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
@@ -20,6 +20,7 @@
public class BaseCompilerCommandParser<
C extends BaseCompilerCommand, B extends BaseCompilerCommand.Builder<C, B>> {
+ protected static final String ART_PROFILE_FLAG = "--art-profile";
protected static final String MIN_API_FLAG = "--min-api";
protected static final String THREAD_COUNT_FLAG = "--thread-count";
protected static final String MAP_DIAGNOSTICS = "--map-diagnostics";
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java
index 0336de9..444f522 100644
--- a/src/main/java/com/android/tools/r8/D8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -8,6 +8,8 @@
import com.android.tools.r8.experimental.startup.StartupProfileProviderUtils;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.profile.art.ArtProfileConsumerUtils;
+import com.android.tools.r8.profile.art.ArtProfileProviderUtils;
import com.android.tools.r8.utils.ExceptionDiagnostic;
import com.android.tools.r8.utils.FlagFile;
import com.android.tools.r8.utils.StringDiagnostic;
@@ -27,7 +29,7 @@
static final String STARTUP_PROFILE_FLAG = "--startup-profile";
- private static final Set<String> OPTIONS_WITH_PARAMETER =
+ private static final Set<String> OPTIONS_WITH_ONE_PARAMETER =
ImmutableSet.of(
"--output",
"--lib",
@@ -41,8 +43,12 @@
"--desugared-lib",
"--desugared-lib-pg-conf-output",
THREAD_COUNT_FLAG,
+ ART_PROFILE_FLAG,
STARTUP_PROFILE_FLAG);
+ // Note: this must be a subset of OPTIONS_WITH_ONE_PARAMETER.
+ private static final Set<String> OPTIONS_WITH_TWO_PARAMETERS = ImmutableSet.of(ART_PROFILE_FLAG);
+
public static List<ParseFlagInfo> getFlags() {
return ImmutableList.<ParseFlagInfo>builder()
.add(ParseFlagInfoImpl.getDebug(true))
@@ -83,6 +89,7 @@
.add(ParseFlagInfoImpl.getThreadCount())
.add(ParseFlagInfoImpl.getMapDiagnostics())
.add(ParseFlagInfoImpl.getAndroidPlatformBuild())
+ .add(ParseFlagInfoImpl.getArtProfile())
.add(ParseFlagInfoImpl.getStartupProfile())
.add(ParseFlagInfoImpl.getVersion("d8"))
.add(ParseFlagInfoImpl.getHelp())
@@ -211,7 +218,8 @@
for (int i = 0; i < expandedArgs.length; i++) {
String arg = expandedArgs[i].trim();
String nextArg = null;
- if (OPTIONS_WITH_PARAMETER.contains(arg)) {
+ String nextNextArg = null;
+ if (OPTIONS_WITH_ONE_PARAMETER.contains(arg)) {
if (++i < expandedArgs.length) {
nextArg = expandedArgs[i];
} else {
@@ -219,6 +227,15 @@
new StringDiagnostic("Missing parameter for " + expandedArgs[i - 1] + ".", origin));
break;
}
+ if (OPTIONS_WITH_TWO_PARAMETERS.contains(arg)) {
+ if (++i < expandedArgs.length) {
+ nextNextArg = expandedArgs[i];
+ } else {
+ builder.error(
+ new StringDiagnostic("Missing parameter for " + expandedArgs[i - 2] + ".", origin));
+ break;
+ }
+ }
}
if (arg.length() == 0) {
continue;
@@ -310,6 +327,12 @@
builder.setDesugaredLibraryKeepRuleConsumer(consumer);
} else if (arg.equals("--android-platform-build")) {
builder.setAndroidPlatformBuild(true);
+ } else if (arg.equals(ART_PROFILE_FLAG)) {
+ Path artProfilePath = Paths.get(nextArg);
+ Path rewrittenArtProfilePath = Paths.get(nextNextArg);
+ builder.addArtProfileForRewriting(
+ ArtProfileProviderUtils.createFromHumanReadableArtProfile(artProfilePath),
+ ArtProfileConsumerUtils.create(rewrittenArtProfilePath));
} else if (arg.equals(STARTUP_PROFILE_FLAG)) {
Path startupProfilePath = Paths.get(nextArg);
builder.addStartupProfileProviders(
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index 94440be..bfe119b 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -163,6 +163,11 @@
}
@Override
+ List<ArtProfileForRewriting> getArtProfilesForRewriting() {
+ return getD8Command().getArtProfilesForRewriting();
+ }
+
+ @Override
InternalOptions getInternalOptions() {
InternalOptions internal = new InternalOptions(factory, getReporter());
assert !internal.debug;
diff --git a/src/main/java/com/android/tools/r8/L8CommandParser.java b/src/main/java/com/android/tools/r8/L8CommandParser.java
index 4603160..af389fe 100644
--- a/src/main/java/com/android/tools/r8/L8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/L8CommandParser.java
@@ -6,6 +6,8 @@
import com.android.tools.r8.D8CommandParser.OrderedClassFileResourceProvider;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.profile.art.ArtProfileConsumerUtils;
+import com.android.tools.r8.profile.art.ArtProfileProviderUtils;
import com.android.tools.r8.utils.FlagFile;
import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.StringUtils;
@@ -18,7 +20,7 @@
public class L8CommandParser extends BaseCompilerCommandParser<L8Command, L8Command.Builder> {
- private static final Set<String> OPTIONS_WITH_PARAMETER =
+ private static final Set<String> OPTIONS_WITH_ONE_PARAMETER =
ImmutableSet.of(
"--output",
"--lib",
@@ -26,7 +28,11 @@
"--desugared-lib",
THREAD_COUNT_FLAG,
"--pg-conf",
- "--pg-map-output");
+ "--pg-map-output",
+ ART_PROFILE_FLAG);
+
+ // Note: this must be a subset of OPTIONS_WITH_ONE_PARAMETER.
+ private static final Set<String> OPTIONS_WITH_TWO_PARAMETERS = ImmutableSet.of(ART_PROFILE_FLAG);
public static void main(String[] args) throws CompilationFailedException {
L8Command command = parse(args, Origin.root()).build();
@@ -62,6 +68,7 @@
.addAll(ParseFlagInfoImpl.getAssertionsFlags())
.add(ParseFlagInfoImpl.getThreadCount())
.add(ParseFlagInfoImpl.getMapDiagnostics())
+ .add(ParseFlagInfoImpl.getArtProfile())
.add(ParseFlagInfoImpl.getVersion("l8"))
.add(ParseFlagInfoImpl.getHelp())
.build();
@@ -105,7 +112,8 @@
for (int i = 0; i < expandedArgs.length; i++) {
String arg = expandedArgs[i].trim();
String nextArg = null;
- if (OPTIONS_WITH_PARAMETER.contains(arg)) {
+ String nextNextArg = null;
+ if (OPTIONS_WITH_ONE_PARAMETER.contains(arg)) {
if (++i < expandedArgs.length) {
nextArg = expandedArgs[i];
} else {
@@ -113,6 +121,15 @@
new StringDiagnostic("Missing parameter for " + expandedArgs[i - 1] + ".", origin));
break;
}
+ if (OPTIONS_WITH_TWO_PARAMETERS.contains(arg)) {
+ if (++i < expandedArgs.length) {
+ nextNextArg = expandedArgs[i];
+ } else {
+ builder.error(
+ new StringDiagnostic("Missing parameter for " + expandedArgs[i - 2] + ".", origin));
+ break;
+ }
+ }
}
if (arg.length() == 0) {
continue;
@@ -162,6 +179,12 @@
builder.addDesugaredLibraryConfiguration(StringResource.fromFile(Paths.get(nextArg)));
} else if (arg.equals("--classfile")) {
outputMode = OutputMode.ClassFile;
+ } else if (arg.equals(ART_PROFILE_FLAG)) {
+ Path artProfilePath = Paths.get(nextArg);
+ Path rewrittenArtProfilePath = Paths.get(nextNextArg);
+ builder.addArtProfileForRewriting(
+ ArtProfileProviderUtils.createFromHumanReadableArtProfile(artProfilePath),
+ ArtProfileConsumerUtils.create(rewrittenArtProfilePath));
} else if (arg.equals(THREAD_COUNT_FLAG)) {
parsePositiveIntArgument(
builder::error, THREAD_COUNT_FLAG, nextArg, origin, builder::setThreadCount);
diff --git a/src/main/java/com/android/tools/r8/ParseFlagInfoImpl.java b/src/main/java/com/android/tools/r8/ParseFlagInfoImpl.java
index 595a0b0..ef42ebd 100644
--- a/src/main/java/com/android/tools/r8/ParseFlagInfoImpl.java
+++ b/src/main/java/com/android/tools/r8/ParseFlagInfoImpl.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
+import static com.android.tools.r8.BaseCompilerCommandParser.ART_PROFILE_FLAG;
import static com.android.tools.r8.BaseCompilerCommandParser.MAP_DIAGNOSTICS;
import static com.android.tools.r8.BaseCompilerCommandParser.MIN_API_FLAG;
import static com.android.tools.r8.BaseCompilerCommandParser.THREAD_COUNT_FLAG;
@@ -164,6 +165,14 @@
"is assumed to be the version specified by --min-api.");
}
+ public static ParseFlagInfoImpl getArtProfile() {
+ return flag2(
+ ART_PROFILE_FLAG,
+ "<input>",
+ "<output>",
+ "Rewrite human readable ART profile read from <input> and write to <output>.");
+ }
+
public static ParseFlagInfoImpl getStartupProfile() {
return flag1(STARTUP_PROFILE_FLAG, "<file>", "Startup profile <file> to use for dex layout.");
}
diff --git a/src/main/java/com/android/tools/r8/R8CommandParser.java b/src/main/java/com/android/tools/r8/R8CommandParser.java
index 9ffcdd2..f7db94e 100644
--- a/src/main/java/com/android/tools/r8/R8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/R8CommandParser.java
@@ -9,6 +9,8 @@
import com.android.tools.r8.StringConsumer.FileConsumer;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.profile.art.ArtProfileConsumerUtils;
+import com.android.tools.r8.profile.art.ArtProfileProviderUtils;
import com.android.tools.r8.utils.FlagFile;
import com.android.tools.r8.utils.MapIdTemplateProvider;
import com.android.tools.r8.utils.SourceFileTemplateProvider;
@@ -27,7 +29,7 @@
public class R8CommandParser extends BaseCompilerCommandParser<R8Command, R8Command.Builder> {
// Note: this must be a super-set of OPTIONS_WITH_TWO_PARAMETERS.
- private static final Set<String> OPTIONS_WITH_PARAMETER =
+ private static final Set<String> OPTIONS_WITH_ONE_PARAMETER =
ImmutableSet.of(
"--output",
"--lib",
@@ -44,10 +46,12 @@
"--desugared-lib-pg-conf-output",
"--map-id-template",
"--source-file-template",
+ ART_PROFILE_FLAG,
THREAD_COUNT_FLAG);
// Note: this must be a subset of OPTIONS_WITH_ONE_PARAMETER.
- private static final Set<String> OPTIONS_WITH_TWO_PARAMETERS = ImmutableSet.of("--feature");
+ private static final Set<String> OPTIONS_WITH_TWO_PARAMETERS =
+ ImmutableSet.of(ART_PROFILE_FLAG, "--feature");
// Due to the family of flags (for assertions and diagnostics) we can't base the one/two args
// on this setup of flags. Thus, the flag collection just encodes the descriptive content.
@@ -104,6 +108,7 @@
" %MAP_ID: map id (e.g., value of --map-id-template).",
" %MAP_HASH: compiler generated mapping hash."))
.add(ParseFlagInfoImpl.getAndroidPlatformBuild())
+ .add(ParseFlagInfoImpl.getArtProfile())
.add(ParseFlagInfoImpl.getVersion("r8"))
.add(ParseFlagInfoImpl.getHelp())
.build();
@@ -178,7 +183,7 @@
String arg = expandedArgs[i].trim();
String nextArg = null;
String nextNextArg = null;
- if (OPTIONS_WITH_PARAMETER.contains(arg)) {
+ if (OPTIONS_WITH_ONE_PARAMETER.contains(arg)) {
if (++i < expandedArgs.length) {
nextArg = expandedArgs[i];
} else {
@@ -301,6 +306,12 @@
SourceFileTemplateProvider.create(nextArg, builder.getReporter()));
} else if (arg.equals("--android-platform-build")) {
builder.setAndroidPlatformBuild(true);
+ } else if (arg.equals(ART_PROFILE_FLAG)) {
+ Path artProfilePath = Paths.get(nextArg);
+ Path rewrittenArtProfilePath = Paths.get(nextNextArg);
+ builder.addArtProfileForRewriting(
+ ArtProfileProviderUtils.createFromHumanReadableArtProfile(artProfilePath),
+ ArtProfileConsumerUtils.create(rewrittenArtProfilePath));
} else if (arg.startsWith("--")) {
if (tryParseAssertionArgument(builder, arg, argsOrigin)) {
continue;
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java
index 80a9913..c55157d 100644
--- a/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java
@@ -80,6 +80,10 @@
return (ArtProfileMethodRule) rules.get(method);
}
+ public int size() {
+ return rules.size();
+ }
+
public ArtProfile rewrittenWithLens(AppView<?> appView, GraphLens lens) {
if (lens.isEnumUnboxerLens()) {
return rewrittenWithLens(appView, lens.asEnumUnboxerLens());
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileConsumerUtils.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileConsumerUtils.java
new file mode 100644
index 0000000..bd7c680
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileConsumerUtils.java
@@ -0,0 +1,34 @@
+// Copyright (c) 2023, 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.profile.art;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.TextOutputStream;
+import com.android.tools.r8.utils.UTF8TextOutputStream;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Path;
+
+public class ArtProfileConsumerUtils {
+
+ public static ArtProfileConsumer create(Path rewrittenArtProfile) {
+ return new ArtProfileConsumer() {
+
+ @Override
+ public TextOutputStream getHumanReadableArtProfileConsumer() {
+ try {
+ return new UTF8TextOutputStream(rewrittenArtProfile);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ // Intentionally empty.
+ }
+ };
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileProviderUtils.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileProviderUtils.java
new file mode 100644
index 0000000..bc3cbb5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileProviderUtils.java
@@ -0,0 +1,36 @@
+// Copyright (c) 2023, 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.profile.art;
+
+import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.UTF8TextInputStream;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Path;
+
+public class ArtProfileProviderUtils {
+
+ public static ArtProfileProvider createFromHumanReadableArtProfile(Path artProfile) {
+ return new ArtProfileProvider() {
+ @Override
+ public void getArtProfile(ArtProfileBuilder profileBuilder) {
+ try {
+ profileBuilder.addHumanReadableArtProfile(
+ new UTF8TextInputStream(artProfile), emptyConsumer());
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ @Override
+ public Origin getOrigin() {
+ return new PathOrigin(artProfile);
+ }
+ };
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/UTF8TextOutputStream.java b/src/main/java/com/android/tools/r8/utils/UTF8TextOutputStream.java
new file mode 100644
index 0000000..10ced48
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/UTF8TextOutputStream.java
@@ -0,0 +1,36 @@
+// Copyright (c) 2023, 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.utils;
+
+import com.android.tools.r8.TextOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+public class UTF8TextOutputStream implements TextOutputStream {
+
+ private final OutputStream outputStream;
+
+ public UTF8TextOutputStream(Path path) throws IOException {
+ this(Files.newOutputStream(path));
+ }
+
+ public UTF8TextOutputStream(OutputStream outputStream) {
+ this.outputStream = outputStream;
+ }
+
+ @Override
+ public OutputStream getOutputStream() {
+ return outputStream;
+ }
+
+ @Override
+ public Charset getCharset() {
+ return StandardCharsets.UTF_8;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/CommandTestBase.java b/src/test/java/com/android/tools/r8/CommandTestBase.java
index ed755f9..8fcdb4c 100644
--- a/src/test/java/com/android/tools/r8/CommandTestBase.java
+++ b/src/test/java/com/android/tools/r8/CommandTestBase.java
@@ -5,13 +5,22 @@
package com.android.tools.r8;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.profile.art.ArtProfile;
+import com.android.tools.r8.profile.art.ArtProfileConsumer;
+import com.android.tools.r8.profile.art.ArtProfileForRewriting;
+import com.android.tools.r8.profile.art.ArtProfileProvider;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.IntBox;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
+import java.nio.file.Path;
+import java.util.List;
import org.junit.Test;
public abstract class CommandTestBase<C extends BaseCompilerCommand> extends TestBase {
@@ -285,6 +294,87 @@
});
}
+ @Test
+ public void artProfileFlagAbsentTest() throws Exception {
+ assertTrue(parseWithRequiredArgs().getArtProfilesForRewriting().isEmpty());
+ }
+
+ @Test
+ public void artProfileFlagPresentTest() throws Exception {
+ // Create a simple profile.
+ Path profile = temp.newFile("profile.txt").toPath();
+ Path residualProfile = temp.newFile("profile-out.txt").toPath();
+ String profileRuleFlags = "HSP";
+ String profileRuleDescriptor = "Lfoo/bar/Baz;->qux()V";
+ FileUtils.writeTextFile(profile, profileRuleFlags + profileRuleDescriptor);
+
+ // Pass the profile on the command line.
+ List<ArtProfileForRewriting> artProfilesForRewriting =
+ parseWithRequiredArgs("--art-profile", profile.toString(), residualProfile.toString())
+ .getArtProfilesForRewriting();
+ assertEquals(1, artProfilesForRewriting.size());
+
+ // Extract inputs.
+ ArtProfileForRewriting artProfileForRewriting = artProfilesForRewriting.get(0);
+ ArtProfileProvider artProfileProvider = artProfileForRewriting.getArtProfileProvider();
+ ArtProfileConsumer residualArtProfileConsumer =
+ artProfileForRewriting.getResidualArtProfileConsumer();
+ InternalOptions options = new InternalOptions();
+
+ // Build provided ART profile.
+ ArtProfile.Builder artProfileBuilder =
+ ArtProfile.builderForInitialArtProfile(artProfileProvider, options);
+ artProfileProvider.getArtProfile(artProfileBuilder);
+ ArtProfile artProfile = artProfileBuilder.build();
+
+ // Verify we found the same rule.
+ assertEquals(1, artProfile.size());
+ IntBox count = new IntBox();
+ artProfile.forEachRule(
+ classRule -> fail(),
+ methodRule -> {
+ assertEquals(profileRuleDescriptor, methodRule.getMethod().toSmaliString());
+ assertTrue(methodRule.getMethodRuleInfo().isHot());
+ assertTrue(methodRule.getMethodRuleInfo().isStartup());
+ assertTrue(methodRule.getMethodRuleInfo().isPostStartup());
+ count.increment();
+ });
+ assertEquals(1, count.get());
+
+ // Supply the rule back to the consumer.
+ artProfile.supplyConsumer(residualArtProfileConsumer, options.reporter);
+ assertEquals(
+ ImmutableList.of(profileRuleFlags + profileRuleDescriptor),
+ FileUtils.readAllLines(residualProfile));
+ }
+
+ @Test
+ public void artProfileFlagMissingInputOutputParameterTest() {
+ String expectedErrorContains = "Missing parameter for --art-profile.";
+ try {
+ DiagnosticsChecker.checkErrorsContains(
+ expectedErrorContains, handler -> parseWithRequiredArgs(handler, "--art-profile"));
+ fail("Expected failure");
+ } catch (CompilationFailedException e) {
+ // Expected.
+ }
+ }
+
+ @Test
+ public void artProfileFlagMissingOutputParameterTest() throws Exception {
+ String expectedErrorContains = "Missing parameter for --art-profile.";
+ Path profile = temp.newFile("profile.txt").toPath();
+ FileUtils.writeTextFile(profile, "");
+ try {
+ DiagnosticsChecker.checkErrorsContains(
+ expectedErrorContains,
+ handler -> parseWithRequiredArgs(handler, "--art-profile", profile.toString()));
+ fail("Expected failure");
+ } catch (CompilationFailedException e) {
+ // Expected.
+ }
+ }
+
private String[] prepareArgs(String[] args) {
String[] actualTestArgs;
String[] additionalTestArgs = requiredArgsForTest();