blob: 2fc67d45c6b7d9f67926c2ca5e5c08417c097bd1 [file] [log] [blame]
// Copyright (c) 2024, 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 static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.android.tools.r8.StringConsumer.FileConsumer;
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.dump.CompilerDump;
import com.android.tools.r8.dump.DumpOptions;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.naming.MapConsumer;
import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
import com.android.tools.r8.tracereferences.TraceReferencesBridge;
import com.android.tools.r8.tracereferences.TraceReferencesCommand;
import com.android.tools.r8.tracereferences.TraceReferencesKeepRules;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.AndroidAppConsumers;
import com.android.tools.r8.utils.DumpInputFlags;
import com.android.tools.r8.utils.ExceptionUtils;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.ZipUtils;
import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
import com.google.common.io.ByteStreams;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.function.Consumer;
import java.util.stream.Collectors;
class R8Partial {
private final InternalOptions options;
private final Consumer<AndroidApp> r8InputAppConsumer;
private final Consumer<AndroidApp> d8InputAppConsumer;
private final Consumer<AndroidApp> r8OutputAppConsumer;
private final Consumer<AndroidApp> d8OutputAppConsumer;
R8Partial(InternalOptions options) {
this.options = options;
this.r8InputAppConsumer = options.partialCompilationConfiguration.r8InputAppConsumer;
this.d8InputAppConsumer = options.partialCompilationConfiguration.d8InputAppConsumer;
this.r8OutputAppConsumer = options.partialCompilationConfiguration.r8OutputAppConsumer;
this.d8OutputAppConsumer = options.partialCompilationConfiguration.d8OutputAppConsumer;
}
static void runForTesting(AndroidApp app, InternalOptions options)
throws CompilationFailedException {
ExecutorService executor = ThreadUtils.getExecutorService(options);
ExceptionUtils.withR8CompilationHandler(
options.reporter,
() -> {
try {
new R8Partial(options).runInternal(app, executor);
} finally {
executor.shutdown();
}
});
}
void runInternal(AndroidApp app, ExecutorService executor) throws IOException, ResourceException {
Timing timing = Timing.create("R8 partial " + Version.LABEL, options);
ProgramConsumer originalProgramConsumer = options.programConsumer;
MapConsumer originalMapConsumer = options.mapConsumer;
Path tmp = options.partialCompilationConfiguration.getTempDir();
Path dumpFile = options.partialCompilationConfiguration.getDumpFile();
// Create a dump of the compiler input.
// TODO(b/309743298): Do not use compiler dump to handle splitting the compilation. This should
// be all in memory.
ApplicationReader applicationReader = new ApplicationReader(app, options, timing);
applicationReader.dump(
new DumpInputFlags() {
@Override
public Path getDumpPath() {
return dumpFile;
}
@Override
public boolean shouldDump(DumpOptions options) {
return true;
}
@Override
public boolean shouldFailCompilation() {
return false;
}
@Override
public boolean shouldLogDumpInfoMessage() {
return false;
}
});
CompilerDump dump = CompilerDump.fromArchive(dumpFile, tmp);
if (dump.getBuildProperties().hasMainDexKeepRules()
|| dump.getBuildProperties().hasArtProfileProviders()
|| dump.getBuildProperties().hasStartupProfileProviders()) {
throw options.reporter.fatalError(
"Split compilation does not support legacy multi-dex, baseline or startup profiles");
}
DexApplication dapp = applicationReader.read().toDirect();
AppInfoWithClassHierarchy appInfo =
AppInfoWithClassHierarchy.createForDesugaring(
AppInfo.createInitialAppInfo(dapp, GlobalSyntheticsStrategy.forNonSynthesizing()));
Set<DexProgramClass> d8classes = new HashSet<>();
appInfo
.classes()
.forEach(
clazz -> {
if (!d8classes.contains(clazz)
&& !options.partialCompilationConfiguration.test(
clazz.getType().getDescriptor())) {
d8classes.add(clazz);
// TODO(b/309743298): Improve this to only visit each class once and stop at
// library boundary.
appInfo.forEachSuperType(
clazz,
(superType, subclass, ignored) -> {
DexProgramClass superClass =
asProgramClassOrNull(appInfo.definitionFor(superType));
if (superClass != null) {
d8classes.add(superClass);
}
});
}
});
// Filter the program input into the D8 and R8 parts.
Set<String> d8ZipEntries =
d8classes.stream()
.map(clazz -> ZipUtils.zipEntryNameForClass(clazz.getClassReference()))
.collect(Collectors.toSet());
ZipBuilder d8ProgramBuilder = ZipBuilder.builder(tmp.resolve("d8-program.jar"));
ZipBuilder r8ProgramBuilder = ZipBuilder.builder(tmp.resolve("r8-program.jar"));
ZipUtils.iter(
dump.getProgramArchive(),
(entry, input) -> {
if (d8ZipEntries.contains(entry.getName())) {
d8ProgramBuilder.addBytes(entry.getName(), ByteStreams.toByteArray(input));
} else {
r8ProgramBuilder.addBytes(entry.getName(), ByteStreams.toByteArray(input));
}
});
Path d8Program = d8ProgramBuilder.build();
Path r8Program = r8ProgramBuilder.build();
// Compile D8 input with D8.
Path d8Output = tmp.resolve("d8-output.zip");
D8Command.Builder d8Builder =
D8Command.builder()
.setMinApiLevel(dump.getBuildProperties().getMinApi())
.addLibraryFiles(dump.getLibraryArchive())
.addClasspathFiles(dump.getClasspathArchive())
.addClasspathFiles(r8Program)
.addProgramFiles(d8Program)
.setMode(dump.getBuildProperties().getCompilationMode())
.setOutput(d8Output, OutputMode.DexIndexed);
if (dump.hasDesugaredLibrary()) {
d8Builder.addDesugaredLibraryConfiguration(
Files.readString(dump.getDesugaredLibraryFile(), UTF_8));
}
AndroidAppConsumers d8OutputAppSink = null;
if (d8OutputAppConsumer != null) {
d8OutputAppSink = new AndroidAppConsumers(d8Builder);
}
d8Builder.validate();
D8Command d8command = d8Builder.makeCommand();
AndroidApp d8App = d8command.getInputApp();
if (d8InputAppConsumer != null) {
d8InputAppConsumer.accept(d8App);
}
InternalOptions d8Options = d8command.getInternalOptions();
assert d8Options.getMinApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N)
: "Default interface methods not yet supported";
D8.runInternal(d8App, d8Options, executor);
if (d8OutputAppConsumer != null) {
d8OutputAppConsumer.accept(d8OutputAppSink.build());
}
// Run trace references to produce keep rules for the D8 compiled part.
// TODO(b/309743298): Do not emit keep rules into a file.
Path traceReferencesRules = tmp.resolve("tr.rules");
TraceReferencesKeepRules keepRulesConsumer =
TraceReferencesKeepRules.builder()
.setOutputConsumer(new FileConsumer(traceReferencesRules))
.build();
TraceReferencesCommand.Builder trBuilder =
TraceReferencesCommand.builder()
.setConsumer(keepRulesConsumer)
.addLibraryFiles(dump.getLibraryArchive())
.addTargetFiles(r8Program)
.addSourceFiles(d8Program);
TraceReferencesCommand tr = TraceReferencesBridge.makeCommand(trBuilder);
TraceReferencesBridge.runInternal(tr);
// Compile R8 input with R8 using the keep rules from trace references.
Path r8Output = tmp.resolve("r8-output.zip");
R8Command.Builder r8Builder =
R8Command.builder()
.setMinApiLevel(dump.getBuildProperties().getMinApi())
.addLibraryFiles(dump.getLibraryArchive())
.addClasspathFiles(dump.getClasspathArchive())
.addClasspathFiles(d8Program)
.addProgramFiles(r8Program)
.addProguardConfigurationFiles(dump.getProguardConfigFile(), traceReferencesRules)
.enableLegacyFullModeForKeepRules(true)
.setMode(dump.getBuildProperties().getCompilationMode())
.setOutput(r8Output, OutputMode.DexIndexed);
if (dump.hasDesugaredLibrary()) {
r8Builder.addDesugaredLibraryConfiguration(
Files.readString(dump.getDesugaredLibraryFile(), UTF_8));
}
AndroidAppConsumers r8OutputAppSink = null;
if (r8Builder != null) {
r8OutputAppSink = new AndroidAppConsumers(r8Builder);
}
r8Builder.validate();
R8Command r8Command = r8Builder.makeCommand();
AndroidApp r8App = r8Command.getInputApp();
if (r8InputAppConsumer != null) {
r8InputAppConsumer.accept(r8App);
}
InternalOptions r8Options = r8Command.getInternalOptions();
r8Options.mapConsumer = originalMapConsumer;
r8Options.quiet = true; // Don't write the R8 version.
R8.runInternal(r8App, r8Options, executor);
if (r8OutputAppConsumer != null) {
r8OutputAppConsumer.accept(r8OutputAppSink.build());
}
// Emit resources and merged DEX to the output consumer.
// TODO(b/309743298): Consider passing the DataResourceConsumer to the R8 invocation above.
DataResourceConsumer dataResourceConsumer = originalProgramConsumer.getDataResourceConsumer();
if (dataResourceConsumer != null) {
ZipUtils.iterWithZipFile(
r8Output,
(zip, entry) -> {
if (entry.getName().endsWith(FileUtils.DEX_EXTENSION)) {
return;
}
dataResourceConsumer.accept(
DataEntryResource.fromZip(zip, entry), new DiagnosticsHandler() {});
});
}
// TODO(b/309743298): Handle jumbo string rewriting with PCs in mapping file.
D8Command.Builder mergerBuilder =
D8Command.builder()
.setMinApiLevel(dump.getBuildProperties().getMinApi())
.addLibraryFiles(dump.getLibraryArchive())
.addClasspathFiles(dump.getClasspathArchive())
.addProgramFiles(d8Output, r8Output)
.setMode(dump.getBuildProperties().getCompilationMode())
.setProgramConsumer(originalProgramConsumer);
mergerBuilder.validate();
D8Command mergeCommand = mergerBuilder.makeCommand();
AndroidApp mergeApp = mergeCommand.getInputApp();
InternalOptions mergeOptions = mergeCommand.getInternalOptions();
D8.runInternal(mergeApp, mergeOptions, executor);
timing.end();
}
}