Reduce memory pressure when reading line positions
When reading in line positions in class files, the ArchiveResourceProvider will read all bytes before closing the file and return a map. By using a consumer, we only consume the memory needed for visiting that a single class file at a time.
Bug: 147009037
Change-Id: I3baf9f00689af0a8d5786dbfb0f4bdfd8c479645
diff --git a/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java b/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java
index 5a50f1b..a66d383 100644
--- a/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java
+++ b/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java
@@ -28,6 +28,7 @@
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
+import java.util.function.Consumer;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
@@ -136,4 +137,43 @@
private boolean isProgramResourceName(String name) {
return ZipUtils.isClassFile(name) || (ZipUtils.isDexFile(name) && !ignoreDexInArchive);
}
+
+ public void accept(Consumer<ProgramResource> visitor) throws ResourceException {
+ try (ZipFile zipFile =
+ FileUtils.createZipFile(archive.getPath().toFile(), StandardCharsets.UTF_8)) {
+ final Enumeration<? extends ZipEntry> entries = zipFile.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
+ String name = entry.getName();
+ if (archive.matchesFile(name) && isProgramResourceName(name)) {
+ Origin entryOrigin = new ArchiveEntryOrigin(name, origin);
+ try (InputStream stream = zipFile.getInputStream(entry)) {
+ if (ZipUtils.isDexFile(name)) {
+ OneShotByteResource resource =
+ OneShotByteResource.create(
+ Kind.DEX, entryOrigin, ByteStreams.toByteArray(stream), null);
+ visitor.accept(resource);
+ } else if (ZipUtils.isClassFile(name)) {
+ OneShotByteResource resource =
+ OneShotByteResource.create(
+ Kind.CF,
+ entryOrigin,
+ ByteStreams.toByteArray(stream),
+ Collections.singleton(DescriptorUtils.guessTypeDescriptor(name)));
+ visitor.accept(resource);
+ }
+ }
+ }
+ }
+ } catch (ZipException e) {
+ throw new ResourceException(
+ origin,
+ new CompilationError("Zip error while reading '" + archive + "': " + e.getMessage(), e));
+ } catch (IOException e) {
+ throw new ResourceException(
+ origin,
+ new CompilationError(
+ "I/O exception while reading '" + archive + "': " + e.getMessage(), e));
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/utils/CfLineToMethodMapper.java b/src/main/java/com/android/tools/r8/utils/CfLineToMethodMapper.java
index 94024ee..7713857 100644
--- a/src/main/java/com/android/tools/r8/utils/CfLineToMethodMapper.java
+++ b/src/main/java/com/android/tools/r8/utils/CfLineToMethodMapper.java
@@ -27,7 +27,7 @@
}
public String lookupNameAndDescriptor(String binaryName, int lineNumber)
- throws IOException, ResourceException {
+ throws ResourceException {
if (sourceMethodMapping.isEmpty()) {
readLineNumbersFromClassFiles();
}
@@ -35,13 +35,32 @@
return lineMappings == null ? null : lineMappings.get(lineNumber);
}
- private void readLineNumbersFromClassFiles() throws ResourceException, IOException {
+ private void readLineNumbersFromClassFiles() throws ResourceException {
ClassVisitor classVisitor = new ClassVisitor();
for (ProgramResourceProvider resourceProvider : inputApp.getProgramResourceProviders()) {
- for (ProgramResource programResource : resourceProvider.getProgramResources()) {
- if (programResource.getKind() == Kind.CF) {
- new ClassReader(StreamUtils.StreamToByteArrayClose(programResource.getByteStream()))
- .accept(classVisitor, ClassReader.SKIP_FRAMES);
+ if (resourceProvider instanceof ArchiveResourceProvider) {
+ ArchiveResourceProvider provider = (ArchiveResourceProvider) resourceProvider;
+ provider.accept(
+ programResource -> {
+ try {
+ new ClassReader(StreamUtils.StreamToByteArrayClose(programResource.getByteStream()))
+ .accept(classVisitor, ClassReader.SKIP_FRAMES);
+ } catch (IOException | ResourceException e) {
+ // Intentionally left empty because the addition of inline info for kotlin inline
+ // functions is a best effort.
+ }
+ });
+ } else {
+ for (ProgramResource programResource : resourceProvider.getProgramResources()) {
+ if (programResource.getKind() == Kind.CF) {
+ try {
+ new ClassReader(StreamUtils.StreamToByteArrayClose(programResource.getByteStream()))
+ .accept(classVisitor, ClassReader.SKIP_FRAMES);
+ } catch (IOException e) {
+ // Intentionally left empty because the addition of inline info for kotlin inline
+ // functions is a best effort.
+ }
+ }
}
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index c7324da..df13ac6 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -48,7 +48,6 @@
import com.android.tools.r8.naming.Range;
import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
import com.google.common.base.Suppliers;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
@@ -120,7 +119,6 @@
private KotlinInlineFunctionPositionRemapper(
AppView<?> appView,
- AndroidApp inputApp,
PositionRemapper baseRemapper,
CfLineToMethodMapper lineToMethodMapper) {
this.appView = appView;
@@ -172,7 +170,7 @@
}
// This is the same position, so we should really not mark this as an inline position. Fall
// through to the default case.
- } catch (IOException | ResourceException ignored) {
+ } catch (ResourceException ignored) {
// Intentionally left empty. Remapping of kotlin functions utility is a best effort mapping.
}
return baseRemapper.createRemappedPosition(position);
@@ -322,7 +320,7 @@
// remapper to allow for remapping original positions to kotlin inline positions.
KotlinInlineFunctionPositionRemapper kotlinRemapper =
new KotlinInlineFunctionPositionRemapper(
- appView, inputApp, positionRemapper, cfLineToMethodMapper);
+ appView, positionRemapper, cfLineToMethodMapper);
for (DexEncodedMethod method : methods) {
kotlinRemapper.currentMethod = method;