blob: 5a1910ecbcc40fe979391163de17da530826e66c [file] [log] [blame]
// 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.benchmarks.appdumps;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.LibraryDesugaringTestConfiguration;
import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.R8PartialTestBuilder;
import com.android.tools.r8.R8PartialTestCompileResult;
import com.android.tools.r8.R8TestBuilder;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestBase.Backend;
import com.android.tools.r8.TestCompilerBuilder;
import com.android.tools.r8.ThrowableConsumer;
import com.android.tools.r8.benchmarks.BenchmarkBase;
import com.android.tools.r8.benchmarks.BenchmarkConfig;
import com.android.tools.r8.benchmarks.BenchmarkConfigError;
import com.android.tools.r8.benchmarks.BenchmarkDependency;
import com.android.tools.r8.benchmarks.BenchmarkEnvironment;
import com.android.tools.r8.benchmarks.BenchmarkMethod;
import com.android.tools.r8.benchmarks.BenchmarkMetric;
import com.android.tools.r8.benchmarks.BenchmarkResultsSingle;
import com.android.tools.r8.benchmarks.BenchmarkSuite;
import com.android.tools.r8.benchmarks.BenchmarkTarget;
import com.android.tools.r8.dump.CompilerDump;
import com.android.tools.r8.dump.DumpOptions;
import com.android.tools.r8.keepanno.annotations.AnnotationPattern;
import com.android.tools.r8.keepanno.annotations.KeepEdge;
import com.android.tools.r8.keepanno.annotations.KeepItemKind;
import com.android.tools.r8.keepanno.annotations.KeepTarget;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableSet;
import java.io.IOException;
import java.lang.annotation.RetentionPolicy;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class AppDumpBenchmarkBuilder {
@KeepEdge(
consequences =
@KeepTarget(
kind = KeepItemKind.ONLY_METHODS,
methodAnnotatedByClassName = "androidx.compose.runtime.Composable",
constraints = {},
constrainAnnotations =
@AnnotationPattern(
name = "androidx.compose.runtime.Composable",
retention = RetentionPolicy.CLASS)))
static class KeepComposableAnnotations {}
public static AppDumpBenchmarkBuilder builder() {
return new AppDumpBenchmarkBuilder();
}
private String name;
private BenchmarkDependency dumpDependency;
private int fromRevision = -1;
private CompilationMode compilationMode;
private boolean enableLibraryDesugaring = true;
private boolean enableResourceShrinking = false;
private boolean runtimeOnly = false;
private int warmupIterations = 1;
private final List<String> programPackages = new ArrayList<>();
public void verify() {
if (name == null) {
throw new BenchmarkConfigError("Missing name");
}
if (dumpDependency == null) {
throw new BenchmarkConfigError("Missing dump");
}
}
public AppDumpBenchmarkBuilder setCompilationMode(CompilationMode compilationMode) {
this.compilationMode = compilationMode;
return this;
}
public AppDumpBenchmarkBuilder setEnableLibraryDesugaring(boolean enableLibraryDesugaring) {
this.enableLibraryDesugaring = enableLibraryDesugaring;
return this;
}
public AppDumpBenchmarkBuilder setEnableResourceShrinking(boolean enableResourceShrinking) {
this.enableResourceShrinking = enableResourceShrinking;
return this;
}
public AppDumpBenchmarkBuilder setName(String name) {
this.name = name;
return this;
}
public AppDumpBenchmarkBuilder setDumpDependencyPath(Path dumpDependencyPath) {
return setDumpDependency(
new BenchmarkDependency(
"appdump",
dumpDependencyPath.getFileName().toString(),
dumpDependencyPath.getParent()));
}
public AppDumpBenchmarkBuilder setDumpDependency(BenchmarkDependency dependency) {
this.dumpDependency = dependency;
return this;
}
public AppDumpBenchmarkBuilder setFromRevision(int fromRevision) {
this.fromRevision = fromRevision;
return this;
}
public AppDumpBenchmarkBuilder setRuntimeOnly() {
this.runtimeOnly = true;
return this;
}
public AppDumpBenchmarkBuilder setWarmupIterations(int warmupIterations) {
this.warmupIterations = warmupIterations;
return this;
}
public AppDumpBenchmarkBuilder addProgramPackages(String... pkgs) {
return addProgramPackages(Arrays.asList(pkgs));
}
public AppDumpBenchmarkBuilder addProgramPackages(Collection<String> pkgs) {
programPackages.addAll(pkgs);
return this;
}
public BenchmarkConfig buildR8() {
return buildR8(getDefaultR8Configuration());
}
public BenchmarkConfig buildR8(ThrowableConsumer<? super R8FullTestBuilder> configuration) {
verify();
BenchmarkConfig.Builder builder =
BenchmarkConfig.builder()
.setName(name)
.setTarget(BenchmarkTarget.R8)
.setSuite(BenchmarkSuite.OPENSOURCE_BENCHMARKS)
.setMethod(runR8(this, configuration))
.setFromRevision(fromRevision)
.addDependency(dumpDependency)
.measureRunTime()
// TODO(b/373550435): Update dex2oat to enable checking absence of verification errors
// on SystemUI.
.setEnableDex2OatVerification(!name.equals("SystemUIApp"))
.setTimeout(10, TimeUnit.MINUTES);
if (!runtimeOnly) {
builder
.measureCodeSize()
.measureInstructionCodeSize()
.measureComposableInstructionCodeSize()
.measureDexSegmentsCodeSize()
.measureDex2OatCodeSize();
if (enableResourceShrinking) {
builder.measureResourceSize();
}
}
return builder.build();
}
public BenchmarkConfig buildR8WithPartialShrinking() {
return buildR8WithPartialShrinking(getDefaultR8PartialConfiguration());
}
public BenchmarkConfig buildR8WithPartialShrinking(
ThrowableConsumer<? super R8PartialTestBuilder> configuration) {
return buildR8WithPartialShrinking(configuration, ThrowableConsumer.empty());
}
public BenchmarkConfig buildR8WithPartialShrinking(
ThrowableConsumer<? super R8PartialTestBuilder> configuration,
ThrowableConsumer<? super R8PartialTestCompileResult> compileResultConsumer) {
verify();
BenchmarkConfig.Builder builder =
BenchmarkConfig.builder()
.setName(name)
.setTarget(BenchmarkTarget.R8)
.setSuite(BenchmarkSuite.OPENSOURCE_BENCHMARKS)
.setMethod(runR8WithPartialShrinking(this, configuration, compileResultConsumer))
.setFromRevision(fromRevision)
.addDependency(dumpDependency)
.measureRunTime()
.measureCodeSize()
.measureInstructionCodeSize()
.measureComposableInstructionCodeSize()
.measureDexSegmentsCodeSize()
.measureDex2OatCodeSize()
// TODO(b/373550435): Update dex2oat to enable checking absence of verification errors
// on SystemUI.
.setEnableDex2OatVerification(!name.equals("SystemUIAppPartialShrinking"))
.setTimeout(10, TimeUnit.MINUTES);
if (enableResourceShrinking) {
builder.measureResourceSize();
}
return builder.build();
}
public BenchmarkConfig buildIncrementalD8() {
verify();
if (programPackages.isEmpty()) {
throw new BenchmarkConfigError(
"Incremental benchmark should specifiy at least one program package");
}
return BenchmarkConfig.builder()
.setName(name)
.setTarget(BenchmarkTarget.D8)
.setSuite(BenchmarkSuite.OPENSOURCE_BENCHMARKS)
.setMethod(runIncrementalD8(this))
.setFromRevision(fromRevision)
.addDependency(dumpDependency)
.addSubBenchmark(nameForDexPart(), BenchmarkMetric.RunTimeRaw)
.addSubBenchmark(nameForMergePart(), BenchmarkMetric.RunTimeRaw)
.setTimeout(10, TimeUnit.MINUTES)
.build();
}
public BenchmarkConfig buildBatchD8() {
verify();
return BenchmarkConfig.builder()
.setName(name)
.setTarget(BenchmarkTarget.D8)
.setSuite(BenchmarkSuite.OPENSOURCE_BENCHMARKS)
.setMethod(runBatchD8(this))
.setFromRevision(fromRevision)
.addDependency(dumpDependency)
.measureRunTime()
.measureCodeSize()
.setTimeout(10, TimeUnit.MINUTES)
.build();
}
private String nameForCodePart() {
return name + "Code";
}
private String nameForDexPart() {
return name + "Dex";
}
private String nameForResourcePart() {
return name + "Resource";
}
private String nameForRuntimePart() {
return name + "Runtime";
}
private String nameForMergePart() {
return name + "Merge";
}
private CompilerDump getExtractedDump(BenchmarkEnvironment environment) throws IOException {
String dumpName = enableResourceShrinking ? "dump_app_res.zip" : "dump_app.zip";
Path dump = dumpDependency.getRoot(environment).resolve(dumpName);
return CompilerDump.fromArchive(dump, environment.getTemp().newFolder().toPath());
}
private static void addDesugaredLibrary(
TestCompilerBuilder<?, ?, ?, ?, ?> builder, CompilerDump dump) {
Path config = dump.getDesugaredLibraryFile();
if (Files.exists(config)) {
builder.enableCoreLibraryDesugaring(
LibraryDesugaringTestConfiguration.forSpecification(config));
}
}
private static ThrowableConsumer<? super R8FullTestBuilder> getDefaultR8Configuration() {
return testBuilder ->
testBuilder
.allowUnnecessaryDontWarnWildcards()
.allowUnusedDontWarnPatterns()
.allowUnusedProguardConfigurationRules()
// TODO(b/222228826): Disallow unrecognized diagnostics and open interfaces.
.allowDiagnosticMessages()
.addOptionsModification(
options -> options.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces());
}
private static ThrowableConsumer<? super R8PartialTestBuilder>
getDefaultR8PartialConfiguration() {
return testBuilder ->
testBuilder
.allowUnnecessaryDontWarnWildcards()
.allowUnusedDontWarnPatterns()
.allowUnusedProguardConfigurationRules()
// TODO(b/222228826): Disallow unrecognized diagnostics and open interfaces.
.allowDiagnosticMessages()
.addR8PartialR8OptionsModification(
options -> options.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces());
}
private static ThrowableConsumer<? super R8TestBuilder<?, ?, ?>>
getKeepComposableAnnotationsConfiguration() {
return testBuilder ->
testBuilder
.addProgramClasses(KeepComposableAnnotations.class)
.addKeepClassAndMembersRules("androidx.compose.runtime.Composable")
.addKeepRuntimeVisibleAnnotations()
.enableExperimentalKeepAnnotations();
}
private static BenchmarkMethod runR8(
AppDumpBenchmarkBuilder builder, ThrowableConsumer<? super R8FullTestBuilder> configuration) {
return internalRunR8(builder, configuration);
}
private static BenchmarkMethod runR8WithPartialShrinking(
AppDumpBenchmarkBuilder builder,
ThrowableConsumer<? super R8PartialTestBuilder> configuration,
ThrowableConsumer<? super R8PartialTestCompileResult> compileResultConsumer) {
return internalRunR8Partial(builder, configuration, compileResultConsumer);
}
private static BenchmarkMethod internalRunR8(
AppDumpBenchmarkBuilder builder,
ThrowableConsumer<? super R8FullTestBuilder> configuration) {
return environment ->
BenchmarkBase.runner(environment)
.setWarmupIterations(0)
.run(
results -> {
CompilerDump dump = builder.getExtractedDump(environment);
DumpOptions dumpProperties = dump.getBuildProperties();
TestBase.testForR8(environment.getTemp(), Backend.DEX)
.addProgramFiles(dump.getProgramArchive())
.addLibraryFiles(dump.getLibraryArchive())
.addKeepRuleFiles(dump.getProguardConfigFile())
.addOptionsModification(
options -> {
options.apiModelingOptions().androidApiExtensionPackages =
dumpProperties.getAndroidApiExtensionPackages();
options
.horizontalClassMergerOptions()
.setEnableSameFilePolicy(dumpProperties.getEnableSameFilePolicy());
})
.enableIsolatedSplits(dumpProperties.getIsolatedSplits())
.setMinApi(dumpProperties.getMinApi())
.apply(
testBuilder -> {
dump.forEachFeatureArchive(testBuilder::addFeatureSplit);
addDesugaredLibrary(testBuilder, dump);
})
.apply(configuration)
.applyIf(
environment.getConfig().containsComposableCodeSizeMetric(),
getKeepComposableAnnotationsConfiguration())
.applyIf(
builder.enableResourceShrinking,
b ->
b.enableOptimizedShrinking()
.setAndroidResourcesFromPath(dump.getAndroidResources()))
.apply(
r -> {
try {
r.benchmarkCompile(results)
.benchmarkCodeSize(results)
.benchmarkInstructionCodeSize(results)
.benchmarkDexSegmentsCodeSize(results)
.benchmarkDex2OatCodeSize(
results,
environment.getConfig().isDex2OatVerificationEnabled())
.applyIf(
environment
.getConfig()
.containsMetric(BenchmarkMetric.ResourceSize),
cr -> cr.benchmarkResourceSize(results));
} catch (CompilationFailedException e) {
if (!(e.getCause() instanceof AbortBenchmarkException)) {
throw e;
}
}
});
});
}
private static BenchmarkMethod internalRunR8Partial(
AppDumpBenchmarkBuilder builder,
ThrowableConsumer<? super R8PartialTestBuilder> configuration,
ThrowableConsumer<? super R8PartialTestCompileResult> compileResultConsumer) {
return environment ->
BenchmarkBase.runner(environment)
.setWarmupIterations(builder.warmupIterations)
.run(
results -> {
CompilerDump dump = builder.getExtractedDump(environment);
DumpOptions dumpProperties = dump.getBuildProperties();
// Run R8.
TestBase.testForR8Partial(environment.getTemp())
.addProgramFiles(dump.getProgramArchive())
.addLibraryFiles(dump.getLibraryArchive())
.addKeepRules(
// TODO(b/392529669): Add support for proto shrinking.
StringUtils.replaceAll(
FileUtils.readTextFile(dump.getProguardConfigFile()),
"-shrinkunusedprotofields",
""))
.addR8PartialR8OptionsModification(
options -> {
options.apiModelingOptions().androidApiExtensionPackages =
dumpProperties.getAndroidApiExtensionPackages();
options
.horizontalClassMergerOptions()
.setEnableSameFilePolicy(dumpProperties.getEnableSameFilePolicy());
})
.enableIsolatedSplits(dumpProperties.getIsolatedSplits())
.setMinApi(dumpProperties.getMinApi())
.setR8PartialConfiguration(
b -> {
if (builder.programPackages.isEmpty()) {
b.addJavaTypeIncludePattern("androidx.**");
b.addJavaTypeIncludePattern("kotlin.**");
b.addJavaTypeIncludePattern("kotlinx.**");
} else {
builder.programPackages.forEach(b::addJavaTypeIncludePattern);
}
})
.apply(
b -> {
dump.forEachFeatureArchive(b::addFeatureSplit);
addDesugaredLibrary(b, dump);
})
.apply(configuration)
.applyIf(
environment.getConfig().containsComposableCodeSizeMetric(),
getKeepComposableAnnotationsConfiguration())
.applyIf(
builder.enableResourceShrinking,
b ->
b.enableOptimizedShrinking()
.setAndroidResourcesFromPath(dump.getAndroidResources()))
.apply(
r ->
r.benchmarkCompile(results)
.benchmarkCodeSize(results)
.benchmarkInstructionCodeSize(results)
.benchmarkDexSegmentsCodeSize(results)
.benchmarkDex2OatCodeSize(
results,
environment.getConfig().isDex2OatVerificationEnabled())
.applyIf(
environment
.getConfig()
.containsMetric(BenchmarkMetric.ResourceSize),
cr -> cr.benchmarkResourceSize(results))
.apply(compileResultConsumer));
});
}
private static BenchmarkMethod runBatchD8(AppDumpBenchmarkBuilder builder) {
assert builder.compilationMode != null;
return environment ->
BenchmarkBase.runner(environment)
.setWarmupIterations(builder.warmupIterations)
.run(
results -> {
CompilerDump dump = builder.getExtractedDump(environment);
DumpOptions dumpProperties = dump.getBuildProperties();
TestBase.testForD8(environment.getTemp(), Backend.DEX)
.addProgramFiles(dump.getProgramArchive())
.addLibraryFiles(dump.getLibraryArchive())
.setMinApi(dumpProperties.getMinApi())
.setMode(builder.compilationMode)
.applyIf(builder.enableLibraryDesugaring, b -> addDesugaredLibrary(b, dump))
.benchmarkCompile(results)
.benchmarkCodeSize(results);
});
}
private static BenchmarkMethod runIncrementalD8(AppDumpBenchmarkBuilder builder) {
assert builder.compilationMode.isDebug();
return environment ->
BenchmarkBase.runner(environment)
.setWarmupIterations(builder.warmupIterations)
.reportResultSum()
.run(
results -> {
CompilerDump dump = builder.getExtractedDump(environment);
DumpOptions dumpProperties = dump.getBuildProperties();
int numShards = 1;
PackageSplitResources resources =
PackageSplitResources.create(
environment.getTemp(),
dump.getProgramArchive(),
builder.programPackages,
numShards);
// Compile all files to a single DEX file.
List<List<Path>> compiledShards = new ArrayList<>();
BenchmarkResultsSingle dexResults =
new BenchmarkResultsSingle(
"tmp", ImmutableSet.of(BenchmarkMetric.RunTimeRaw));
for (List<Path> shard : resources.getShards()) {
List<Path> compiledShard = new ArrayList<>(shard.size());
for (Path programFile : shard) {
TestBase.testForD8(environment.getTemp())
.addProgramFiles(programFile)
.addClasspathFiles(dump.getProgramArchive())
.addLibraryFiles(dump.getLibraryArchive())
.applyIf(
builder.enableLibraryDesugaring, b -> addDesugaredLibrary(b, dump))
.debug()
.setIntermediate(true)
.setMinApi(dumpProperties.getMinApi())
.benchmarkCompile(dexResults)
.writeToZip(compiledShard::add);
}
compiledShards.add(compiledShard);
}
dexResults.doAverage();
results
.getSubResults(builder.nameForDexPart())
.addRuntimeResult(dexResults.getRuntimeResults().getLong(0));
// Merge each compiled shard.
for (List<Path> compiledShard : compiledShards) {
TestBase.testForD8(environment.getTemp())
.addProgramFiles(compiledShard)
.addLibraryFiles(dump.getLibraryArchive())
.debug()
.setMinApi(dumpProperties.getMinApi())
.benchmarkCompile(results.getSubResults(builder.nameForMergePart()));
}
});
}
}