Merge "Add tests for (package-)naming."
diff --git a/build.gradle b/build.gradle
index 67d455a..e2c21b6 100644
--- a/build.gradle
+++ b/build.gradle
@@ -101,6 +101,7 @@
dependencies {
compile 'net.sf.jopt-simple:jopt-simple:4.6'
+ compile 'com.googlecode.json-simple:json-simple:1.1.1'
compile group: 'com.google.guava', name: 'guava', version: '19.0'
compile group: 'it.unimi.dsi', name: 'fastutil', version: '7.2.0'
compile group: 'org.apache.commons', name: 'commons-compress', version: '1.12'
@@ -397,6 +398,20 @@
}
}
+task ExtractMarker(type: Jar) {
+ from sourceSets.main.output
+ baseName 'extractmarker'
+ manifest {
+ attributes 'Main-Class': 'com.android.tools.r8.ExtractMarker'
+ }
+ // In order to build without dependencies, pass the exclude_deps property using:
+ // gradle -Pexclude_deps ExtractMarker
+ if (!project.hasProperty('exclude_deps')) {
+ // Also include dependencies
+ from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
+ }
+}
+
task sourceJar(type: Jar, dependsOn: classes) {
classifier = 'src'
from sourceSets.main.allSource
@@ -534,7 +549,7 @@
classpath = sourceSets.examples.compileClasspath
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
- options.compilerArgs += ["-Xlint:-options"]
+ options.compilerArgs = ["-Xlint:none"]
}
examplesDir.eachDir { dir ->
def name = dir.getName();
diff --git a/src/main/java/com/android/tools/r8/BaseCommand.java b/src/main/java/com/android/tools/r8/BaseCommand.java
index 5eb67c4..430624b 100644
--- a/src/main/java/com/android/tools/r8/BaseCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCommand.java
@@ -219,9 +219,53 @@
return self();
}
- /** Set the main-dex list file. */
- public B setMainDexListFile(Path file) {
- app.setMainDexListFile(file);
+ /**
+ * Add main-dex list files.
+ *
+ * Each line in each of the files specifies one class to keep in the primary dex file
+ * (<code>classes.dex</code>).
+ *
+ * A class is specified using the following format: "com/example/MyClass.class". That is
+ * "/" as separator between package components, and a trailing ".class".
+ */
+ public B addMainDexListFiles(Path... files) throws IOException {
+ app.addMainDexListFiles(files);
+ return self();
+ }
+
+ /**
+ * Add main-dex list files.
+ *
+ * @see #addMainDexListFiles(Path...)
+ */
+ public B addMainDexListFiles(Collection<Path> files) throws IOException {
+ app.addMainDexListFiles(files);
+ return self();
+ }
+
+ /**
+ * Add main-dex classes.
+ *
+ * Add classes to keep in the primary dex file (<code>classes.dex</code>).
+ *
+ * NOTE: The name of the classes is specified using the Java fully qualified names format
+ * (e.g. "com.example.MyClass"), and <i>not</i> the format used by the main-dex list file.
+ */
+ public B addMainDexClasses(String... classes) {
+ app.addMainDexClasses(classes);
+ return self();
+ }
+
+ /**
+ * Add main-dex classes.
+ *
+ * Add classes to keep in the primary dex file (<code>classes.dex</code>).
+ *
+ * NOTE: The name of the classes is specified using the Java fully qualified names format
+ * (e.g. "com.example.MyClass"), and <i>not</i> the format used by the main-dex list file.
+ */
+ public B addMainDexClasses(Collection<String> classes) {
+ app.addMainDexClasses(classes);
return self();
}
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index bca7a9f..a6da2ec 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -7,6 +7,8 @@
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.dex.ApplicationWriter;
+import com.android.tools.r8.dex.Marker;
+import com.android.tools.r8.dex.Marker.Tool;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.MainDexError;
import com.android.tools.r8.graph.AppInfo;
@@ -152,6 +154,14 @@
}
}
+ // Compute the marker to be placed in the main dex file.
+ private static Marker getMarker(InternalOptions options) {
+ if (options.customizedMarker != null) {
+ return options.customizedMarker;
+ }
+ return new Marker(Tool.D8);
+ }
+
private static CompilationResult runForTesting(
AndroidApp inputApp, InternalOptions options, ExecutorService executor) throws IOException {
try {
@@ -174,11 +184,11 @@
options.methodsFilter.forEach((m) -> System.out.println(" - " + m));
return null;
}
-
+ Marker marker = getMarker(options);
CompilationResult output =
new CompilationResult(
new ApplicationWriter(
- app, appInfo, options, null, NamingLens.getIdentityLens(), null)
+ app, appInfo, options, marker, null, NamingLens.getIdentityLens(), null)
.write(null, executor),
app,
appInfo);
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index b856556..2937205 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -131,7 +131,6 @@
public static Builder parse(String[] args) throws CompilationException, IOException {
CompilationMode modeSet = null;
Path outputPath = null;
- String mainDexList = null;
Builder builder = builder();
try {
for (int i = 0; i < args.length; i++) {
@@ -168,11 +167,7 @@
} else if (arg.equals("--classpath")) {
builder.addClasspathFiles(Paths.get(args[++i]));
} else if (arg.equals("--main-dex-list")) {
- if (mainDexList != null) {
- throw new CompilationException("Only one --main-dex-list supported");
- }
- mainDexList = args[++i];
- builder.setMainDexListFile(Paths.get(mainDexList));
+ builder.addMainDexListFiles(Paths.get(args[++i]));
} else if (arg.equals("--min-api")) {
builder.setMinApiLevel(Integer.valueOf(args[++i]));
} else if (arg.equals("--intermediate")) {
@@ -210,6 +205,7 @@
InternalOptions internal = new InternalOptions(new DexItemFactory());
assert !internal.debug;
internal.debug = getMode() == CompilationMode.DEBUG;
+ internal.minimalMainDex = internal.debug;
internal.minApiLevel = getMinApiLevel();
internal.intermediate = intermediate;
// Assert and fixup defaults.
diff --git a/src/main/java/com/android/tools/r8/ExtractMarker.java b/src/main/java/com/android/tools/r8/ExtractMarker.java
new file mode 100644
index 0000000..c1234b8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ExtractMarker.java
@@ -0,0 +1,122 @@
+// Copyright (c) 2017, the Rex 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 com.google.common.collect.ImmutableList;
+
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.dex.Marker;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.shaking.ProguardRuleParserException;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.OutputMode;
+import com.android.tools.r8.utils.Timing;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.concurrent.ExecutionException;
+
+public class ExtractMarker {
+ private static class Command extends BaseCommand {
+
+ public static class Builder
+ extends BaseCommand.Builder<ExtractMarker.Command, ExtractMarker.Command.Builder> {
+
+ private Builder() {
+ super(CompilationMode.RELEASE);
+ }
+
+ @Override
+ ExtractMarker.Command.Builder self() {
+ return this;
+ }
+
+ @Override
+ public ExtractMarker.Command build() throws CompilationException, IOException {
+ // If printing versions ignore everything else.
+ if (isPrintHelp()) {
+ return new ExtractMarker.Command(isPrintHelp());
+ }
+ validate();
+ return new ExtractMarker.Command(
+ getAppBuilder().build(), getOutputPath(), getOutputMode(), getMode(), getMinApiLevel());
+ }
+ }
+
+ static final String USAGE_MESSAGE = String.join("\n", ImmutableList.of(
+ "Usage: extractmarker [options] <input-files>",
+ " where <input-files> are dex files",
+ " --version # Print the version of r8.",
+ " --help # Print this message."));
+
+ public static ExtractMarker.Command.Builder builder() {
+ return new ExtractMarker.Command.Builder();
+ }
+
+ public static ExtractMarker.Command.Builder parse(String[] args)
+ throws CompilationException, IOException {
+ ExtractMarker.Command.Builder builder = builder();
+ parse(args, builder);
+ return builder;
+ }
+
+ private static void parse(String[] args, ExtractMarker.Command.Builder builder)
+ throws CompilationException, IOException {
+ for (int i = 0; i < args.length; i++) {
+ String arg = args[i].trim();
+ if (arg.length() == 0) {
+ continue;
+ } else if (arg.equals("--help")) {
+ builder.setPrintHelp(true);
+ } else {
+ if (arg.startsWith("--")) {
+ throw new CompilationException("Unknown option: " + arg);
+ }
+ builder.addProgramFiles(Paths.get(arg));
+ }
+ }
+ }
+
+ private Command(
+ AndroidApp inputApp,
+ Path outputPath,
+ OutputMode outputMode,
+ CompilationMode mode,
+ int minApiLevel) {
+ super(inputApp, outputPath, outputMode, mode, minApiLevel);
+ }
+
+ private Command(boolean printHelp) {
+ super(printHelp, false);
+ }
+
+ @Override
+ InternalOptions getInternalOptions() {
+ return new InternalOptions();
+ }
+ }
+
+ public static void main(String[] args)
+ throws IOException, ProguardRuleParserException, CompilationException, ExecutionException {
+ ExtractMarker.Command.Builder builder = ExtractMarker.Command.parse(args);
+ ExtractMarker.Command command = builder.build();
+ if (command.isPrintHelp()) {
+ System.out.println(ExtractMarker.Command.USAGE_MESSAGE);
+ return;
+ }
+ AndroidApp app = command.getInputApp();
+ DexApplication dexApp =
+ new ApplicationReader(app, new InternalOptions(), new Timing("ExtractMarker")).read();
+ Marker readMarker = dexApp.dexItemFactory.extractMarker();
+ if (readMarker == null) {
+ System.out.println("D8/R8 marker not found.");
+ System.exit(1);
+ } else {
+ System.out.println(readMarker.toString());
+ System.exit(0);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 50f79dc..4393a3e 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -8,6 +8,8 @@
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.dex.ApplicationWriter;
import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.dex.Marker;
+import com.android.tools.r8.dex.Marker.Tool;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.MainDexError;
import com.android.tools.r8.graph.AppInfo;
@@ -77,6 +79,14 @@
options.itemFactory.resetSortedIndices();
}
+ // Compute the marker to be placed in the main dex file.
+ private static Marker getMarker(InternalOptions options) {
+ if (options.customizedMarker != null) {
+ return options.customizedMarker;
+ }
+ return new Marker(Tool.R8);
+ }
+
public static AndroidApp writeApplication(
ExecutorService executorService,
DexApplication application,
@@ -88,8 +98,9 @@
InternalOptions options)
throws ExecutionException {
try {
+ Marker marker = getMarker(options);
return new ApplicationWriter(
- application, appInfo, options, deadCode, namingLens, proguardSeedsData)
+ application, appInfo, options, marker, deadCode, namingLens, proguardSeedsData)
.write(packageDistribution, executorService);
} catch (IOException e) {
throw new RuntimeException("Cannot write dex application", e);
@@ -432,7 +443,7 @@
outputApp.writeProguardSeeds(closer, seedsOut);
}
}
- if (options.printMainDexList && outputApp.hasMainDexList()) {
+ if (outputApp.hasMainDexListOutput()) {
try (Closer closer = Closer.create()) {
OutputStream mainDexOut =
FileUtils.openPathWithDefault(
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 8d3d937..241746b 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -26,7 +26,7 @@
public static class Builder extends BaseCommand.Builder<R8Command, Builder> {
private final List<Path> mainDexRules = new ArrayList<>();
- private boolean minimalMainDex = false;
+ private Path mainDexListOutput = null;
private final List<Path> proguardConfigFiles = new ArrayList<>();
private Optional<Boolean> treeShaking = Optional.empty();
private Optional<Boolean> minification = Optional.empty();
@@ -77,14 +77,8 @@
return self();
}
- /**
- * Request minimal main dex generated when main dex rules are used.
- *
- * The main purpose of this is to verify that the main dex rules are sufficient
- * for running on a platform without native multi dex support.
- */
- public Builder setMinimalMainDex(boolean value) {
- minimalMainDex = value;
+ public Builder setMainDexListOutputPath(Path mainDexListOutputPath) {
+ mainDexListOutput = mainDexListOutputPath;
return self();
}
@@ -132,9 +126,9 @@
protected void validate() throws CompilationException {
super.validate();
- if (minimalMainDex && mainDexRules.isEmpty() && !getAppBuilder().hasMainDexList()) {
+ if (mainDexListOutput != null && mainDexRules.isEmpty() && !getAppBuilder().hasMainDexList()) {
throw new CompilationException(
- "Option --minimal-main-dex require --main-dex-rules");
+ "Option --main-dex-list-output require --main-dex-rules and/or --main-dex-list");
}
}
@@ -182,7 +176,7 @@
getOutputPath(),
getOutputMode(),
mainDexKeepRules,
- minimalMainDex,
+ mainDexListOutput,
configuration,
getMode(),
getMinApiLevel(),
@@ -215,13 +209,13 @@
" --no-minification # Force disable minification of names.",
" --main-dex-rules <file> # Proguard keep rules for classes to place in the",
" # primary dex file.",
- " --minimal-main-dex # Only place classes specified by --main-dex-rules",
- " # in the primary dex file.",
+ " --main-dex-list <file> # List of classes to place in the primary dex file.",
+ " --main-dex-list-output <file> # Output the full main-dex list in <file>.",
" --version # Print the version of r8.",
" --help # Print this message."));
private final ImmutableList<ProguardConfigurationRule> mainDexKeepRules;
- private final boolean minimalMainDex;
+ private final Path mainDexListOutput;
private final ProguardConfiguration proguardConfiguration;
private final boolean useTreeShaking;
private final boolean useMinification;
@@ -285,8 +279,10 @@
builder.setMinification(false);
} else if (arg.equals("--main-dex-rules")) {
builder.addMainDexRules(Paths.get(args[++i]));
- } else if (arg.equals("--minimal-main-dex")) {
- builder.setMinimalMainDex(true);
+ } else if (arg.equals("--main-dex-list")) {
+ builder.addMainDexListFiles(Paths.get(args[++i]));
+ } else if (arg.equals("--main-dex-list-output")) {
+ builder.setMainDexListOutputPath(Paths.get(args[++i]));
} else if (arg.equals("--pg-conf")) {
builder.addProguardConfigurationFiles(Paths.get(args[++i]));
} else if (arg.equals("--pg-map")) {
@@ -328,7 +324,7 @@
Path outputPath,
OutputMode outputMode,
ImmutableList<ProguardConfigurationRule> mainDexKeepRules,
- boolean minimalMainDex,
+ Path mainDexListOutput,
ProguardConfiguration proguardConfiguration,
CompilationMode mode,
int minApiLevel,
@@ -340,7 +336,7 @@
assert mainDexKeepRules != null;
assert getOutputMode() == OutputMode.Indexed : "Only regular mode is supported in R8";
this.mainDexKeepRules = mainDexKeepRules;
- this.minimalMainDex = minimalMainDex;
+ this.mainDexListOutput = mainDexListOutput;
this.proguardConfiguration = proguardConfiguration;
this.useTreeShaking = useTreeShaking;
this.useMinification = useMinification;
@@ -350,7 +346,7 @@
private R8Command(boolean printHelp, boolean printVersion) {
super(printHelp, printVersion);
mainDexKeepRules = ImmutableList.of();
- minimalMainDex = false;
+ mainDexListOutput = null;
proguardConfiguration = null;
useTreeShaking = false;
useMinification = false;
@@ -409,7 +405,10 @@
internal.classObfuscationDictionary = proguardConfiguration.getClassObfuscationDictionary();
internal.obfuscationDictionary = proguardConfiguration.getObfuscationDictionary();
internal.mainDexKeepRules = mainDexKeepRules;
- internal.minimalMainDex = minimalMainDex;
+ internal.minimalMainDex = internal.debug;
+ if (mainDexListOutput != null) {
+ internal.printMainDexListFile = mainDexListOutput;
+ }
internal.keepRules = proguardConfiguration.getRules();
internal.dontWarnPatterns = proguardConfiguration.getDontWarnPatterns();
internal.outputMode = getOutputMode();
diff --git a/src/main/java/com/android/tools/r8/bisect/Bisect.java b/src/main/java/com/android/tools/r8/bisect/Bisect.java
index 4fe3773..ee4477d 100644
--- a/src/main/java/com/android/tools/r8/bisect/Bisect.java
+++ b/src/main/java/com/android/tools/r8/bisect/Bisect.java
@@ -177,7 +177,7 @@
throws IOException, ExecutionException {
InternalOptions options = new InternalOptions();
AppInfo appInfo = new AppInfo(app);
- ApplicationWriter writer = new ApplicationWriter(app, appInfo, options, null, null, null);
+ ApplicationWriter writer = new ApplicationWriter(app, appInfo, options, null, null, null, null);
AndroidApp outApp = writer.write(null, executor);
outApp.writeToDirectory(output, OutputMode.Indexed);
}
diff --git a/src/main/java/com/android/tools/r8/code/Format10t.java b/src/main/java/com/android/tools/r8/code/Format10t.java
index 840efae..e5d2952 100644
--- a/src/main/java/com/android/tools/r8/code/Format10t.java
+++ b/src/main/java/com/android/tools/r8/code/Format10t.java
@@ -10,7 +10,7 @@
abstract class Format10t extends Base1Format {
- public final /* offset */ byte AA;
+ public /* offset */ byte AA;
// +AA | op
Format10t(int high, BytecodeStream stream) {
diff --git a/src/main/java/com/android/tools/r8/code/Format20t.java b/src/main/java/com/android/tools/r8/code/Format20t.java
index 89d740b..b3e3627 100644
--- a/src/main/java/com/android/tools/r8/code/Format20t.java
+++ b/src/main/java/com/android/tools/r8/code/Format20t.java
@@ -10,7 +10,7 @@
abstract class Format20t extends Base2Format {
- public final /* offset */ short AAAA;
+ public /* offset */ short AAAA;
// øø | op | +AAAA
Format20t(int high, BytecodeStream stream) {
diff --git a/src/main/java/com/android/tools/r8/code/Format21t.java b/src/main/java/com/android/tools/r8/code/Format21t.java
index 12b752b..26f7650 100644
--- a/src/main/java/com/android/tools/r8/code/Format21t.java
+++ b/src/main/java/com/android/tools/r8/code/Format21t.java
@@ -11,10 +11,10 @@
import com.android.tools.r8.naming.ClassNameMapper;
import java.nio.ShortBuffer;
-abstract class Format21t extends Base2Format {
+public abstract class Format21t extends Base2Format {
public final short AA;
- public final /* offset */ short BBBB;
+ public /* offset */ short BBBB;
// AA | op | +BBBB
Format21t(int high, BytecodeStream stream) {
diff --git a/src/main/java/com/android/tools/r8/code/Format22t.java b/src/main/java/com/android/tools/r8/code/Format22t.java
index 6cd51d3..3ff5190 100644
--- a/src/main/java/com/android/tools/r8/code/Format22t.java
+++ b/src/main/java/com/android/tools/r8/code/Format22t.java
@@ -11,11 +11,11 @@
import com.android.tools.r8.naming.ClassNameMapper;
import java.nio.ShortBuffer;
-abstract class Format22t extends Base2Format {
+public abstract class Format22t extends Base2Format {
public final byte A;
public final byte B;
- public final /* offset */ short CCCC;
+ public /* offset */ short CCCC;
// vB | vA | op | +CCCC
Format22t(int high, BytecodeStream stream) {
diff --git a/src/main/java/com/android/tools/r8/code/Format22x.java b/src/main/java/com/android/tools/r8/code/Format22x.java
index bca2636..fba570b 100644
--- a/src/main/java/com/android/tools/r8/code/Format22x.java
+++ b/src/main/java/com/android/tools/r8/code/Format22x.java
@@ -46,11 +46,11 @@
}
public String toString(ClassNameMapper naming) {
- return formatString("v" + AA + ", v" + BBBB);
+ return formatString("v" + AA + ", v" + (int)BBBB);
}
public String toSmaliString(ClassNameMapper naming) {
- return formatSmaliString("v" + AA + ", v" + BBBB);
+ return formatSmaliString("v" + AA + ", v" + (int)BBBB);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/code/Format30t.java b/src/main/java/com/android/tools/r8/code/Format30t.java
index 927c879..77cb49d 100644
--- a/src/main/java/com/android/tools/r8/code/Format30t.java
+++ b/src/main/java/com/android/tools/r8/code/Format30t.java
@@ -10,7 +10,7 @@
abstract class Format30t extends Base3Format {
- public final /* offset */ int AAAAAAAA;
+ public /* offset */ int AAAAAAAA;
// øø | op | AAAAlo | AAAAhi
Format30t(int high, BytecodeStream stream) {
diff --git a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
index 6bbb406..6750f4c 100644
--- a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
+++ b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
@@ -450,13 +450,15 @@
ExecutorService executor = ThreadUtils.getExecutorService(numberOfThreads);
D8Output result;
try {
- result = D8.run(
- D8Command.builder()
- .addProgramFiles(inputs)
- .setMode(mode)
- .setMinApiLevel(dexArgs.minApiLevel)
- .setMainDexListFile(mainDexList)
- .build());
+ D8Command.Builder builder = D8Command.builder();
+ builder
+ .addProgramFiles(inputs)
+ .setMode(mode)
+ .setMinApiLevel(dexArgs.minApiLevel);
+ if (mainDexList != null) {
+ builder.addMainDexListFiles(mainDexList);
+ }
+ result = D8.run(builder.build());
} finally {
executor.shutdown();
}
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index 355080f..2553650 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -29,6 +29,7 @@
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.ClassProvider;
import com.android.tools.r8.utils.ClasspathClassCollection;
+import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.LibraryClassCollection;
import com.android.tools.r8.utils.MainDexList;
@@ -45,6 +46,7 @@
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
+import java.util.stream.Collectors;
public class ApplicationReader {
@@ -149,11 +151,18 @@
if (inputApp.hasMainDexList()) {
futures.add(executorService.submit(() -> {
try {
- InputStream input = inputApp.getMainDexList(closer);
- builder.addToMainDexList(MainDexList.parse(input, itemFactory));
+ for (Resource resource : inputApp.getMainDexListResources()) {
+ InputStream input = closer.register(resource.getStream());
+ builder.addToMainDexList(MainDexList.parse(input, itemFactory));
+ }
} catch (IOException e) {
throw new RuntimeException(e);
}
+ builder.addToMainDexList(
+ inputApp.getMainDexClasses()
+ .stream()
+ .map(clazz -> itemFactory.createType(DescriptorUtils.javaTypeToDescriptor(clazz)))
+ .collect(Collectors.toList()));
}));
}
}
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 5613169..e6ec968 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.graph.DexDebugInfo;
import com.android.tools.r8.graph.DexEncodedArray;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.DexValue;
@@ -43,6 +44,7 @@
public final NamingLens namingLens;
public final byte[] proguardSeedsData;
public final InternalOptions options;
+ public DexString markerString;
private static class SortAnnotations extends MixedSectionCollection {
@@ -105,6 +107,7 @@
DexApplication application,
AppInfo appInfo,
InternalOptions options,
+ Marker marker,
byte[] deadCode,
NamingLens namingLens,
byte[] proguardSeedsData) {
@@ -113,6 +116,9 @@
this.appInfo = appInfo;
assert options != null;
this.options = options;
+ this.markerString = (marker == null)
+ ? null
+ : application.dexItemFactory.createString(marker.toString());
this.deadCode = deadCode;
this.namingLens = namingLens;
this.proguardSeedsData = proguardSeedsData;
@@ -123,6 +129,8 @@
application.timing.begin("DexApplication.write");
try {
application.dexItemFactory.sort(namingLens);
+ assert this.markerString == null || application.dexItemFactory.extractMarker() != null;
+
SortAnnotations sortAnnotations = new SortAnnotations();
application.classes().forEach((clazz) -> clazz.addDependencies(sortAnnotations));
@@ -148,7 +156,8 @@
distributor =
new VirtualFile.PackageMapDistributor(this, packageDistribution, executorService);
} else {
- distributor = new VirtualFile.FillFilesDistributor(this, options.minimalMainDex);
+ boolean minimal = options.minimalMainDex && !application.mainDexList.isEmpty();
+ distributor = new VirtualFile.FillFilesDistributor(this, minimal);
}
Map<Integer, VirtualFile> newFiles = distributor.run();
@@ -186,7 +195,7 @@
}
byte[] mainDexList = writeMainDexList();
if (mainDexList != null) {
- builder.setMainDexListData(mainDexList);
+ builder.setMainDexListOutputData(mainDexList);
}
return builder.build();
} finally {
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index 332f292..a8f56a7 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -5,8 +5,6 @@
import static com.android.tools.r8.utils.LebUtils.sizeAsUleb128;
-import com.google.common.collect.Sets;
-
import com.android.tools.r8.code.Instruction;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.AppInfo;
@@ -43,7 +41,6 @@
import com.android.tools.r8.graph.ObjectToOffsetMapping;
import com.android.tools.r8.graph.PresortedComparable;
import com.android.tools.r8.graph.ProgramClassVisitor;
-import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
@@ -51,12 +48,11 @@
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.LebUtils;
-
+import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2IntLinkedOpenHashMap;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
-
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
@@ -156,14 +152,16 @@
return this;
}
- private void rewriteCodeWithJumboStrings(IRConverter converter, DexEncodedMethod method) {
+ private void rewriteCodeWithJumboStrings(DexEncodedMethod method) {
if (method.getCode() == null) {
return;
}
DexCode code = method.getCode().asDexCode();
if (code.highestSortingString != null) {
if (mapping.getOffsetFor(code.highestSortingString) > Constants.MAX_NON_JUMBO_INDEX) {
- converter.processJumboStrings(method, mapping.getFirstJumboString());
+ JumboStringRewriter rewriter =
+ new JumboStringRewriter(method, mapping.getFirstJumboString(), options.itemFactory);
+ rewriter.rewrite();
}
}
}
@@ -179,9 +177,8 @@
return this;
}
// At least one method needs a jumbo string.
- IRConverter converter = new IRConverter(application, appInfo, options, false);
for (DexProgramClass clazz : classes) {
- clazz.forEachMethod(method -> rewriteCodeWithJumboStrings(converter, method));
+ clazz.forEachMethod(method -> rewriteCodeWithJumboStrings(method));
}
return this;
}
@@ -297,7 +294,7 @@
}
} else {
- if (method.accessFlags.isConstructor()) {
+ if (method.isInstanceInitializer()) {
throw new CompilationError(
"Interface must not have constructors: " + method.method.toSourceString());
}
diff --git a/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java b/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
new file mode 100644
index 0000000..4b97c51
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
@@ -0,0 +1,568 @@
+// Copyright (c) 2017, 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.dex;
+
+import static com.android.tools.r8.graph.DexCode.TryHandler.NO_HANDLER;
+
+import com.android.tools.r8.code.ConstString;
+import com.android.tools.r8.code.ConstStringJumbo;
+import com.android.tools.r8.code.FillArrayDataPayload;
+import com.android.tools.r8.code.Format21t;
+import com.android.tools.r8.code.Format22t;
+import com.android.tools.r8.code.Format31t;
+import com.android.tools.r8.code.Goto;
+import com.android.tools.r8.code.Goto16;
+import com.android.tools.r8.code.Goto32;
+import com.android.tools.r8.code.IfEq;
+import com.android.tools.r8.code.IfEqz;
+import com.android.tools.r8.code.IfGe;
+import com.android.tools.r8.code.IfGez;
+import com.android.tools.r8.code.IfGt;
+import com.android.tools.r8.code.IfGtz;
+import com.android.tools.r8.code.IfLe;
+import com.android.tools.r8.code.IfLez;
+import com.android.tools.r8.code.IfLt;
+import com.android.tools.r8.code.IfLtz;
+import com.android.tools.r8.code.IfNe;
+import com.android.tools.r8.code.IfNez;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.Nop;
+import com.android.tools.r8.code.SwitchPayload;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexCode.Try;
+import com.android.tools.r8.graph.DexCode.TryHandler;
+import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair;
+import com.android.tools.r8.graph.DexDebugEvent;
+import com.android.tools.r8.graph.DexDebugEvent.AdvancePC;
+import com.android.tools.r8.graph.DexDebugEvent.Default;
+import com.android.tools.r8.graph.DexDebugInfo;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexString;
+import com.google.common.collect.Lists;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+public class JumboStringRewriter {
+
+ private static class TryTargets {
+ private Instruction start;
+ private Instruction end;
+ private boolean endsAfterLastInstruction;
+
+ TryTargets(Instruction start, Instruction end, boolean endsAfterLastInstruction) {
+ assert start != null;
+ assert end != null;
+ this.start = start;
+ this.end = end;
+ this.endsAfterLastInstruction = endsAfterLastInstruction;
+ }
+
+ void replaceTarget(Instruction target, Instruction newTarget) {
+ if (start == target) {
+ start = newTarget;
+ }
+ if (end == target) {
+ end = newTarget;
+ }
+ }
+
+ int getStartOffset() {
+ return start.getOffset();
+ }
+
+ int getStartToEndDelta() {
+ if (endsAfterLastInstruction) {
+ return end.getOffset() + end.getSize() - start.getOffset();
+ }
+ return end.getOffset() - start.getOffset();
+ }
+ }
+
+ private final DexEncodedMethod method;
+ private final DexString firstJumboString;
+ private final DexItemFactory factory;
+ private final Map<Instruction, List<Instruction>> instructionTargets = new IdentityHashMap<>();
+ private final Int2ReferenceMap<Instruction> debugEventTargets
+ = new Int2ReferenceOpenHashMap<>();
+ private final Map<Instruction, Instruction> payloadToSwitch = new IdentityHashMap<>();
+ private final Map<Try, TryTargets> tryTargets = new IdentityHashMap<>();
+ private final Map<TryHandler, List<Instruction>> handlerTargets = new IdentityHashMap<>();
+
+ public JumboStringRewriter(
+ DexEncodedMethod method, DexString firstJumboString, DexItemFactory factory) {
+ this.method = method;
+ this.firstJumboString = firstJumboString;
+ this.factory = factory;
+ }
+
+ public void rewrite() {
+ // Build maps from everything in the code that uses offsets or direct addresses to reference
+ // instructions to the actual instruction referenced.
+ recordTargets();
+ // Expand the code by rewriting jumbo strings and branching instructions.
+ List<Instruction> newInstructions = expandCode();
+ // Commit to the new instruction offsets and update instructions, try-catch structures
+ // and debug info with the new offsets.
+ rewriteInstructionOffsets(newInstructions);
+ Try[] newTries = rewriteTryOffsets();
+ TryHandler[] newHandlers = rewriteHandlerOffsets();
+ DexDebugInfo newDebugInfo = rewriteDebugInfoOffsets();
+ // Set the new code on the method.
+ DexCode code = method.getCode().asDexCode();
+ method.setDexCode(new DexCode(
+ code.registerSize,
+ code.incomingRegisterSize,
+ code.outgoingRegisterSize,
+ newInstructions.toArray(new Instruction[newInstructions.size()]),
+ newTries,
+ newHandlers,
+ newDebugInfo,
+ code.highestSortingString));
+ }
+
+ private void rewriteInstructionOffsets(List<Instruction> instructions) {
+ for (Instruction instruction : instructions) {
+ if (instruction instanceof Format22t) { // IfEq, IfGe, IfGt, IfLe, IfLt, IfNe
+ Format22t condition = (Format22t) instruction;
+ int offset = instructionTargets.get(condition).get(0).getOffset() - instruction.getOffset();
+ assert Short.MIN_VALUE <= offset && offset <= Short.MAX_VALUE;
+ condition.CCCC = (short) offset;
+ } else if (instruction instanceof Format21t) { // IfEqz, IfGez, IfGtz, IfLez, IfLtz, IfNez
+ Format21t condition = (Format21t) instruction;
+ int offset = instructionTargets.get(condition).get(0).getOffset() - instruction.getOffset();
+ assert Short.MIN_VALUE <= offset && offset <= Short.MAX_VALUE;
+ condition.BBBB = (short) offset;
+ } else if (instruction instanceof Goto) {
+ Goto jump = (Goto) instruction;
+ int offset = instructionTargets.get(jump).get(0).getOffset() - instruction.getOffset();
+ assert Byte.MIN_VALUE <= offset && offset <= Byte.MAX_VALUE;
+ jump.AA = (byte) offset;
+ } else if (instruction instanceof Goto16) {
+ Goto16 jump = (Goto16) instruction;
+ int offset = instructionTargets.get(jump).get(0).getOffset() - instruction.getOffset();
+ assert Short.MIN_VALUE <= offset && offset <= Short.MAX_VALUE;
+ jump.AAAA = (short) offset;
+ } else if (instruction instanceof Goto32) {
+ Goto32 jump = (Goto32) instruction;
+ int offset = instructionTargets.get(jump).get(0).getOffset() - instruction.getOffset();
+ jump.AAAAAAAA = offset;
+ } else if (instruction instanceof Format31t) { // FillArrayData, SparseSwitch, PackedSwitch
+ Format31t payloadUser = (Format31t) instruction;
+ int offset =
+ instructionTargets.get(payloadUser).get(0).getOffset() - instruction.getOffset();
+ payloadUser.setPayloadOffset(offset);
+ } else if (instruction instanceof SwitchPayload) {
+ SwitchPayload payload = (SwitchPayload) instruction;
+ Instruction switchInstruction = payloadToSwitch.get(payload);
+ List<Instruction> switchTargets = instructionTargets.get(payload);
+ int[] targets = payload.switchTargetOffsets();
+ for (int i = 0; i < switchTargets.size(); i++) {
+ Instruction target = switchTargets.get(i);
+ targets[i] = target.getOffset() - switchInstruction.getOffset();
+ }
+ }
+ }
+ }
+
+ private Try[] rewriteTryOffsets() {
+ DexCode code = method.getCode().asDexCode();
+ Try[] result = new Try[code.tries.length];
+ for (int i = 0; i < code.tries.length; i++) {
+ Try theTry = code.tries[i];
+ TryTargets targets = tryTargets.get(theTry);
+ result[i] = new Try(targets.getStartOffset(), targets.getStartToEndDelta(), -1);
+ result[i].handlerIndex = theTry.handlerIndex;
+ }
+ return result;
+ }
+
+ private TryHandler[] rewriteHandlerOffsets() {
+ DexCode code = method.getCode().asDexCode();
+ if (code.handlers == null) {
+ return null;
+ }
+ TryHandler[] result = new TryHandler[code.handlers.length];
+ for (int i = 0; i < code.handlers.length; i++) {
+ TryHandler handler = code.handlers[i];
+ List<Instruction> targets = handlerTargets.get(handler);
+ Iterator<Instruction> it = targets.iterator();
+ int catchAllAddr = NO_HANDLER;
+ if (handler.catchAllAddr != NO_HANDLER) {
+ catchAllAddr = it.next().getOffset();
+ }
+ TypeAddrPair[] newPairs = new TypeAddrPair[handler.pairs.length];
+ for (int j = 0; j < handler.pairs.length; j++) {
+ TypeAddrPair pair = handler.pairs[j];
+ newPairs[j] = new TypeAddrPair(pair.type, it.next().getOffset());
+ }
+ result[i] = new TryHandler(newPairs, catchAllAddr);
+ }
+ return result;
+ }
+
+ private DexDebugInfo rewriteDebugInfoOffsets() {
+ DexCode code = method.getCode().asDexCode();
+ if (debugEventTargets.size() != 0) {
+ int lastOriginalOffset = 0;
+ int lastNewOffset = 0;
+ List<DexDebugEvent> events = new ArrayList<>();
+ for (DexDebugEvent event : code.getDebugInfo().events) {
+ if (event instanceof AdvancePC) {
+ AdvancePC advance = (AdvancePC) event;
+ lastOriginalOffset += advance.delta;
+ Instruction target = debugEventTargets.get(lastOriginalOffset);
+ int pcDelta = target.getOffset() - lastNewOffset;
+ addAdvancementEvents(0, pcDelta, events);
+ lastNewOffset = target.getOffset();
+ } else if (event instanceof Default) {
+ Default defaultEvent = (Default) event;
+ lastOriginalOffset += defaultEvent.getPCDelta();
+ Instruction target = debugEventTargets.get(lastOriginalOffset);
+ int lineDelta = defaultEvent.getLineDelta();
+ int pcDelta = target.getOffset() - lastNewOffset;
+ addAdvancementEvents(lineDelta, pcDelta, events);
+ lastNewOffset = target.getOffset();
+ } else {
+ events.add(event);
+ }
+ }
+ return new DexDebugInfo(
+ code.getDebugInfo().startLine,
+ code.getDebugInfo().parameters,
+ events.toArray(new DexDebugEvent[events.size()]));
+ }
+ return code.getDebugInfo();
+ }
+
+ private void addAdvancementEvents(int lineDelta, int pcDelta, List<DexDebugEvent> events) {
+ if (lineDelta < Constants.DBG_LINE_BASE
+ || lineDelta - Constants.DBG_LINE_BASE >= Constants.DBG_LINE_RANGE) {
+ events.add(factory.createAdvanceLine(lineDelta));
+ lineDelta = 0;
+ if (pcDelta == 0) {
+ return;
+ }
+ }
+ if (pcDelta >= Constants.DBG_ADDRESS_RANGE) {
+ events.add(factory.createAdvancePC(pcDelta));
+ pcDelta = 0;
+ if (lineDelta == 0) {
+ return;
+ }
+ }
+ int specialOpcode =
+ 0x0a + (lineDelta - Constants.DBG_LINE_BASE) + Constants.DBG_LINE_RANGE * pcDelta;
+ assert specialOpcode >= 0x0a;
+ assert specialOpcode <= 0xff;
+ events.add(factory.createDefault(specialOpcode));
+ }
+
+ private List<Instruction> expandCode() {
+ LinkedList<Instruction> instructions = new LinkedList<>();
+ Collections.addAll(instructions, method.getCode().asDexCode().instructions);
+ int offsetDelta;
+ do {
+ ListIterator<Instruction> it = instructions.listIterator();
+ offsetDelta = 0;
+ while (it.hasNext()) {
+ Instruction instruction = it.next();
+ instruction.setOffset(instruction.getOffset() + offsetDelta);
+ if (instruction instanceof ConstString) {
+ ConstString string = (ConstString) instruction;
+ if (string.getString().compareTo(firstJumboString) >= 0) {
+ ConstStringJumbo jumboString = new ConstStringJumbo(string.AA, string.getString());
+ jumboString.setOffset(string.getOffset());
+ offsetDelta++;
+ it.set(jumboString);
+ replaceTarget(instruction, jumboString);
+ }
+ } else if (instruction instanceof Format22t) { // IfEq, IfGe, IfGt, IfLe, IfLt, IfNe
+ Format22t condition = (Format22t) instruction;
+ int offset =
+ instructionTargets.get(condition).get(0).getOffset() - instruction.getOffset();
+ if (Short.MIN_VALUE > offset || offset > Short.MAX_VALUE) {
+ Format22t newCondition = null;
+ switch (condition.getType().inverted()) {
+ case EQ:
+ newCondition = new IfEq(condition.A, condition.B, 0);
+ break;
+ case GE:
+ newCondition = new IfGe(condition.A, condition.B, 0);
+ break;
+ case GT:
+ newCondition = new IfGt(condition.A, condition.B, 0);
+ break;
+ case LE:
+ newCondition = new IfLe(condition.A, condition.B, 0);
+ break;
+ case LT:
+ newCondition = new IfLt(condition.A, condition.B, 0);
+ break;
+ case NE:
+ newCondition = new IfNe(condition.A, condition.B, 0);
+ break;
+ }
+ offsetDelta = rewriteIfToIfAndGoto(offsetDelta, it, condition, newCondition);
+ }
+ } else if (instruction instanceof Format21t) { // IfEqz, IfGez, IfGtz, IfLez, IfLtz, IfNez
+ Format21t condition = (Format21t) instruction;
+ int offset =
+ instructionTargets.get(condition).get(0).getOffset() - instruction.getOffset();
+ if (Short.MIN_VALUE > offset || offset > Short.MAX_VALUE) {
+ Format21t newCondition = null;
+ switch (condition.getType().inverted()) {
+ case EQ:
+ newCondition = new IfEqz(condition.AA, 0);
+ break;
+ case GE:
+ newCondition = new IfGez(condition.AA, 0);
+ break;
+ case GT:
+ newCondition = new IfGtz(condition.AA, 0);
+ break;
+ case LE:
+ newCondition = new IfLez(condition.AA, 0);
+ break;
+ case LT:
+ newCondition = new IfLtz(condition.AA, 0);
+ break;
+ case NE:
+ newCondition = new IfNez(condition.AA, 0);
+ break;
+ }
+ offsetDelta = rewriteIfToIfAndGoto(offsetDelta, it, condition, newCondition);
+ }
+ } else if (instruction instanceof Goto) {
+ Goto jump = (Goto) instruction;
+ int offset =
+ instructionTargets.get(jump).get(0).getOffset() - instruction.getOffset();
+ if (Byte.MIN_VALUE > offset || offset > Byte.MAX_VALUE) {
+ Instruction newJump;
+ if (Short.MIN_VALUE > offset || offset > Short.MAX_VALUE) {
+ newJump = new Goto32(offset);
+ } else {
+ newJump = new Goto16(offset);
+ }
+ newJump.setOffset(jump.getOffset());
+ it.set(newJump);
+ offsetDelta += (newJump.getSize() - jump.getSize());
+ replaceTarget(jump, newJump);
+ List<Instruction> targets = instructionTargets.remove(jump);
+ instructionTargets.put(newJump, targets);
+ }
+ } else if (instruction instanceof Goto16) {
+ Goto16 jump = (Goto16) instruction;
+ int offset =
+ instructionTargets.get(jump).get(0).getOffset() - instruction.getOffset();
+ if (Short.MIN_VALUE > offset || offset > Short.MAX_VALUE) {
+ Instruction newJump = new Goto32(offset);
+ newJump.setOffset(jump.getOffset());
+ it.set(newJump);
+ offsetDelta += (newJump.getSize() - jump.getSize());
+ replaceTarget(jump, newJump);
+ List<Instruction> targets = instructionTargets.remove(jump);
+ instructionTargets.put(newJump, targets);
+ }
+ } else if (instruction instanceof Goto32) {
+ // Instruction big enough for any offset.
+ } else if (instruction instanceof Format31t) { // FillArrayData, SparseSwitch, PackedSwitch
+ // Instruction big enough for any offset.
+ } else if (instruction instanceof SwitchPayload
+ || instruction instanceof FillArrayDataPayload) {
+ if (instruction.getOffset() % 2 != 0) {
+ offsetDelta++;
+ it.previous();
+ Nop nop = new Nop();
+ nop.setOffset(instruction.getOffset());
+ it.add(nop);
+ it.next();
+ instruction.setOffset(instruction.getOffset() + 1);
+ }
+ // Instruction big enough for any offset.
+ }
+ }
+ } while (offsetDelta > 0);
+ return instructions;
+ }
+
+ private int rewriteIfToIfAndGoto(
+ int offsetDelta,
+ ListIterator<Instruction> it,
+ Instruction condition,
+ Instruction newCondition) {
+ int jumpOffset = condition.getOffset() + condition.getSize();
+ Goto32 jump = new Goto32(0);
+ jump.setOffset(jumpOffset);
+ newCondition.setOffset(condition.getOffset());
+ it.set(newCondition);
+ replaceTarget(condition, newCondition);
+ it.add(jump);
+ offsetDelta += jump.getSize();
+ instructionTargets.put(jump, instructionTargets.remove(condition));
+ Instruction fallthroughInstruction = it.next();
+ instructionTargets.put(newCondition, Lists.newArrayList(fallthroughInstruction));
+ it.previous();
+ return offsetDelta;
+ }
+
+ private void replaceTarget(Instruction target, Instruction newTarget) {
+ for (List<Instruction> instructions : instructionTargets.values()) {
+ instructions.replaceAll((i) -> i == target ? newTarget : i);
+ }
+ for (Int2ReferenceMap.Entry<Instruction> entry : debugEventTargets.int2ReferenceEntrySet()) {
+ if (entry.getValue() == target) {
+ entry.setValue(newTarget);
+ }
+ }
+ for (Entry<Try, TryTargets> entry : tryTargets.entrySet()) {
+ entry.getValue().replaceTarget(target, newTarget);
+ }
+ for (List<Instruction> instructions : handlerTargets.values()) {
+ instructions.replaceAll((i) -> i == target ? newTarget : i);
+ }
+ }
+
+ private void recordInstructionTargets(Int2ReferenceMap<Instruction> offsetToInstruction) {
+ Instruction[] instructions = method.getCode().asDexCode().instructions;
+ for (Instruction instruction : instructions) {
+ if (instruction instanceof Format22t) { // IfEq, IfGe, IfGt, IfLe, IfLt, IfNe
+ Format22t condition = (Format22t) instruction;
+ Instruction target = offsetToInstruction.get(condition.getOffset() + condition.CCCC);
+ assert target != null;
+ instructionTargets.put(instruction, Lists.newArrayList(target));
+ } else if (instruction instanceof Format21t) { // IfEqz, IfGez, IfGtz, IfLez, IfLtz, IfNez
+ Format21t condition = (Format21t) instruction;
+ Instruction target = offsetToInstruction.get(condition.getOffset() + condition.BBBB);
+ assert target != null;
+ instructionTargets.put(instruction, Lists.newArrayList(target));
+ } else if (instruction instanceof Goto) {
+ Goto jump = (Goto) instruction;
+ Instruction target = offsetToInstruction.get(jump.getOffset() + jump.AA);
+ assert target != null;
+ instructionTargets.put(instruction, Lists.newArrayList(target));
+ } else if (instruction instanceof Goto16) {
+ Goto16 jump = (Goto16) instruction;
+ Instruction target = offsetToInstruction.get(jump.getOffset() + jump.AAAA);
+ assert target != null;
+ instructionTargets.put(instruction, Lists.newArrayList(target));
+ } else if (instruction instanceof Goto32) {
+ Goto32 jump = (Goto32) instruction;
+ Instruction target = offsetToInstruction.get(jump.getOffset() + jump.AAAAAAAA);
+ assert target != null;
+ instructionTargets.put(instruction, Lists.newArrayList(target));
+ } else if (instruction instanceof Format31t) { // FillArrayData, SparseSwitch, PackedSwitch
+ Format31t offsetInstruction = (Format31t) instruction;
+ Instruction target = offsetToInstruction.get(
+ offsetInstruction.getOffset() + offsetInstruction.getPayloadOffset());
+ assert target != null;
+ instructionTargets.put(instruction, Lists.newArrayList(target));
+ } else if (instruction instanceof SwitchPayload) {
+ SwitchPayload payload = (SwitchPayload) instruction;
+ int[] targetOffsets = payload.switchTargetOffsets();
+ int switchOffset = payloadToSwitch.get(instruction).getOffset();
+ List<Instruction> targets = new ArrayList<>();
+ for (int i = 0; i < targetOffsets.length; i++) {
+ Instruction target = offsetToInstruction.get(switchOffset + targetOffsets[i]);
+ assert target != null;
+ targets.add(target);
+ }
+ instructionTargets.put(instruction, targets);
+ }
+ }
+ }
+
+ private void recordDebugEventTargets(Int2ReferenceMap<Instruction> offsetToInstruction) {
+ DexDebugInfo debugInfo = method.getCode().asDexCode().getDebugInfo();
+ if (debugInfo != null) {
+ int address = 0;
+ for (DexDebugEvent event : debugInfo.events) {
+ if (event instanceof AdvancePC) {
+ AdvancePC advance = (AdvancePC) event;
+ address += advance.delta;
+ Instruction target = offsetToInstruction.get(address);
+ assert target != null;
+ debugEventTargets.put(address, target);
+ } else if (event instanceof Default) {
+ Default defaultEvent = (Default) event;
+ address += defaultEvent.getPCDelta();
+ Instruction target = offsetToInstruction.get(address);
+ assert target != null;
+ debugEventTargets.put(address, target);
+ }
+ }
+ }
+ }
+
+ private void recordTryAndHandlerTargets(
+ Int2ReferenceMap<Instruction> offsetToInstruction,
+ Instruction lastInstruction) {
+ DexCode code = method.getCode().asDexCode();
+ for (Try theTry : code.tries) {
+ Instruction start = offsetToInstruction.get(theTry.startAddress);
+ int endAddress = theTry.startAddress + theTry.instructionCount;
+ TryTargets targets;
+ if (endAddress > lastInstruction.getOffset()) {
+ targets = new TryTargets(start, lastInstruction, true);
+ } else {
+ Instruction end = offsetToInstruction.get(endAddress);
+ targets = new TryTargets(start, end, false);
+ }
+ assert theTry.startAddress == targets.getStartOffset();
+ assert theTry.instructionCount == targets.getStartToEndDelta();
+ tryTargets.put(theTry, targets);
+ }
+ if (code.handlers != null) {
+ for (TryHandler handler : code.handlers) {
+ List<Instruction> targets = new ArrayList<>();
+ if (handler.catchAllAddr != NO_HANDLER) {
+ Instruction target = offsetToInstruction.get(handler.catchAllAddr);
+ assert target != null;
+ targets.add(target);
+ }
+ for (TypeAddrPair pair : handler.pairs) {
+ Instruction target = offsetToInstruction.get(pair.addr);
+ assert target != null;
+ targets.add(target);
+ }
+ handlerTargets.put(handler, targets);
+ }
+ }
+ }
+
+ private void recordTargets() {
+ Int2ReferenceMap<Instruction> offsetToInstruction = new Int2ReferenceOpenHashMap<>();
+ Instruction[] instructions = method.getCode().asDexCode().instructions;
+ boolean containsPayloads = false;
+ for (Instruction instruction : instructions) {
+ offsetToInstruction.put(instruction.getOffset(), instruction);
+ if (instruction instanceof Format31t) { // FillArrayData, SparseSwitch, PackedSwitch
+ containsPayloads = true;
+ }
+ }
+ if (containsPayloads) {
+ for (Instruction instruction : instructions) {
+ if (instruction instanceof Format31t) { // FillArrayData, SparseSwitch, PackedSwitch
+ Instruction payload =
+ offsetToInstruction.get(instruction.getOffset() + instruction.getPayloadOffset());
+ assert payload != null;
+ payloadToSwitch.put(payload, instruction);
+ }
+ }
+ }
+ recordInstructionTargets(offsetToInstruction);
+ recordDebugEventTargets(offsetToInstruction);
+ Instruction lastInstruction = instructions[instructions.length - 1];
+ recordTryAndHandlerTargets(offsetToInstruction, lastInstruction);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/dex/Marker.java b/src/main/java/com/android/tools/r8/dex/Marker.java
new file mode 100644
index 0000000..4762fd4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/dex/Marker.java
@@ -0,0 +1,115 @@
+// Copyright (c) 2017, the Rex 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.dex;
+
+import org.json.simple.JSONObject;
+import org.json.simple.parser.JSONParser;
+import org.json.simple.parser.ParseException;
+
+import java.util.Map;
+import java.util.TreeMap;
+
+
+/**
+ * Abstraction for hidden dex marker intended for the main dex file.
+ */
+public class Marker {
+
+ public enum Tool {D8, R8}
+
+ private static final String kPrefix = "~~";
+ private static final String kD8prefix = kPrefix + Tool.D8 + "{";
+ private static final String kR8prefix = kPrefix + Tool.R8 + "{";
+
+ private final TreeMap<String, Object> content;
+ private final Tool tool;
+
+ public Marker(Tool tool) {
+ this.tool = tool;
+ this.content = new TreeMap<>();
+ }
+
+ private Marker(Tool tool, JSONObject object) {
+ this.tool = tool;
+ content = new TreeMap<>();
+ // This loop is necessary to make the type checker to shut up.
+ for (Object e : object.entrySet()) {
+ Map.Entry entry = (Map.Entry) e;
+ content.put(String.valueOf(entry.getKey()), entry.getValue());
+ }
+ }
+
+ public Marker put(String key, int value) {
+ // value is converted to Long ensuring equals works with the parsed json string.
+ return internalPut(key, new Long(value));
+ }
+
+ public Marker put(String key, String value) {
+ return internalPut(key, value);
+ }
+
+ private Marker internalPut(String key, Object value) {
+ assert (key != null) && (value != null);
+ assert !content.containsKey(key);
+ content.put(key, value);
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ // The JSONObject does not support a predictable sorted serialization of the object.
+ // Therefore, a TreeMap is used and iteration is over the keySet.
+ StringBuffer sb = new StringBuffer(kPrefix + tool);
+ boolean first = true;
+ sb.append('{');
+ for (String key : content.keySet()) {
+ if (first) {
+ first = false;
+ } else {
+ sb.append(',');
+ }
+ sb.append(JSONObject.toString(key, content.get(key)));
+ }
+ sb.append('}');
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof Marker) {
+ Marker other = (Marker) obj;
+ return (tool == other.tool) && content.equals(other.content);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return tool.hashCode() + 3 * content.hashCode();
+ }
+
+ // Try to parse str as a marker.
+ // Returns null if parsing fails.
+ public static Marker parse(String str) {
+ if (str.startsWith(kD8prefix)) {
+ return internalParse(Tool.D8, str.substring(kD8prefix.length() - 1));
+ }
+ if (str.startsWith(kR8prefix)) {
+ return internalParse(Tool.R8, str.substring(kR8prefix.length() - 1));
+ }
+ return null;
+ }
+
+ private static Marker internalParse(Tool tool, String str) {
+ try {
+ Object result = new JSONParser().parse(str);
+ if (result instanceof JSONObject) {
+ return new Marker(tool, (JSONObject) result);
+ }
+ } catch (ParseException e) {
+ // Fall through.
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index 3432b62..f4fa536 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -318,7 +318,12 @@
// 1. Place the remaining files based on their packages in sorted order.
// Start with 1 file. The package populator will add more if needed.
- nameToFileMap.put(0, new VirtualFile(0, writer.namingLens));
+ VirtualFile main = new VirtualFile(0, writer.namingLens);
+ nameToFileMap.put(0, main);
+ if (writer.markerString != null) {
+ main.transaction.addString(writer.markerString);
+ main.commitTransaction();
+ }
// First fill required classes into the main dex file.
fillForMainDexList(classes);
@@ -925,7 +930,7 @@
} else {
assert clazz.superType != null;
// We don't have a package, add this to a list of classes that we will add last.
- assert current.transaction.isEmpty();
+ assert current.transaction.classes.isEmpty();
nonPackageClasses.add(clazz);
continue;
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 3601ee9..afd923c 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -331,6 +331,10 @@
return null;
}
+ public void registerNewType(DexType newType, DexType superType) {
+ // We do not track subtyping relationships in the basic AppInfo. So do nothing.
+ }
+
public List<DexClass> getSuperTypeClasses(DexType type) {
List<DexClass> result = new ArrayList<>();
do {
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index 503bc89..9c87b61 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -227,6 +227,12 @@
}
@Override
+ public void registerNewType(DexType newType, DexType superType) {
+ // Register the relationship between this type and its superType.
+ superType.addDirectSubtype(newType);
+ }
+
+ @Override
public boolean hasSubtyping() {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 04c61a4..d09d395 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -175,12 +175,8 @@
}
public DexEncodedMethod getClassInitializer() {
- for (DexEncodedMethod method : directMethods()) {
- if (method.accessFlags.isConstructor() && method.accessFlags.isStatic()) {
- return method;
- }
- }
- return null;
+ return Arrays.stream(directMethods()).filter(DexEncodedMethod::isClassInitializer).findAny()
+ .orElse(null);
}
public Resource.Kind getOrigin() {
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index dc3ae37..7915df3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -175,6 +175,9 @@
public String toString(DexEncodedMethod method, ClassNameMapper naming) {
StringBuilder builder = new StringBuilder();
+ if (method != null) {
+ builder.append(method.toSourceString()).append("\n");
+ }
builder.append("registers: ").append(registerSize);
builder.append(", inputs: ").append(incomingRegisterSize);
builder.append(", outputs: ").append(outgoingRegisterSize).append("\n");
@@ -311,7 +314,7 @@
public static final int NO_INDEX = -1;
- private final int handlerOffset;
+ public final int handlerOffset;
public /* offset */ int startAddress;
public /* offset */ int instructionCount;
public int handlerIndex;
@@ -375,7 +378,7 @@
public static final int NO_HANDLER = -1;
public final TypeAddrPair[] pairs;
- public /* offset */ int catchAllAddr;
+ public final /* offset */ int catchAllAddr;
public TryHandler(TypeAddrPair[] pairs, int catchAllAddr) {
this.pairs = pairs;
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
index 39ec7d0..7bfa534 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
@@ -5,6 +5,8 @@
import com.android.tools.r8.ir.code.MoveType;
import com.google.common.collect.ImmutableMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -44,6 +46,7 @@
private boolean prologueEnd = false;
private boolean epilogueBegin = false;
private final Map<Integer, LocalEntry> locals = new HashMap<>();
+ private final Int2ReferenceMap<DebugLocalInfo> arguments = new Int2ReferenceArrayMap<>();
// Delayed construction of an entry. Is finalized once locals information has been collected.
private DexDebugEntry pending = null;
@@ -65,7 +68,7 @@
if (!method.accessFlags.isStatic()) {
DexString name = factory.thisName;
DexType type = method.method.getHolder();
- startLocal(argumentRegister, name, type, null);
+ startArgument(argumentRegister, name, type);
argumentRegister += MoveType.fromDexType(type).requiredRegisters();
}
DexType[] types = method.method.proto.parameters.values;
@@ -73,9 +76,9 @@
for (int i = 0; i < types.length; i++) {
// If null, the parameter has a parameterized type and the local is introduced in the stream.
if (names[i] != null) {
- startLocal(argumentRegister, names[i], types[i], null);
- argumentRegister += MoveType.fromDexType(types[i]).requiredRegisters();
+ startArgument(argumentRegister, names[i], types[i]);
}
+ argumentRegister += MoveType.fromDexType(types[i]).requiredRegisters();
}
currentLine = info.startLine;
for (DexDebugEvent event : info.events) {
@@ -83,6 +86,10 @@
}
}
+ public Int2ReferenceMap<DebugLocalInfo> getArguments() {
+ return arguments;
+ }
+
public void setFile(DexString file) {
currentFile = file;
}
@@ -104,6 +111,12 @@
epilogueBegin = true;
}
+ public void startArgument(int register, DexString name, DexType type) {
+ DebugLocalInfo argument = canonicalize(name, type, null);
+ arguments.put(register, argument);
+ getEntry(register).set(argument);
+ }
+
public void startLocal(int register, DexString name, DexType type, DexString signature) {
getEntry(register).set(canonicalize(name, type, signature));
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
index 572e655..db99359 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
@@ -31,10 +31,9 @@
public abstract void addToBuilder(DexDebugEntryBuilder builder);
-
public static class AdvancePC extends DexDebugEvent {
- final int delta;
+ public final int delta;
public void writeOn(DebugBytecodeWriter writer, ObjectToOffsetMapping mapping) {
writer.putByte(Constants.DBG_ADVANCE_PC);
@@ -350,6 +349,16 @@
builder.setPosition(address, line);
}
+ public int getPCDelta() {
+ int adjustedOpcode = value - Constants.DBG_FIRST_SPECIAL;
+ return adjustedOpcode / Constants.DBG_LINE_RANGE;
+ }
+
+ public int getLineDelta() {
+ int adjustedOpcode = value - Constants.DBG_FIRST_SPECIAL;
+ return Constants.DBG_LINE_BASE + (adjustedOpcode % Constants.DBG_LINE_RANGE);
+ }
+
public String toString() {
return "DEFAULT " + value;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 947ab44..e66b984 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -98,22 +98,21 @@
return compilationState != CompilationState.NOT_PROCESSED;
}
- public boolean cannotInline() {
- return compilationState == CompilationState.NOT_PROCESSED
- || compilationState == CompilationState.PROCESSED_NOT_INLINING_CANDIDATE;
+ public boolean isInstanceInitializer() {
+ return accessFlags.isConstructor() && !accessFlags.isStatic();
+ }
+
+ public boolean isClassInitializer() {
+ return accessFlags.isConstructor() && accessFlags.isStatic();
}
public boolean isInliningCandidate(DexEncodedMethod container, boolean alwaysInline,
AppInfoWithSubtyping appInfo) {
- if (container.accessFlags.isStatic() && container.accessFlags.isConstructor()) {
+ if (isClassInitializer()) {
// This will probably never happen but never inline a class initializer.
return false;
}
if (alwaysInline) {
- // Only inline constructor iff holder classes are equal.
- if (!accessFlags.isStatic() && accessFlags.isConstructor()) {
- return container.method.getHolder() == method.getHolder();
- }
return true;
}
switch (compilationState) {
@@ -172,14 +171,6 @@
code = builder.build(method.getArity());
}
- // Replaces the dex code in the method by setting code to result of compiling the IR.
- public void setCode(IRCode ir, RegisterAllocator registerAllocator,
- DexItemFactory dexItemFactory, DexString firstJumboString) {
- final DexBuilder builder =
- new DexBuilder(ir, registerAllocator, dexItemFactory, firstJumboString);
- code = builder.build(method.getArity());
- }
-
public String toString() {
return "Encoded method " + method;
}
@@ -207,6 +198,10 @@
return code;
}
+ public void setDexCode(DexCode code) {
+ this.code = code;
+ }
+
public void removeCode() {
code = null;
}
@@ -307,10 +302,10 @@
itemFactory.stringType),
itemFactory.constructorMethodName);
DexCode code;
- if (accessFlags.isConstructor() && !accessFlags.isStatic()) {
+ if (isInstanceInitializer()) {
// The Java VM Spec requires that a constructor calls an initializer from the super class
// or another constructor from the current class. For simplicity we do the latter by just
- // calling outself. This is ok, as the constructor always throws before the recursive call.
+ // calling ourself. This is ok, as the constructor always throws before the recursive call.
code = generateCodeFromTemplate(3, 2, new ConstStringJumbo(0, tag),
new ConstStringJumbo(1, message),
new InvokeStatic(2, logMethod, 0, 1, 0, 0, 0),
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 6685da7..339f4c0 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.graph;
import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.dex.Marker;
import com.android.tools.r8.graph.DexDebugEvent.AdvanceLine;
import com.android.tools.r8.graph.DexDebugEvent.AdvancePC;
import com.android.tools.r8.graph.DexDebugEvent.Default;
@@ -289,6 +290,18 @@
return canonicalize(strings, new DexString(source));
}
+ // Debugging support to extract marking string.
+ synchronized public Marker extractMarker() {
+ // This is slow but it is not needed for any production code yet.
+ for (DexString dexString : strings.keySet()) {
+ Marker result = Marker.parse(dexString.toString());
+ if (result != null) {
+ return result;
+ }
+ }
+ return null;
+ }
+
public DexType createType(DexString descriptor) {
assert !sorted;
DexType type = new DexType(descriptor);
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index d3a5676..60a7319 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -148,7 +148,6 @@
public void addStaticMethod(DexEncodedMethod staticMethod) {
assert staticMethod.accessFlags.isStatic();
assert !staticMethod.accessFlags.isPrivate();
- assert !staticMethod.accessFlags.isConstructor();
directMethods = Arrays.copyOf(directMethods, directMethods.length + 1);
directMethods[directMethods.length - 1] = staticMethod;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexString.java b/src/main/java/com/android/tools/r8/graph/DexString.java
index e6914ec..75a6d35 100644
--- a/src/main/java/com/android/tools/r8/graph/DexString.java
+++ b/src/main/java/com/android/tools/r8/graph/DexString.java
@@ -21,7 +21,7 @@
this.content = content;
}
- DexString(String string) {
+ public DexString(String string) {
this.size = string.length();
this.content = encode(string);
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index e06c0ad..a371684 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -453,4 +453,9 @@
}
return getPackageOrName(false);
}
+
+ public boolean isImmediateSubtypeOf(DexType type) {
+ assert hierarchyLevel != UNKNOWN_LEVEL;
+ return type.directSubtypes.contains(this);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java b/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
index 74a0800..c8157d6 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
@@ -90,8 +90,7 @@
for (IndexedDexItem item : items) {
item.assignVirtualFileIndex(virtualFileId, index);
// For strings collect the first jumbo string (if any).
- if (index > Constants.MAX_NON_JUMBO_INDEX) {
- assert item instanceof DexString;
+ if ((index > Constants.MAX_NON_JUMBO_INDEX) && (item instanceof DexString)) {
if (index == Constants.FIRST_JUMBO_INDEX) {
firstJumboString = (DexString) item;
}
diff --git a/src/main/java/com/android/tools/r8/graph/PresortedComparable.java b/src/main/java/com/android/tools/r8/graph/PresortedComparable.java
index 3caf8f2..eb130d6 100644
--- a/src/main/java/com/android/tools/r8/graph/PresortedComparable.java
+++ b/src/main/java/com/android/tools/r8/graph/PresortedComparable.java
@@ -13,4 +13,8 @@
// Layered comparison methods that make use of indices for subpart comparisons. These rely
// on subparts already being sorted and having indices assigned.
int layeredCompareTo(T other, NamingLens namingLens);
+
+ static <T extends PresortedComparable<T>> int slowCompare(T a, T b) {
+ return a.slowCompareTo(b);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
index c2cd2ea..e787e59 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
@@ -12,7 +12,10 @@
import com.android.tools.r8.code.AgetWide;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
import java.util.Arrays;
@@ -114,4 +117,9 @@
public ArrayGet asArrayGet() {
return this;
}
+
+ @Override
+ public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
+ return Constraint.ALWAYS;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
index 40bd443..88325a4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
@@ -5,7 +5,10 @@
package com.android.tools.r8.ir.code;
import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
public class ArrayLength extends Instruction {
@@ -79,4 +82,9 @@
assert other.isArrayLength();
return 0;
}
+
+ @Override
+ public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
+ return Constraint.ALWAYS;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
index 20ce7e8..11694a3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
@@ -12,7 +12,10 @@
import com.android.tools.r8.code.AputWide;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
import com.android.tools.r8.utils.InternalOptions;
import java.util.List;
@@ -135,4 +138,9 @@
public ArrayPut asArrayPut() {
return this;
}
+
+ @Override
+ public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
+ return Constraint.ALWAYS;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
index f93f996..968e560 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
@@ -106,7 +106,7 @@
if (current == null) {
throw new IllegalStateException();
}
- assert current.outValue() == null || current.outValue().numberOfAllUsers() == 0;
+ assert current.outValue() == null || !current.outValue().isUsed();
for (int i = 0; i < current.inValues().size(); i++) {
Value value = current.inValues().get(i);
value.removeUser(current);
@@ -323,7 +323,7 @@
while (inlineeIterator.hasNext()) {
Instruction instruction = inlineeIterator.next();
if (instruction.isArgument()) {
- assert instruction.outValue().numberOfAllUsers() == 0;
+ assert !instruction.outValue().isUsed();
assert instruction.outValue() == arguments.get(index++);
inlineeIterator.remove();
}
@@ -349,6 +349,8 @@
List<Value> arguments = inlinee.collectArguments();
assert invoke.inValues().size() == arguments.size();
for (int i = 0; i < invoke.inValues().size(); i++) {
+ // TODO(zerny): Support inlining in --debug mode.
+ assert arguments.get(i).getDebugInfo() == null;
if ((i == 0) && (downcast != null)) {
Value invokeValue = invoke.inValues().get(0);
Value receiverValue = arguments.get(0);
diff --git a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
index 573db67..0c0797f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
@@ -7,8 +7,10 @@
import com.android.tools.r8.code.MoveObject;
import com.android.tools.r8.code.MoveObjectFrom16;
import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
public class CheckCast extends Instruction {
@@ -94,4 +96,9 @@
public String toString() {
return super.toString() + "; " + type;
}
+
+ @Override
+ public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
+ return Constraint.classIsVisible(holder, type, info);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
index 3a3626e..efde56a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
@@ -5,8 +5,10 @@
package com.android.tools.r8.ir.code;
import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import com.android.tools.r8.utils.InternalOptions;
public class ConstClass extends ConstInstruction {
@@ -76,4 +78,9 @@
public ConstClass asConstClass() {
return this;
}
+
+ @Override
+ public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
+ return Constraint.classIsVisible(holder, clazz, info);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstString.java b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
index 82db97a..bb1ff32 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
-import com.android.tools.r8.code.ConstStringJumbo;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -30,11 +29,7 @@
public void buildDex(DexBuilder builder) {
builder.registerStringReference(value);
int dest = builder.allocatedRegister(dest(), getNumber());
- if (builder.isJumboString(value)) {
- builder.add(this, new ConstStringJumbo(dest, value));
- } else {
- builder.add(this, new com.android.tools.r8.code.ConstString(dest, value));
- }
+ builder.add(this, new com.android.tools.r8.code.ConstString(dest, value));
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
index 0a1cc1b..55bb03d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
@@ -4,8 +4,11 @@
package com.android.tools.r8.ir.code;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.DebugLocalInfo;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringUtils;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
@@ -82,4 +85,9 @@
StringUtils.append(builder, starting.int2ReferenceEntrySet());
return builder.toString();
}
+
+ @Override
+ public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
+ return Constraint.ALWAYS;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
index 7a5acc5..9a8f8a5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
@@ -4,8 +4,11 @@
package com.android.tools.r8.ir.code;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import com.android.tools.r8.utils.InternalOptions;
public class DebugPosition extends Instruction {
@@ -74,4 +77,9 @@
}
builder.append(line);
}
+
+ @Override
+ public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
+ return Constraint.ALWAYS;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
index 955a6cc..afa18e5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
@@ -6,7 +6,6 @@
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
-import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
@@ -76,10 +75,6 @@
@Override
public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
- DexClass targetClass = info.definitionFor(type());
- if (targetClass == null) {
- return Constraint.NEVER;
- }
- return Constraint.deriveConstraint(holder, type(), targetClass.accessFlags, info);
+ return Constraint.classIsVisible(holder, type, info);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index e6ba6c0..e33c796 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -61,7 +61,7 @@
}
public void setOutValue(Value value) {
- assert outValue == null || !outValue.hasUsersInfo() || outValue.numberOfAllUsers() == 0;
+ assert outValue == null || !outValue.hasUsersInfo() || !outValue.isUsed();
outValue = value;
if (outValue != null) {
outValue.definition = this;
@@ -839,7 +839,5 @@
}
// Returns the inlining constraint for this instruction.
- public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
- return Constraint.NEVER;
- }
+ public abstract Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
index d0d0739..f597b2c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
@@ -7,7 +7,9 @@
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import java.util.List;
public final class InvokeCustom extends Invoke {
@@ -88,4 +90,9 @@
public InvokeCustom asInvokeCustom() {
return this;
}
+
+ @Override
+ public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
+ return Constraint.NEVER;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
index 2912c1a..0049baa 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import java.util.List;
public class InvokeNewArray extends Invoke {
@@ -93,4 +94,9 @@
public InvokeNewArray asInvokeNewArray() {
return this;
}
+
+ @Override
+ public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
+ return Constraint.classIsVisible(holder, type, info);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Monitor.java b/src/main/java/com/android/tools/r8/ir/code/Monitor.java
index b3e155b..a3b71e1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Monitor.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Monitor.java
@@ -8,7 +8,10 @@
import com.android.tools.r8.code.MonitorEnter;
import com.android.tools.r8.code.MonitorExit;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
public class Monitor extends Instruction {
@@ -80,4 +83,10 @@
public Monitor asMonitor() {
return this;
}
+
+ @Override
+ public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
+ // Conservative choice.
+ return Constraint.NEVER;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/MoveException.java b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
index 023e6f0..3dff4f3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/MoveException.java
+++ b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
@@ -4,7 +4,10 @@
package com.android.tools.r8.ir.code;
import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import com.android.tools.r8.utils.InternalOptions;
public class MoveException extends Instruction {
@@ -82,4 +85,10 @@
}
return super.toString();
}
+
+ @Override
+ public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
+ // TODO(64432527): Revisit this constraint.
+ return Constraint.NEVER;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
index f7b0367..17a08da 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
@@ -5,8 +5,10 @@
import com.android.tools.r8.code.NewArray;
import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
public class NewArrayEmpty extends Instruction {
@@ -68,4 +70,9 @@
public NewArrayEmpty asNewArrayEmpty() {
return this;
}
+
+ @Override
+ public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
+ return Constraint.classIsVisible(holder, type, info);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
index 63a1a0e..aed765e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
@@ -6,7 +6,10 @@
import com.android.tools.r8.code.FillArrayData;
import com.android.tools.r8.code.FillArrayDataPayload;
import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import com.android.tools.r8.utils.InternalOptions;
import java.util.Arrays;
@@ -94,4 +97,9 @@
public NewArrayFilledData asNewArrayFilledData() {
return this;
}
+
+ @Override
+ public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
+ return Constraint.ALWAYS;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
index 1aa2cbb..8d1671d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -4,8 +4,10 @@
package com.android.tools.r8.ir.code;
import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
public class NewInstance extends Instruction {
@@ -69,4 +71,9 @@
public NewInstance asNewInstance() {
return this;
}
+
+ @Override
+ public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
+ return Constraint.classIsVisible(holder, clazz, info);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java
index 1abdb1d..7e05dc9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Phi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -231,8 +231,11 @@
StringBuilder builder = new StringBuilder();
builder.append("v");
builder.append(number);
+ if (getLocalInfo() != null) {
+ builder.append("(").append(getLocalInfo()).append(")");
+ }
builder.append(" <- phi");
- StringUtils.append(builder, ListUtils.map(operands, (Value operand) -> "v" + operand.number));
+ StringUtils.append(builder, ListUtils.map(operands, Value::toString));
return builder.toString();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index e0bfdd0..035bf95 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -219,6 +219,12 @@
return numberOfUsers() + numberOfPhiUsers() + numberOfDebugUsers();
}
+ public boolean isUsed() {
+ return !users.isEmpty()
+ || !phiUsers.isEmpty()
+ || ((debugData != null) && !debugData.debugUsers.isEmpty());
+ }
+
public void addUser(Instruction user) {
users.add(user);
uniqueUsers = null;
@@ -503,7 +509,7 @@
public boolean isDead(InternalOptions options) {
// Totally unused values are trivially dead.
- return numberOfAllUsers() == 0 || isDead(new HashSet<>(), options);
+ return !isUsed() || isDead(new HashSet<>(), options);
}
protected boolean isDead(Set<Value> active, InternalOptions options) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
index 70e156b..48c8eb0 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -17,15 +17,23 @@
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ThreadUtils;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.function.Consumer;
+import java.util.function.Function;
import java.util.stream.Collectors;
/**
@@ -45,6 +53,10 @@
*/
public class CallGraph {
+ private CallGraph(InternalOptions options) {
+ this.shuffle = options.testing.irOrdering;
+ }
+
private static class Node {
public final DexEncodedMethod method;
@@ -121,6 +133,7 @@
private final Map<DexEncodedMethod, Node> nodes = new LinkedHashMap<>();
private final Map<DexEncodedMethod, Set<DexEncodedMethod>> breakers = new HashMap<>();
+ private final Function<List<DexEncodedMethod>, List<DexEncodedMethod>> shuffle;
// Returns whether the method->callee edge has been removed from the call graph
// to break a cycle in the call graph.
@@ -133,8 +146,8 @@
private Set<DexEncodedMethod> doubleCallSite = Sets.newIdentityHashSet();
public static CallGraph build(DexApplication application, AppInfoWithSubtyping appInfo,
- GraphLense graphLense) {
- CallGraph graph = new CallGraph();
+ GraphLense graphLense, InternalOptions options) {
+ CallGraph graph = new CallGraph(options);
DexClass[] classes = application.classes().toArray(new DexClass[application.classes().size()]);
Arrays.sort(classes, (DexClass a, DexClass b) -> a.type.slowCompareTo(b.type));
for (DexClass clazz : classes) {
@@ -185,7 +198,9 @@
private static boolean allMethodsExists(DexApplication application, CallGraph graph) {
for (DexProgramClass clazz : application.classes()) {
- clazz.forEachMethod(method -> { assert graph.nodes.get(method) != null; });
+ clazz.forEachMethod(method -> {
+ assert graph.nodes.get(method) != null;
+ });
}
return true;
}
@@ -197,19 +212,19 @@
* Please note that there are no cycles in this graph (see {@link #breakCycles}).
* <p>
*
- * @return List of {@link DexEncodedMethod}.
+ * @return List of {@link DexEncodedMethod}.
*/
- List<DexEncodedMethod> extractLeaves() {
+ private List<DexEncodedMethod> extractLeaves() {
if (isEmpty()) {
- return null;
+ return Collections.emptyList();
}
// First identify all leaves before removing them from the graph.
List<Node> leaves = nodes.values().stream().filter(Node::isLeaf).collect(Collectors.toList());
- leaves.forEach( leaf -> {
- leaf.callers.forEach( caller -> caller.callees.remove(leaf));
+ leaves.forEach(leaf -> {
+ leaf.callers.forEach(caller -> caller.callees.remove(leaf));
nodes.remove(leaf.method);
});
- return leaves.stream().map( leaf -> leaf.method).collect(Collectors.toList());
+ return shuffle.apply(leaves.stream().map(leaf -> leaf.method).collect(Collectors.toList()));
}
private int traverse(Node node, Set<Node> stack, Set<Node> marked) {
@@ -253,7 +268,7 @@
int numberOfCycles = 0;
Set<Node> stack = Sets.newIdentityHashSet();
Set<Node> marked = Sets.newIdentityHashSet();
- for(Node node : nodes.values()) {
+ for (Node node : nodes.values()) {
numberOfCycles += traverse(node, stack, marked);
}
return numberOfCycles;
@@ -279,6 +294,21 @@
return nodes.size() == 0;
}
+ public void forEachMethod(Consumer<DexEncodedMethod> consumer, ExecutorService executorService)
+ throws ExecutionException {
+ while (!isEmpty()) {
+ List<DexEncodedMethod> methods = extractLeaves();
+ assert methods.size() > 0;
+ List<Future<?>> futures = new ArrayList<>();
+ for (DexEncodedMethod method : methods) {
+ futures.add(executorService.submit(() -> {
+ consumer.accept(method);
+ }));
+ }
+ ThreadUtils.awaitFutures(futures);
+ }
+ }
+
public void dump() {
nodes.forEach((m, n) -> System.out.println(n + "\n"));
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index 737dc46..da067d8 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -85,9 +85,6 @@
// List of generated FillArrayData dex instructions.
private final List<FillArrayDataInfo> fillArrayDataInfos = new ArrayList<>();
- // First jumbo string if known.
- private final DexString firstJumboString;
-
// Set of if instructions that have offsets that are so large that they cannot be encoded in
// the if instruction format.
private Set<BasicBlock> ifsNeedingRewrite = Sets.newIdentityHashSet();
@@ -116,18 +113,6 @@
this.ir = ir;
this.registerAllocator = registerAllocator;
this.dexItemFactory = dexItemFactory;
- this.firstJumboString = null;
- }
-
- public DexBuilder(IRCode ir, RegisterAllocator registerAllocator,
- DexItemFactory dexItemFactory, DexString firstJumboString) {
- assert ir != null;
- assert registerAllocator != null;
- assert dexItemFactory != null;
- this.ir = ir;
- this.registerAllocator = registerAllocator;
- this.dexItemFactory = dexItemFactory;
- this.firstJumboString = firstJumboString;
}
private void reset() {
@@ -143,15 +128,6 @@
nextBlock = null;
}
- public boolean isJumboString(DexString string) {
- if (firstJumboString == null) {
- return false;
- }
- // We have to use compareTo here, as slowCompareTo will return the wrong order when minification
- // is used.
- return firstJumboString.compareTo(string) <= 0;
- }
-
/**
* Build the dex instructions added to this builder.
*
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 8e613c0..6f94b1e 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -50,7 +50,7 @@
public class IRConverter {
- public static final int PEEPHOLE_OPTIMIZATION_PASSES = 2;
+ private static final int PEEPHOLE_OPTIMIZATION_PASSES = 2;
private final Timing timing;
public final DexApplication application;
@@ -266,7 +266,7 @@
removeLambdaDeserializationMethods();
timing.begin("Build call graph");
- callGraph = CallGraph.build(application, appInfo.withSubtyping(), graphLense);
+ callGraph = CallGraph.build(application, appInfo.withSubtyping(), graphLense, options);
timing.end();
// The process is in two phases.
@@ -278,22 +278,10 @@
// Process the application identifying outlining candidates.
timing.begin("IR conversion phase 1");
OptimizationFeedback directFeedback = new OptimizationFeedbackDirect();
- while (!callGraph.isEmpty()) {
- List<DexEncodedMethod> methods = callGraph.extractLeaves();
- assert methods.size() > 0;
- // For testing we have the option to determine the processing order of the methods.
- if (options.testing.irOrdering != null) {
- methods = options.testing.irOrdering.apply(methods);
- }
- List<Future<?>> futures = new ArrayList<>();
- for (DexEncodedMethod method : methods) {
- futures.add(executorService.submit(() -> {
+ callGraph.forEachMethod(method -> {
processMethod(method, directFeedback,
outliner == null ? Outliner::noProcessing : outliner::identifyCandidates);
- }));
- }
- ThreadUtils.awaitFutures(futures);
- }
+ }, executorService);
timing.end();
// Build a new application with jumbo string info.
@@ -314,13 +302,20 @@
// add the outline support class IF needed.
DexProgramClass outlineClass = prepareOutlining();
if (outlineClass != null) {
- // Process the selected methods for outlining.
- for (DexEncodedMethod method : outliner.getMethodsSelectedForOutlining()) {
+ // We need a new call graph to ensure deterministic order and also processing inside out
+ // to get maximal inlining. Use a identity lense, as the code has been rewritten.
+ callGraph = CallGraph
+ .build(application, appInfo.withSubtyping(), GraphLense.getIdentityLense(), options);
+ Set<DexEncodedMethod> outlineMethods = outliner.getMethodsSelectedForOutlining();
+ callGraph.forEachMethod(method -> {
+ if (!outlineMethods.contains(method)) {
+ return;
+ }
// This is the second time we compile this method first mark it not processed.
assert !method.getCode().isOutlineCode();
processMethod(method, ignoreOptimizationFeedback, outliner::applyOutliningCandidate);
assert method.isProcessed();
- }
+ }, executorService);
builder.addSynthesizedClass(outlineClass, true);
clearDexMethodCompilationState(outlineClass);
}
@@ -330,10 +325,6 @@
return builder.build();
}
- public void processJumboStrings(DexEncodedMethod method, DexString firstJumboString) {
- convertMethodJumboStringsOnly(method, firstJumboString);
- }
-
private void clearDexMethodCompilationState() {
application.classes().forEach(this::clearDexMethodCompilationState);
}
@@ -374,6 +365,8 @@
count++;
result = application.dexItemFactory.createType(DescriptorUtils.javaTypeToDescriptor(name));
} while (application.definitionFor(result) != null);
+ // Register the newly generated type in the subtyping hierarchy, if we have one.
+ appInfo.registerNewType(result, appInfo.dexItemFactory.objectType);
return result;
}
@@ -461,6 +454,7 @@
assert !options.debug;
inliner.performInlining(method, code, callGraph);
}
+ codeRewriter.removeCastChains(code);
codeRewriter.rewriteLongCompareAndRequireNonNull(code, options);
codeRewriter.commonSubexpressionElimination(code);
codeRewriter.simplifyArrayConstruction(code);
@@ -537,44 +531,6 @@
}
}
- // Convert a method ensuring that strings sorting equal or higher than the argument
- // firstJumboString are encoded as jumbo strings.
- // TODO(sgjesse): Consider replacing this with a direct dex2dex converter instead of going
- // through IR.
- private void convertMethodJumboStringsOnly(
- DexEncodedMethod method, DexString firstJumboString) {
- // This is only used for methods already converted to Dex, but missing jumbo strings.
- assert method.getCode() != null && method.getCode().isDexCode();
- if (options.verbose) {
- System.out.println("Processing jumbo strings: " + method.toSourceString());
- }
- if (Log.ENABLED) {
- Log.debug(getClass(), "Original code for %s:\n%s",
- method.toSourceString(), logCode(options, method));
- }
- IRCode code = method.buildIR(options);
- if (Log.ENABLED) {
- Log.debug(getClass(), "Initial (SSA) flow graph for %s:\n%s",
- method.toSourceString(), code);
- }
- // Compilation header if printing CFGs for this method.
- printC1VisualizerHeader(method);
- printMethod(code, "Initial IR (SSA)");
-
- // Methods passed through here should have been through IR processing already and
- // therefore, we skip most of the IR processing.
-
- // Perform register allocation.
- RegisterAllocator registerAllocator = performRegisterAllocation(code, method);
- method.setCode(code, registerAllocator, appInfo.dexItemFactory, firstJumboString);
-
- if (Log.ENABLED) {
- Log.debug(getClass(), "Resulting dex code for %s:\n%s",
- method.toSourceString(), logCode(options, method));
- }
- printMethod(code, "Final IR (non-SSA)");
- }
-
private RegisterAllocator performRegisterAllocation(IRCode code, DexEncodedMethod method) {
// Always perform dead code elimination before register allocation. The register allocator
// does not allow dead code (to make sure that we do not waste registers for unneeded values).
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 55e6296..0a6ea7e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -81,6 +81,9 @@
: factory.createMethod(lambdaClassType, constructorProto, rewriter.classConstructorName);
this.instanceField = !stateless ? null
: factory.createField(lambdaClassType, lambdaClassType, rewriter.instanceFieldName);
+
+ // We have to register this new class as a subtype of object.
+ rewriter.appInfo.registerNewType(type, factory.objectType);
}
// Generate unique lambda class type for lambda descriptor and instantiation point context.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index 7d3bc33..af32cb7 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.ir.desugar;
import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexApplication.Builder;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -57,6 +58,7 @@
static final String LAMBDA_INSTANCE_FIELD_NAME = "INSTANCE";
final IRConverter converter;
+ final AppInfo appInfo;
final DexItemFactory factory;
final DexMethod metafactoryMethod;
@@ -78,7 +80,6 @@
//
// NOTE: synchronize concurrent access on `knownCallSites`.
private final Map<DexCallSite, LambdaDescriptor> knownCallSites = new IdentityHashMap<>();
-
// Maps lambda class type into lambda class representation. Since lambda class
// type uniquely defines lambda class, effectively canonicalizes lambda classes.
// NOTE: synchronize concurrent access on `knownLambdaClasses`.
@@ -93,6 +94,7 @@
assert converter != null;
this.converter = converter;
this.factory = converter.application.dexItemFactory;
+ this.appInfo = converter.appInfo;
DexType metafactoryType = factory.createType(METAFACTORY_TYPE_DESCR);
DexType callSiteType = factory.createType(CALLSITE_TYPE_DESCR);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index b1cdcbc..981f673 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.Binop;
import com.android.tools.r8.ir.code.CatchHandlers;
+import com.android.tools.r8.ir.code.CheckCast;
import com.android.tools.r8.ir.code.Cmp;
import com.android.tools.r8.ir.code.Cmp.Bias;
import com.android.tools.r8.ir.code.ConstNumber;
@@ -509,7 +510,7 @@
Instruction current = iterator.next();
if (current.isInvokeMethod()) {
InvokeMethod invoke = current.asInvokeMethod();
- if (invoke.outValue() != null) {
+ if (invoke.outValue() != null && invoke.outValue().getLocalInfo() == null) {
DexEncodedMethod target = invoke.computeSingleTarget(appInfo.withSubtyping());
// We have a set of library classes with optimization information - consider those
// as well.
@@ -541,42 +542,45 @@
assert code.isConsistentGraph();
}
- // For supporting assert javac adds the static field $assertionsDisabled to all classes which
- // have methods with assertions. This is used to support the Java VM -ea flag.
- //
- // The class:
- //
- // class A {
- // void m() {
- // assert xxx;
- // }
- // }
- //
- // Is compiled into:
- //
- // class A {
- // static boolean $assertionsDisabled;
- // static {
- // $assertionsDisabled = A.class.desiredAssertionStatus();
- // }
- //
- // // method with "assert xxx";
- // void m() {
- // if (!$assertionsDisabled) {
- // if (xxx) {
- // throw new AssertionError(...);
- // }
- // }
- // }
- // }
- //
- // With the rewriting below (and other rewritings) the resulting code is:
- //
- // class A {
- // void m() {
- // }
- // }
- //
+
+ /**
+ * For supporting assert javac adds the static field $assertionsDisabled to all classes which
+ * have methods with assertions. This is used to support the Java VM -ea flag.
+ *
+ * The class:
+ * <pre>
+ * class A {
+ * void m() {
+ * assert xxx;
+ * }
+ * }
+ * </pre>
+ * Is compiled into:
+ * <pre>
+ * class A {
+ * static boolean $assertionsDisabled;
+ * static {
+ * $assertionsDisabled = A.class.desiredAssertionStatus();
+ * }
+ *
+ * // method with "assert xxx";
+ * void m() {
+ * if (!$assertionsDisabled) {
+ * if (xxx) {
+ * throw new AssertionError(...);
+ * }
+ * }
+ * }
+ * }
+ * </pre>
+ * With the rewriting below (and other rewritings) the resulting code is:
+ * <pre>
+ * class A {
+ * void m() {
+ * }
+ * }
+ * </pre>
+ */
public void disableAssertions(IRCode code) {
InstructionIterator iterator = code.instructionIterator();
while (iterator.hasNext()) {
@@ -600,6 +604,28 @@
}
}
+ /**
+ * Due to inlining, we might see chains of casts on subtypes. It suffices to cast to the lowest
+ * subtype, as that will fail if a cast on a supertype would have failed.
+ */
+ public void removeCastChains(IRCode code) {
+ InstructionIterator it = code.instructionIterator();
+ while (it.hasNext()) {
+ Instruction current = it.next();
+ if (current.isCheckCast()
+ && current.outValue() != null && current.outValue().isUsed()
+ && current.outValue().numberOfPhiUsers() == 0) {
+ CheckCast checkCast = current.asCheckCast();
+ if (checkCast.outValue().uniqueUsers().stream().allMatch(
+ user -> user.isCheckCast()
+ && user.asCheckCast().getType().isSubtypeOf(checkCast.getType(), appInfo))) {
+ checkCast.outValue().replaceUsers(checkCast.inValues().get(0));
+ it.remove();
+ }
+ }
+ }
+ }
+
private boolean canBeFolded(Instruction instruction) {
return (instruction.isBinop() && instruction.asBinop().canBeFolded()) ||
(instruction.isUnop() && instruction.asUnop().canBeFolded());
@@ -1240,7 +1266,7 @@
}
private Value addConstString(IRCode code, InstructionListIterator iterator, String s) {
- Value value = code.createValue(MoveType.OBJECT);;
+ Value value = code.createValue(MoveType.OBJECT);
iterator.add(new ConstString(value, dexItemFactory.createString(s)));
return value;
}
@@ -1279,7 +1305,7 @@
iterator.add(new ConstString(value, dexItemFactory.createString("INVOKE ")));
iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, value)));
- value = code.createValue(MoveType.OBJECT);;
+ value = code.createValue(MoveType.OBJECT);
iterator.add(
new ConstString(value, dexItemFactory.createString(method.method.qualifiedName())));
iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, value)));
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
index b683f3a..3a1902e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
@@ -105,7 +105,7 @@
// Remove unused invoke results.
if (current.isInvoke()
&& current.outValue() != null
- && current.outValue().numberOfAllUsers() == 0) {
+ && !current.outValue().isUsed()) {
current.setOutValue(null);
}
// Never remove instructions that can have side effects, except for const-class.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index f750b09..ce6ad76 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -183,6 +183,13 @@
}
}
+ public static Constraint classIsVisible(DexType context, DexType clazz,
+ AppInfoWithSubtyping appInfo) {
+ DexClass definition = appInfo.definitionFor(clazz);
+ return definition == null ? NEVER
+ : deriveConstraint(context, clazz, definition.accessFlags, appInfo);
+ }
+
public static Constraint min(Constraint one, Constraint other) {
return one.ordinal() < other.ordinal() ? one : other;
}
@@ -205,15 +212,15 @@
public final DexEncodedMethod target;
public final Invoke invoke;
- public final Reason reason;
+ final Reason reason;
- public InlineAction(DexEncodedMethod target, Invoke invoke, Reason reason) {
+ InlineAction(DexEncodedMethod target, Invoke invoke, Reason reason) {
this.target = target;
this.invoke = invoke;
this.reason = reason;
}
- public boolean forceInline() {
+ boolean forceInline() {
return reason != Reason.SIMPLE;
}
@@ -252,12 +259,12 @@
// Before that method invokes another instance initialization method of myClass or its direct
// superclass on this, the only operation the method can perform on this is assigning fields
// declared within myClass.
- //
// Allow inlining a constructor into a constructor of the same class, as the constructor code
// is expected to adhere to the VM specification.
- if (method.accessFlags.isConstructor()
- && method.method.holder == invoke.getInvokedMethod().holder) {
+ DexType methodHolder = method.method.holder;
+ boolean methodIsConstructor = method.isInstanceInitializer();
+ if (methodIsConstructor && methodHolder == invoke.asInvokeMethod().getInvokedMethod().holder) {
return true;
}
@@ -265,6 +272,8 @@
// un-initialized object is not an argument of an invoke of <init>.
// Also, we cannot inline a constructor if it initializes final fields, as such is only allowed
// from within a constructor of the corresponding class.
+ // Lastly, we can only inline a constructor, if its own <init> call is on the method's class. If
+ // we inline into a constructor, calls to super.<init> are also OK.
InstructionIterator iterator = code.instructionIterator();
Instruction instruction = iterator.next();
// A constructor always has the un-initialized object as the first argument.
@@ -277,12 +286,23 @@
if (instruction.isInvokeDirect() && !seenSuperInvoke) {
DexMethod target = instruction.asInvokeDirect().getInvokedMethod();
seenSuperInvoke = appInfo.dexItemFactory.isConstructor(target);
+ if (seenSuperInvoke
+ // Calls to init on same class are always OK.
+ && target.holder != methodHolder
+ // If we are inlining into a constructor, calls to superclass init are OK.
+ && (!methodHolder.isImmediateSubtypeOf(target.holder) || !methodIsConstructor)) {
+ return false;
+ }
}
if (!seenSuperInvoke) {
return false;
}
}
if (instruction.isInstancePut()) {
+ // Fields may not be initialized outside of a constructor.
+ if (!methodIsConstructor) {
+ return false;
+ }
DexField field = instruction.asInstancePut().getField();
DexEncodedField target = appInfo.lookupInstanceTarget(field.getHolder(), field);
if (target != null && target.accessFlags.isFinal()) {
@@ -293,16 +313,6 @@
return true;
}
- /// Computer the receiver value for the holder method.
- private Value receiverValue(DexEncodedMethod method, IRCode code) {
- // Ignore static methods.
- if (method.accessFlags.isStatic()) {
- return null;
- }
- // Find the outValue of the first argument instruction in the first block.
- return code.collectArguments().get(0);
- }
-
public void performInlining(DexEncodedMethod method, IRCode code, CallGraph callGraph) {
int instruction_allowance = 1500;
instruction_allowance -= numberOfInstructions(code);
@@ -338,7 +348,7 @@
IRCode inlinee = result
.buildIR(code.valueNumberGenerator, appInfo, graphLense, options);
if (inlinee != null) {
- // TODO(sgjesse): Get rid of this additional check by improved inlining.
+ // TODO(64432527): Get rid of this additional check by improved inlining.
if (block.hasCatchHandlers() && inlinee.getNormalExitBlock() == null) {
continue;
}
@@ -356,8 +366,8 @@
performInlining(target, inlinee, callGraph);
}
// Make sure constructor inlining is legal.
- if (target.accessFlags.isConstructor()
- && !target.accessFlags.isStatic()
+ assert !target.isClassInitializer();
+ if (target.isInstanceInitializer()
&& !legalConstructorInline(method, invoke, inlinee)) {
continue;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
index fb6c2eb..d32f7f4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
@@ -29,7 +29,7 @@
private final CallGraph callGraph;
private final InliningInfo info;
- public InliningOracle(
+ InliningOracle(
Inliner inliner,
DexEncodedMethod method,
CallGraph callGraph) {
@@ -39,7 +39,7 @@
info = Log.ENABLED ? new InliningInfo(method) : null;
}
- public void finish() {
+ void finish() {
if (Log.ENABLED) {
System.out.println(info.toString());
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 8a02435..a34c8cb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -137,10 +137,10 @@
ProguardMemberRuleLookup lookup = lookupMemberRule(definition);
if (lookup != null) {
if (lookup.type == RuleType.ASSUME_NO_SIDE_EFFECTS
- && (invoke.outValue() == null || invoke.outValue().numberOfAllUsers() == 0)) {
+ && (invoke.outValue() == null || !invoke.outValue().isUsed())) {
iterator.remove();
invokeReplaced = true;
- } else {
+ } else if (invoke.outValue() != null && invoke.outValue().isUsed()) {
// Check to see if a constant value can be assumed.
Instruction replacement =
constantReplacementFromProguardRule(lookup.rule, code, invoke);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 8cc8ec4..3857c1f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -400,7 +400,7 @@
// Allow all new-instance instructions in a outline.
if (instruction.isNewInstance()) {
- if (instruction.outValue().numberOfAllUsers() > 0) {
+ if (instruction.outValue().isUsed()) {
// Track the new-instance value to make sure the <init> call is part of the outline.
pendingNewInstanceIndex = index;
}
@@ -711,7 +711,7 @@
}
}
assert m.proto.shorty.toString().length() - 1 == in.size();
- if (returnValue != null && returnValue.numberOfAllUsers() == 0) {
+ if (returnValue != null && !returnValue.isUsed()) {
returnValue = null;
}
Invoke outlineInvoke = new InvokeStatic(m, returnValue, in);
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 2e4a434..38cdb29 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -432,6 +432,17 @@
ListIterator<Instruction> instructionIterator,
Int2ReferenceMap<DebugLocalInfo> initialLocals,
Int2ReferenceMap<DebugLocalInfo> finalLocals) {
+ if (!options.singleStepDebug) {
+ while (instructionIterator.hasNext()) {
+ if (instructionIterator.next().getNumber() != -1) {
+ break;
+ }
+ }
+ return Int2ReferenceMaps.emptyMap();
+ }
+ // TODO(zerny): Investigate supporting accurate single stepping through spill instructions.
+ // The current code should preferably be updated to account for moving locals and not just
+ // end their scope.
int spillCount;
int firstClobberedMove = -1;
Int2ReferenceMap<DebugLocalInfo> clobberedLocals = Int2ReferenceMaps.emptyMap();
@@ -1944,7 +1955,7 @@
// For instructions that define values which have no use create a live range covering
// the instruction. This will typically be instructions that can have side effects even
// if their output is not used.
- if (definition.numberOfAllUsers() == 0) {
+ if (!definition.isUsed()) {
addLiveRange(definition, block, instruction.getNumber() + INSTRUCTION_NUMBER_DELTA);
}
live.remove(definition);
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index d25c4bc..c45a5c1 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -31,7 +31,9 @@
import com.android.tools.r8.utils.MethodSignatureEquivalence;
import com.android.tools.r8.utils.Timing;
import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Queues;
import com.google.common.collect.Sets;
@@ -39,7 +41,6 @@
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
@@ -49,6 +50,7 @@
import java.util.Map.Entry;
import java.util.Queue;
import java.util.Set;
+import java.util.SortedSet;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -886,20 +888,24 @@
.collect(Collectors.toCollection(Sets::newIdentityHashSet));
}
- Set<DexField> collectInstanceFieldsRead() {
- return Collections.unmodifiableSet(collectFields(instanceFieldsRead));
+ SortedSet<DexField> collectInstanceFieldsRead() {
+ return ImmutableSortedSet.copyOf(
+ PresortedComparable::slowCompareTo, collectFields(instanceFieldsRead));
}
- Set<DexField> collectInstanceFieldsWritten() {
- return Collections.unmodifiableSet(collectFields(instanceFieldsWritten));
+ SortedSet<DexField> collectInstanceFieldsWritten() {
+ return ImmutableSortedSet.copyOf(
+ PresortedComparable::slowCompareTo, collectFields(instanceFieldsWritten));
}
- Set<DexField> collectStaticFieldsRead() {
- return Collections.unmodifiableSet(collectFields(staticFieldsRead));
+ SortedSet<DexField> collectStaticFieldsRead() {
+ return ImmutableSortedSet.copyOf(
+ PresortedComparable::slowCompareTo, collectFields(staticFieldsRead));
}
- Set<DexField> collectStaticFieldsWritten() {
- return Collections.unmodifiableSet(collectFields(staticFieldsWritten));
+ SortedSet<DexField> collectStaticFieldsWritten() {
+ return ImmutableSortedSet.copyOf(
+ PresortedComparable::slowCompareTo, collectFields(staticFieldsWritten));
}
private Set<DexField> collectReachedFields(Map<DexType, Set<DexField>> map,
@@ -918,14 +924,16 @@
return target == null ? field : target.field;
}
- Set<DexField> collectFieldsRead() {
- return Sets.union(collectReachedFields(instanceFieldsRead, this::tryLookupInstanceField),
- collectReachedFields(staticFieldsRead, this::tryLookupStaticField));
+ SortedSet<DexField> collectFieldsRead() {
+ return ImmutableSortedSet.copyOf(PresortedComparable::slowCompareTo,
+ Sets.union(collectReachedFields(instanceFieldsRead, this::tryLookupInstanceField),
+ collectReachedFields(staticFieldsRead, this::tryLookupStaticField)));
}
- Set<DexField> collectFieldsWritten() {
- return Sets.union(collectReachedFields(instanceFieldsWritten, this::tryLookupInstanceField),
- collectReachedFields(staticFieldsWritten, this::tryLookupStaticField));
+ SortedSet<DexField> collectFieldsWritten() {
+ return ImmutableSortedSet.copyOf(PresortedComparable::slowCompareTo,
+ Sets.union(collectReachedFields(instanceFieldsWritten, this::tryLookupInstanceField),
+ collectReachedFields(staticFieldsWritten, this::tryLookupStaticField)));
}
private static class Action {
@@ -1009,68 +1017,68 @@
* Set of types that are mentioned in the program. We at least need an empty abstract classitem
* for these.
*/
- public final Set<DexType> liveTypes;
+ public final SortedSet<DexType> liveTypes;
/**
* Set of types that are actually instantiated. These cannot be abstract.
*/
- final Set<DexType> instantiatedTypes;
+ final SortedSet<DexType> instantiatedTypes;
/**
* Set of methods that are the immediate target of an invoke. They might not actually be live
* but are required so that invokes can find the method. If such a method is not live (i.e. not
* contained in {@link #liveMethods}, it may be marked as abstract and its implementation may be
* removed.
*/
- final Set<DexMethod> targetedMethods;
+ final SortedSet<DexMethod> targetedMethods;
/**
* Set of methods that belong to live classes and can be reached by invokes. These need to be
* kept.
*/
- final Set<DexMethod> liveMethods;
+ final SortedSet<DexMethod> liveMethods;
/**
* Set of fields that belong to live classes and can be reached by invokes. These need to be
* kept.
*/
- public final Set<DexField> liveFields;
+ public final SortedSet<DexField> liveFields;
/**
* Set of all fields which may be touched by a get operation. This is actual field definitions.
*/
- public final Set<DexField> fieldsRead;
+ public final SortedSet<DexField> fieldsRead;
/**
* Set of all fields which may be touched by a put operation. This is actual field definitions.
*/
- public final Set<DexField> fieldsWritten;
+ public final SortedSet<DexField> fieldsWritten;
/**
* Set of all field ids used in instance field reads.
*/
- public final Set<DexField> instanceFieldReads;
+ public final SortedSet<DexField> instanceFieldReads;
/**
* Set of all field ids used in instance field writes.
*/
- public final Set<DexField> instanceFieldWrites;
+ public final SortedSet<DexField> instanceFieldWrites;
/**
* Set of all field ids used in static static field reads.
*/
- public final Set<DexField> staticFieldReads;
+ public final SortedSet<DexField> staticFieldReads;
/**
* Set of all field ids used in static field writes.
*/
- public final Set<DexField> staticFieldWrites;
+ public final SortedSet<DexField> staticFieldWrites;
/**
* Set of all methods referenced in virtual invokes;
*/
- public final Set<DexMethod> virtualInvokes;
+ public final SortedSet<DexMethod> virtualInvokes;
/**
* Set of all methods referenced in super invokes;
*/
- public final Set<DexMethod> superInvokes;
+ public final SortedSet<DexMethod> superInvokes;
/**
* Set of all methods referenced in direct invokes;
*/
- public final Set<DexMethod> directInvokes;
+ public final SortedSet<DexMethod> directInvokes;
/**
* Set of all methods referenced in static invokes;
*/
- public final Set<DexMethod> staticInvokes;
+ public final SortedSet<DexMethod> staticInvokes;
/**
* Set of all items that have to be kept independent of whether they are used.
*/
@@ -1090,8 +1098,10 @@
private AppInfoWithLiveness(AppInfoWithSubtyping appInfo, Enqueuer enqueuer) {
super(appInfo);
- this.liveTypes = Collections.unmodifiableSet(enqueuer.liveTypes);
- this.instantiatedTypes = enqueuer.instantiatedTypes.getItems();
+ this.liveTypes =
+ ImmutableSortedSet.copyOf(PresortedComparable::slowCompareTo, enqueuer.liveTypes);
+ this.instantiatedTypes = ImmutableSortedSet.copyOf(
+ PresortedComparable::slowCompareTo, enqueuer.instantiatedTypes.getItems());
this.targetedMethods = toDescriptorSet(enqueuer.targetedMethods.getItems());
this.liveMethods = toDescriptorSet(enqueuer.liveMethods.getItems());
this.liveFields = toDescriptorSet(enqueuer.liveFields.getItems());
@@ -1101,7 +1111,7 @@
this.staticFieldWrites = enqueuer.collectStaticFieldsWritten();
this.fieldsRead = enqueuer.collectFieldsRead();
this.fieldsWritten = enqueuer.collectFieldsWritten();
- this.pinnedItems = Collections.unmodifiableSet(enqueuer.pinnedItems);
+ this.pinnedItems = ImmutableSet.copyOf(enqueuer.pinnedItems);
this.virtualInvokes = joinInvokedMethods(enqueuer.virtualInvokes);
this.superInvokes = joinInvokedMethods(enqueuer.superInvokes);
this.directInvokes = joinInvokedMethods(enqueuer.directInvokes);
@@ -1165,24 +1175,27 @@
assert Sets.intersection(instanceFieldWrites, staticFieldWrites).size() == 0;
}
- private Set<DexMethod> joinInvokedMethods(Map<DexType, Set<DexMethod>> invokes) {
- ImmutableSet.Builder<DexMethod> builder = ImmutableSet.builder();
+ private SortedSet<DexMethod> joinInvokedMethods(Map<DexType, Set<DexMethod>> invokes) {
+ ImmutableSortedSet.Builder<DexMethod> builder =
+ new ImmutableSortedSet.Builder<>(PresortedComparable::slowCompare);
invokes.values().forEach(builder::addAll);
return builder.build();
}
- private <T extends PresortedComparable<T>> Set<T> toDescriptorSet(
+ private <T extends PresortedComparable<T>> SortedSet<T> toDescriptorSet(
Set<? extends KeyedDexItem<T>> set) {
- ImmutableSet.Builder<T> builder = ImmutableSet.builder();
+ ImmutableSortedSet.Builder<T> builder =
+ new ImmutableSortedSet.Builder<>(PresortedComparable::slowCompareTo);
for (KeyedDexItem<T> item : set) {
builder.add(item.getKey());
}
return builder.build();
}
- private static <T> ImmutableSet<T> rewriteItems(Set<T> original,
- BiFunction<T, DexEncodedMethod, T> rewrite) {
- ImmutableSet.Builder<T> builder = ImmutableSet.builder();
+ private static <T extends PresortedComparable<T>> ImmutableSortedSet<T> rewriteItems(
+ Set<T> original, BiFunction<T, DexEncodedMethod, T> rewrite) {
+ ImmutableSortedSet.Builder<T> builder =
+ new ImmutableSortedSet.Builder<>(PresortedComparable::slowCompare);
for (T item : original) {
builder.add(rewrite.apply(item, null));
}
@@ -1245,11 +1258,11 @@
}
Set<T> getItems() {
- return Collections.unmodifiableSet(items);
+ return ImmutableSet.copyOf(items);
}
Map<T, KeepReason> getReasons() {
- return Collections.unmodifiableMap(reasons);
+ return ImmutableMap.copyOf(reasons);
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardCheckDiscardRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardCheckDiscardRule.java
new file mode 100644
index 0000000..54302db
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardCheckDiscardRule.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2017, 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.shaking;
+
+import com.android.tools.r8.graph.DexAccessFlags;
+import java.util.List;
+import java.util.Set;
+
+public class ProguardCheckDiscardRule extends ProguardConfigurationRule {
+
+ public static class Builder extends ProguardConfigurationRule.Builder {
+
+ private Builder() {
+ }
+
+ public ProguardCheckDiscardRule build() {
+ return new ProguardCheckDiscardRule(classAnnotation, classAccessFlags,
+ negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
+ inheritanceClassName, inheritanceIsExtends, memberRules);
+ }
+ }
+
+ private ProguardCheckDiscardRule(
+ ProguardTypeMatcher classAnnotation,
+ DexAccessFlags classAccessFlags,
+ DexAccessFlags negatedClassAccessFlags,
+ boolean classTypeNegated,
+ ProguardClassType classType,
+ List<ProguardTypeMatcher> classNames,
+ ProguardTypeMatcher inheritanceAnnotation,
+ ProguardTypeMatcher inheritanceClassName,
+ boolean inheritanceIsExtends,
+ Set<ProguardMemberRule> memberRules) {
+ super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
+ classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ @Override
+ String typeString() {
+ return "checkdiscard";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 17e5128..31fb77d 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -137,16 +137,16 @@
} else if (acceptString("keepattributes")) {
parseKeepAttributes();
} else if (acceptString("keeppackagenames")) {
- ProguardKeepRule rule = parseKeepPackageNamesRule();
+ ProguardKeepPackageNamesRule rule = parseKeepPackageNamesRule();
configurationBuilder.addRule(rule);
} else if (acceptString("checkdiscard")) {
- ProguardKeepRule rule = parseCheckDiscardRule();
+ ProguardCheckDiscardRule rule = parseCheckDiscardRule();
configurationBuilder.addRule(rule);
} else if (acceptString("keep")) {
ProguardKeepRule rule = parseKeepRule();
configurationBuilder.addRule(rule);
} else if (acceptString("whyareyoukeeping")) {
- ProguardKeepRule rule = parseWhyAreYouKeepingRule();
+ ProguardWhyAreYouKeepingRule rule = parseWhyAreYouKeepingRule();
configurationBuilder.addRule(rule);
} else if (acceptString("dontoptimize")) {
configurationBuilder.setOptimize(false);
@@ -351,37 +351,36 @@
ProguardKeepRule.Builder keepRuleBuilder = ProguardKeepRule.builder();
parseRuleTypeAndModifiers(keepRuleBuilder);
parseClassSpec(keepRuleBuilder, false);
+ if (keepRuleBuilder.getMemberRules().isEmpty()) {
+ // If there are no member rules, a default rule for the parameterless constructor
+ // applies. So we add that here.
+ ProguardMemberRule.Builder defaultRuleBuilder = ProguardMemberRule.builder();
+ defaultRuleBuilder.setName(Constants.INSTANCE_INITIALIZER_NAME);
+ defaultRuleBuilder.setRuleType(ProguardMemberType.INIT);
+ defaultRuleBuilder.setArguments(Collections.emptyList());
+ keepRuleBuilder.getMemberRules().add(defaultRuleBuilder.build());
+ }
return keepRuleBuilder.build();
}
- private ProguardKeepRule parseWhyAreYouKeepingRule()
+ private ProguardWhyAreYouKeepingRule parseWhyAreYouKeepingRule()
throws ProguardRuleParserException {
- ProguardKeepRule.Builder keepRuleBuilder = ProguardKeepRule.builder();
- keepRuleBuilder.getModifiersBuilder().setFlagsToHaveNoEffect();
- keepRuleBuilder.getModifiersBuilder().whyAreYouKeeping = true;
- keepRuleBuilder.setType(ProguardKeepRuleType.KEEP);
+ ProguardWhyAreYouKeepingRule.Builder keepRuleBuilder = ProguardWhyAreYouKeepingRule.builder();
parseClassSpec(keepRuleBuilder, false);
return keepRuleBuilder.build();
}
- private ProguardKeepRule parseKeepPackageNamesRule()
+ private ProguardKeepPackageNamesRule parseKeepPackageNamesRule()
throws ProguardRuleParserException {
- ProguardKeepRule.Builder keepRuleBuilder = ProguardKeepRule.builder();
- keepRuleBuilder.getModifiersBuilder().setFlagsToHaveNoEffect();
- keepRuleBuilder.getModifiersBuilder().keepPackageNames = true;
- keepRuleBuilder.setType(ProguardKeepRuleType.KEEP);
+ ProguardKeepPackageNamesRule.Builder keepRuleBuilder = ProguardKeepPackageNamesRule.builder();
keepRuleBuilder.setClassNames(parseClassNames());
return keepRuleBuilder.build();
}
- private ProguardKeepRule parseCheckDiscardRule()
+ private ProguardCheckDiscardRule parseCheckDiscardRule()
throws ProguardRuleParserException {
- ProguardKeepRule.Builder keepRuleBuilder = ProguardKeepRule.builder();
- keepRuleBuilder.getModifiersBuilder().setFlagsToHaveNoEffect();
- keepRuleBuilder.getModifiersBuilder().checkDiscarded = true;
+ ProguardCheckDiscardRule.Builder keepRuleBuilder = ProguardCheckDiscardRule.builder();
parseClassSpec(keepRuleBuilder, false);
- keepRuleBuilder.setType(keepRuleBuilder.getMemberRules().isEmpty() ? ProguardKeepRuleType.KEEP
- : ProguardKeepRuleType.KEEP_CLASS_MEMBERS);
return keepRuleBuilder.build();
}
@@ -528,14 +527,6 @@
}
skipWhitespace();
expectChar('}');
- } else {
- // If there are no member rules, a default rule for the parameterless constructor
- // applies. So we add that here.
- ProguardMemberRule.Builder defaultRuleBuilder = ProguardMemberRule.builder();
- defaultRuleBuilder.setName(Constants.INSTANCE_INITIALIZER_NAME);
- defaultRuleBuilder.setRuleType(ProguardMemberType.INIT);
- defaultRuleBuilder.setArguments(Collections.emptyList());
- classSpecificationBuilder.getMemberRules().add(defaultRuleBuilder.build());
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepPackageNamesRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepPackageNamesRule.java
new file mode 100644
index 0000000..787a31c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepPackageNamesRule.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2017, 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.shaking;
+
+import com.android.tools.r8.graph.DexAccessFlags;
+import java.util.List;
+import java.util.Set;
+
+public class ProguardKeepPackageNamesRule extends ProguardConfigurationRule {
+
+ public static class Builder extends ProguardConfigurationRule.Builder {
+
+ private Builder() {
+ }
+
+ public ProguardKeepPackageNamesRule build() {
+ return new ProguardKeepPackageNamesRule(classAnnotation, classAccessFlags,
+ negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
+ inheritanceClassName, inheritanceIsExtends, memberRules);
+ }
+ }
+
+ private ProguardKeepPackageNamesRule(
+ ProguardTypeMatcher classAnnotation,
+ DexAccessFlags classAccessFlags,
+ DexAccessFlags negatedClassAccessFlags,
+ boolean classTypeNegated,
+ ProguardClassType classType,
+ List<ProguardTypeMatcher> classNames,
+ ProguardTypeMatcher inheritanceAnnotation,
+ ProguardTypeMatcher inheritanceClassName,
+ boolean inheritanceIsExtends,
+ Set<ProguardMemberRule> memberRules) {
+ super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
+ classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+ }
+
+ public static ProguardKeepPackageNamesRule.Builder builder() {
+ return new ProguardKeepPackageNamesRule.Builder();
+ }
+
+ @Override
+ String typeString() {
+ return "keeppackagenames";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java
index e2f53b5..5db35cd 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java
@@ -8,51 +8,30 @@
public boolean allowsShrinking = false;
public boolean allowsOptimization = false;
public boolean allowsObfuscation = false;
- public boolean whyAreYouKeeping = false;
public boolean includeDescriptorClasses = false;
- public boolean keepPackageNames = false;
- public boolean checkDiscarded = false;
-
- void setFlagsToHaveNoEffect() {
- allowsShrinking = true;
- allowsOptimization = true;
- allowsObfuscation = true;
- whyAreYouKeeping = false;
- includeDescriptorClasses = false;
- keepPackageNames = false;
- }
private Builder() {}
ProguardKeepRuleModifiers build() {
return new ProguardKeepRuleModifiers(allowsShrinking, allowsOptimization, allowsObfuscation,
- whyAreYouKeeping, includeDescriptorClasses, keepPackageNames, checkDiscarded);
+ includeDescriptorClasses);
}
}
public final boolean allowsShrinking;
public final boolean allowsOptimization;
public final boolean allowsObfuscation;
- public final boolean whyAreYouKeeping;
public final boolean includeDescriptorClasses;
- public final boolean keepPackageNames;
- public final boolean checkDiscarded;
private ProguardKeepRuleModifiers(
boolean allowsShrinking,
boolean allowsOptimization,
boolean allowsObfuscation,
- boolean whyAreYouKeeping,
- boolean includeDescriptorClasses,
- boolean keepPackageNames,
- boolean checkDiscarded) {
+ boolean includeDescriptorClasses) {
this.allowsShrinking = allowsShrinking;
this.allowsOptimization = allowsOptimization;
this.allowsObfuscation = allowsObfuscation;
- this.whyAreYouKeeping = whyAreYouKeeping;
this.includeDescriptorClasses = includeDescriptorClasses;
- this.keepPackageNames = keepPackageNames;
- this.checkDiscarded = checkDiscarded;
}
/**
* Create a new empty builder.
@@ -71,8 +50,7 @@
return allowsShrinking == that.allowsShrinking
&& allowsOptimization == that.allowsOptimization
&& allowsObfuscation == that.allowsObfuscation
- && includeDescriptorClasses == that.includeDescriptorClasses
- && keepPackageNames == that.keepPackageNames;
+ && includeDescriptorClasses == that.includeDescriptorClasses;
}
@Override
@@ -80,9 +58,7 @@
return (allowsShrinking ? 1 : 0)
| (allowsOptimization ? 2 : 0)
| (allowsObfuscation ? 4 : 0)
- | (whyAreYouKeeping ? 8 : 0)
- | (includeDescriptorClasses ? 16 : 0)
- | (keepPackageNames ? 32 : 0);
+ | (includeDescriptorClasses ? 8 : 0);
}
@Override
@@ -91,9 +67,7 @@
appendWithComma(builder, allowsObfuscation, "allowobfuscation");
appendWithComma(builder, allowsShrinking, "allowshrinking");
appendWithComma(builder, allowsOptimization, "allowoptimization");
- appendWithComma(builder, whyAreYouKeeping, "whyareyoukeeping");
appendWithComma(builder, includeDescriptorClasses, "includedescriptorclasses");
- appendWithComma(builder, keepPackageNames, "keeppackagenames");
return builder.toString();
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
index 1e2d6d4..f3e6c6f 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
@@ -200,7 +200,7 @@
public boolean matches(DexEncodedMethod method, RootSetBuilder builder) {
switch (getRuleType()) {
case ALL_METHODS:
- if (method.accessFlags.isConstructor() && method.accessFlags.isStatic()) {
+ if (method.isClassInitializer()) {
break;
}
case ALL:
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardWhyAreYouKeepingRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardWhyAreYouKeepingRule.java
new file mode 100644
index 0000000..581a0db
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardWhyAreYouKeepingRule.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2017, 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.shaking;
+
+import com.android.tools.r8.graph.DexAccessFlags;
+import java.util.List;
+import java.util.Set;
+
+public class ProguardWhyAreYouKeepingRule extends ProguardConfigurationRule {
+
+ public static class Builder extends ProguardConfigurationRule.Builder {
+
+ private Builder() {
+ }
+
+ public ProguardWhyAreYouKeepingRule build() {
+ return new ProguardWhyAreYouKeepingRule(classAnnotation, classAccessFlags,
+ negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
+ inheritanceClassName, inheritanceIsExtends, memberRules);
+ }
+ }
+
+ private ProguardWhyAreYouKeepingRule(
+ ProguardTypeMatcher classAnnotation,
+ DexAccessFlags classAccessFlags,
+ DexAccessFlags negatedClassAccessFlags,
+ boolean classTypeNegated,
+ ProguardClassType classType,
+ List<ProguardTypeMatcher> classNames,
+ ProguardTypeMatcher inheritanceAnnotation,
+ ProguardTypeMatcher inheritanceClassName,
+ boolean inheritanceIsExtends,
+ Set<ProguardMemberRule> memberRules) {
+ super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
+ classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+ }
+
+ public static ProguardWhyAreYouKeepingRule.Builder builder() {
+ return new ProguardWhyAreYouKeepingRule.Builder();
+ }
+
+ @Override
+ String typeString() {
+ return "whyareyoukeeping";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 6ac5694..1ddd7d3 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -3,9 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
-import com.google.common.base.Equivalence.Wrapper;
-import com.google.common.collect.Sets;
-
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexAnnotationSet;
@@ -24,9 +21,11 @@
import com.android.tools.r8.shaking.ProguardTypeMatcher.MatchSpecificType;
import com.android.tools.r8.utils.MethodSignatureEquivalence;
import com.android.tools.r8.utils.ThreadUtils;
-
+import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.Sets;
import java.io.PrintStream;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
@@ -185,12 +184,23 @@
}
case KEEP: {
markClass(clazz, rule);
- markClass(clazz, rule);
markMatchingVisibleMethods(clazz, memberKeepRules, rule, null);
markMatchingFields(clazz, memberKeepRules, rule, null);
break;
}
}
+ } else if (rule instanceof ProguardCheckDiscardRule) {
+ if (memberKeepRules.isEmpty()) {
+ markClass(clazz, rule);
+ } else {
+ markMatchingFields(clazz, memberKeepRules, rule, clazz.type);
+ markMatchingMethods(clazz, memberKeepRules, rule, clazz.type);
+ }
+ } else if (rule instanceof ProguardWhyAreYouKeepingRule
+ || rule instanceof ProguardKeepPackageNamesRule) {
+ markClass(clazz, rule);
+ markMatchingVisibleMethods(clazz, memberKeepRules, rule, null);
+ markMatchingFields(clazz, memberKeepRules, rule, null);
} else if (rule instanceof ProguardAssumeNoSideEffectRule) {
markMatchingVisibleMethods(clazz, memberKeepRules, rule, null);
markMatchingFields(clazz, memberKeepRules, rule, null);
@@ -247,13 +257,24 @@
Collection<ProguardMemberRule> memberKeepRules, ProguardConfigurationRule rule,
DexType onlyIfClassKept) {
Set<Wrapper<DexMethod>> methodsMarked = new HashSet<>();
+ Arrays.stream(clazz.directMethods()).forEach(method ->
+ markMethod(method, memberKeepRules, rule, methodsMarked, onlyIfClassKept));
while (clazz != null) {
- clazz.forEachMethod(method ->
+ Arrays.stream(clazz.virtualMethods()).forEach(method ->
markMethod(method, memberKeepRules, rule, methodsMarked, onlyIfClassKept));
clazz = application.definitionFor(clazz.superType);
}
}
+ private void markMatchingMethods(DexClass clazz,
+ Collection<ProguardMemberRule> memberKeepRules, ProguardConfigurationRule rule,
+ DexType onlyIfClassKept) {
+ Arrays.stream(clazz.directMethods()).forEach(method ->
+ markMethod(method, memberKeepRules, rule, null, onlyIfClassKept));
+ Arrays.stream(clazz.virtualMethods()).forEach(method ->
+ markMethod(method, memberKeepRules, rule, null, onlyIfClassKept));
+ }
+
private void markMatchingFields(DexClass clazz,
Collection<ProguardMemberRule> memberKeepRules, ProguardConfigurationRule rule,
DexType onlyIfClassKept) {
@@ -375,7 +396,8 @@
private void markMethod(DexEncodedMethod method, Collection<ProguardMemberRule> rules,
ProguardConfigurationRule context, Set<Wrapper<DexMethod>> methodsMarked,
DexType onlyIfClassKept) {
- if (methodsMarked.contains(MethodSignatureEquivalence.get().wrap(method.method))) {
+ if ((methodsMarked != null)
+ && methodsMarked.contains(MethodSignatureEquivalence.get().wrap(method.method))) {
return;
}
for (ProguardMemberRule rule : rules) {
@@ -384,6 +406,9 @@
Log.verbose(getClass(), "Marking method `%s` due to `%s { %s }`.", method, context,
rule);
}
+ if (methodsMarked != null) {
+ methodsMarked.add(MethodSignatureEquivalence.get().wrap(method.method));
+ }
addItemToSets(method, context, rule, onlyIfClassKept);
}
}
@@ -461,24 +486,19 @@
if (!modifiers.allowsObfuscation) {
noObfuscation.add(item);
}
- if (modifiers.whyAreYouKeeping) {
- assert onlyIfClassKept == null;
- reasonAsked.add(item);
- }
- if (modifiers.keepPackageNames) {
- assert onlyIfClassKept == null;
- keepPackageName.add(item);
- }
if (modifiers.includeDescriptorClasses) {
includeDescriptorClasses(item, keepRule);
}
- if (modifiers.checkDiscarded) {
- checkDiscarded.add(item);
- }
} else if (context instanceof ProguardAssumeNoSideEffectRule) {
noSideEffects.put(item, rule);
+ } else if (context instanceof ProguardWhyAreYouKeepingRule) {
+ reasonAsked.add(item);
+ } else if (context instanceof ProguardKeepPackageNamesRule) {
+ keepPackageName.add(item);
} else if (context instanceof ProguardAssumeValuesRule) {
assumedValues.put(item, rule);
+ } else if (context instanceof ProguardCheckDiscardRule) {
+ checkDiscarded.add(item);
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java b/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
index e01439d..d3306d9 100644
--- a/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
@@ -379,7 +379,7 @@
private DexEncodedMethod renameConstructors(DexEncodedMethod method) {
// Only rename instance initializers.
- if (!method.accessFlags.isConstructor() || method.accessFlags.isStatic()) {
+ if (!method.isInstanceInitializer()) {
return method;
}
DexType holder = method.method.holder;
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 2a5c9e0..d8fd6c2 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -113,7 +113,7 @@
}
private boolean isDefaultConstructor(DexEncodedMethod method) {
- return method.accessFlags.isConstructor() && !method.accessFlags.isStatic()
+ return method.isInstanceInitializer()
&& method.method.proto.parameters.isEmpty();
}
diff --git a/src/main/java/com/android/tools/r8/shaking/protolite/ProtoLitePruner.java b/src/main/java/com/android/tools/r8/shaking/protolite/ProtoLitePruner.java
index 8b0ac9e..4610f41 100644
--- a/src/main/java/com/android/tools/r8/shaking/protolite/ProtoLitePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/protolite/ProtoLitePruner.java
@@ -196,7 +196,7 @@
// Remove now unneeded constructor calls.
InvokeStatic invokeStatic = insn.asInvokeStatic();
DexMethod invokedMethod = invokeStatic.getInvokedMethod();
- if ((invokeStatic.outValue().numberOfAllUsers() == 0)
+ if ((!invokeStatic.outValue().isUsed())
&& invokedMethod.proto.returnType.isSubtypeOf(protobufListType, appInfo)) {
it.remove();
}
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index b328895..2480417 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -52,7 +52,9 @@
private final Resource proguardMap;
private final Resource proguardSeeds;
private final Resource packageDistribution;
- private final Resource mainDexList;
+ private final List<Resource> mainDexListResources;
+ private final List<String> mainDexClasses;
+ private final Resource mainDexListOutput;
// See factory methods and AndroidApp.Builder below.
private AndroidApp(
@@ -64,7 +66,9 @@
Resource proguardMap,
Resource proguardSeeds,
Resource packageDistribution,
- Resource mainDexList) {
+ List<Resource> mainDexListResources,
+ List<String> mainDexClasses,
+ Resource mainDexListOutput) {
this.programResources = programResources;
this.programFileArchiveReaders = programFileArchiveReaders;
this.classpathResourceProviders = classpathResourceProviders;
@@ -73,7 +77,9 @@
this.proguardMap = proguardMap;
this.proguardSeeds = proguardSeeds;
this.packageDistribution = packageDistribution;
- this.mainDexList = mainDexList;
+ this.mainDexListResources = mainDexListResources;
+ this.mainDexClasses = mainDexClasses;
+ this.mainDexListOutput = mainDexListOutput;
}
/**
@@ -239,17 +245,45 @@
}
/**
- * True if the main dex list resource exists.
+ * True if the main dex list resources exists.
*/
public boolean hasMainDexList() {
- return mainDexList != null;
+ return !(mainDexListResources.isEmpty() && mainDexClasses.isEmpty());
}
/**
- * Get the input stream of the main dex list resource if it exists.
+ * True if the main dex list resources exists.
*/
- public InputStream getMainDexList(Closer closer) throws IOException {
- return mainDexList == null ? null : closer.register(mainDexList.getStream());
+ public boolean hasMainDexListResources() {
+ return !mainDexListResources.isEmpty();
+ }
+
+ /**
+ * Get the main dex list resources if any.
+ */
+ public List<Resource> getMainDexListResources() {
+ return mainDexListResources;
+ }
+
+ /**
+ * Get the main dex classes if any.
+ */
+ public List<String> getMainDexClasses() {
+ return mainDexClasses;
+ }
+
+ /**
+ * True if the main dex list resource exists.
+ */
+ public boolean hasMainDexListOutput() {
+ return mainDexListOutput != null;
+ }
+
+ /**
+ * Get the main dex list output resources if any.
+ */
+ public InputStream getMainDexListOutput(Closer closer) throws IOException {
+ return mainDexListOutput == null ? null : closer.register(mainDexListOutput.getStream());
}
/**
@@ -361,7 +395,7 @@
}
public void writeMainDexList(Closer closer, OutputStream out) throws IOException {
- InputStream input = getMainDexList(closer);
+ InputStream input = getMainDexListOutput(closer);
assert input != null;
out.write(ByteStreams.toByteArray(input));
}
@@ -385,7 +419,9 @@
private Resource proguardMap;
private Resource proguardSeeds;
private Resource packageDistribution;
- private Resource mainDexList;
+ private List<Resource> mainDexListResources = new ArrayList<>();
+ private List<String> mainDexListClasses = new ArrayList<>();
+ private Resource mainDexListOutput;
// See AndroidApp::builder().
private Builder() {
@@ -401,7 +437,9 @@
proguardMap = app.proguardMap;
proguardSeeds = app.proguardSeeds;
packageDistribution = app.packageDistribution;
- mainDexList = app.mainDexList;
+ mainDexListResources = app.mainDexListResources;
+ mainDexListClasses = app.mainDexClasses;
+ mainDexListOutput = app.mainDexListOutput;
}
/**
@@ -585,22 +623,49 @@
}
/**
- * Set the main-dex list file.
+ * Add a main-dex list file.
*/
- public Builder setMainDexListFile(Path file) {
- mainDexList = file == null ? null : Resource.fromFile(null, file);
+ public Builder addMainDexListFiles(Path... files) throws IOException {
+ return addMainDexListFiles(Arrays.asList(files));
+ }
+
+ public Builder addMainDexListFiles(Collection<Path> files) throws IOException {
+ for (Path file : files) {
+ if (!Files.exists(file)) {
+ throw new FileNotFoundException("Non-existent input file: " + file);
+ }
+ // TODO(sgjesse): Should we just read the file here? This will sacrifice the parallelism
+ // in ApplicationReader where all input resources are read in parallel.
+ mainDexListResources.add(Resource.fromFile(null, file));
+ }
+ return this;
+ }
+
+
+ /**
+ * Add main-dex classes.
+ */
+ public Builder addMainDexClasses(String... classes) {
+ return addMainDexClasses(Arrays.asList(classes));
+ }
+
+ /**
+ * Add main-dex classes.
+ */
+ public Builder addMainDexClasses(Collection<String> classes) {
+ mainDexListClasses.addAll(classes);
return this;
}
public boolean hasMainDexList() {
- return mainDexList != null;
+ return !(mainDexListResources.isEmpty() && mainDexListClasses.isEmpty());
}
/**
- * Set the main-dex list data.
+ * Set the main-dex list output data.
*/
- public Builder setMainDexListData(byte[] content) {
- mainDexList = content == null ? null : Resource.fromBytes(null, content);
+ public Builder setMainDexListOutputData(byte[] content) {
+ mainDexListOutput = content == null ? null : Resource.fromBytes(null, content);
return this;
}
@@ -617,7 +682,9 @@
proguardMap,
proguardSeeds,
packageDistribution,
- mainDexList);
+ mainDexListResources,
+ mainDexListClasses,
+ mainDexListOutput);
}
private void addProgramFile(Path file) throws IOException {
diff --git a/src/main/java/com/android/tools/r8/utils/FileUtils.java b/src/main/java/com/android/tools/r8/utils/FileUtils.java
index 5c5a776..90c22f0 100644
--- a/src/main/java/com/android/tools/r8/utils/FileUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/FileUtils.java
@@ -13,7 +13,6 @@
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index eb746d7..9fea848 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.utils;
import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.dex.Marker;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
@@ -45,6 +46,9 @@
// Silencing output.
public boolean quiet = false;
+ // Hidden marker for classes.dex
+ public Marker customizedMarker;
+
public List<String> methodsFilter = ImmutableList.of();
public int minApiLevel = Constants.DEFAULT_ANDROID_API;
// Skipping min_api check and compiling an intermediate result intended for later merging.
@@ -69,7 +73,6 @@
public Path seedsFile;
public boolean printMapping;
public Path printMappingFile;
- public boolean printMainDexList;
public Path printMainDexListFile;
public boolean ignoreMissingClasses = false;
public boolean skipMinification = false;
@@ -84,6 +87,7 @@
public boolean allowParameterName = false;
public boolean debug = false;
+ public boolean singleStepDebug = false;
public final TestingOptions testing = new TestingOptions();
// TODO(zerny): These stateful dictionaries do not belong here.
@@ -141,7 +145,9 @@
}
public static class TestingOptions {
- public Function<List<DexEncodedMethod>, List<DexEncodedMethod>> irOrdering;
+
+ public Function<List<DexEncodedMethod>, List<DexEncodedMethod>> irOrdering
+ = Function.identity();
}
public static class AttributeRemovalOptions {
diff --git a/src/main/java/com/android/tools/r8/utils/MainDexList.java b/src/main/java/com/android/tools/r8/utils/MainDexList.java
index b219314..826901a 100644
--- a/src/main/java/com/android/tools/r8/utils/MainDexList.java
+++ b/src/main/java/com/android/tools/r8/utils/MainDexList.java
@@ -28,6 +28,18 @@
}
}
+ public static DexType parse(String clazz, DexItemFactory itemFactory) {
+ if (!clazz.endsWith(CLASS_EXTENSION)) {
+ throw new CompilationError("Illegal main-dex-list entry '" + clazz + "'.");
+ }
+ String name = clazz.substring(0, clazz.length() - CLASS_EXTENSION.length());
+ if (name.contains("" + JAVA_PACKAGE_SEPARATOR)) {
+ throw new CompilationError("Illegal main-dex-list entry '" + clazz + "'.");
+ }
+ String descriptor = "L" + name + ";";
+ return itemFactory.createType(descriptor);
+ }
+
public static Set<DexType> parse(InputStream input, DexItemFactory itemFactory) {
Set<DexType> result = Sets.newIdentityHashSet();
try {
@@ -35,15 +47,7 @@
new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
String line;
while ((line = file.readLine()) != null) {
- if (!line.endsWith(CLASS_EXTENSION)) {
- throw new CompilationError("Illegal main-dex-list entry '" + line + "'.");
- }
- String name = line.substring(0, line.length() - CLASS_EXTENSION.length());
- if (name.contains("" + JAVA_PACKAGE_SEPARATOR)) {
- throw new CompilationError("Illegal main-dex-list entry '" + line + "'.");
- }
- String descriptor = "L" + name + ";";
- result.add(itemFactory.createType(descriptor));
+ result.add(parse(line, itemFactory));
}
} catch (IOException e) {
throw new CompilationError("Cannot load main-dex-list.");
diff --git a/src/test/debugTestResources/Locals.java b/src/test/debugTestResources/Locals.java
index 3ad6596..3ab5d7b 100644
--- a/src/test/debugTestResources/Locals.java
+++ b/src/test/debugTestResources/Locals.java
@@ -188,6 +188,29 @@
}
}
+ public static int stepEmptyForLoopBody1(int n) {
+ int i;
+ for (i = 0; i < n; i++) ;
+ return i;
+ }
+
+ public static int stepEmptyForLoopBody2(int n) {
+ int i;
+ for (i = 0; i < n; i++) {
+ // has a line but still empty...
+ }
+ return i;
+ }
+
+ public static int stepNonEmptyForLoopBody(int n) {
+ int i;
+ for (i = 0; i < n; i++)
+ nop();
+ return i;
+ }
+
+ public static void nop() {}
+
public static void main(String[] args) {
noLocals();
unusedLocals();
@@ -198,6 +221,9 @@
reverseRange(1,2,3,4,5,6,7);
new Locals().lotsOfArrayLength();
new Locals().foo(21);
+ stepEmptyForLoopBody1(3);
+ stepEmptyForLoopBody2(3);
+ stepNonEmptyForLoopBody(3);
}
}
diff --git a/src/test/examples/inlining/Inlining.java b/src/test/examples/inlining/Inlining.java
index bd8870f..f89a0e1 100644
--- a/src/test/examples/inlining/Inlining.java
+++ b/src/test/examples/inlining/Inlining.java
@@ -52,14 +52,39 @@
this.a = a;
}
- @CheckDiscarded
InlineConstructor(long a) {
this((int) a);
}
+ InlineConstructor(int a, int loopy) {
+ this.a = a;
+ // Make this too big to inline.
+ if (loopy > 10) {
+ throw new RuntimeException("Too big!");
+ }
+ for (int i = 1; i < loopy; i++) {
+ this.a = this.a * i;
+ }
+ }
+
+ @CheckDiscarded
+ InlineConstructor() {
+ this(42, 9);
+ }
+
static InlineConstructor create() {
return new InlineConstructor(10L);
}
+
+ static InlineConstructor createMore() {
+ new InlineConstructor(0, 0);
+ new InlineConstructor(0, 0);
+ new InlineConstructor(0, 0);
+ new InlineConstructor(0, 0);
+ new InlineConstructor(0, 0);
+ new InlineConstructor(0, 0);
+ return new InlineConstructor();
+ }
}
class InlineConstructorOfInner {
@@ -152,6 +177,8 @@
InlineConstructor ic = InlineConstructor.create();
Assert(ic != null);
+ InlineConstructor ic2 = InlineConstructor.createMore();
+ Assert(ic2 != null);
InlineConstructorOfInner icoi = new InlineConstructorOfInner();
Assert(icoi != null);
diff --git a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
index 055a880..063d5fc 100644
--- a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
+++ b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
@@ -4850,7 +4850,7 @@
if (testMatch(failuresToTriage, name, compilerUnderTest, dexVm, compilationMode)) {
outcome = Outcome.FAILS_WITH_ART;
}
- if (testMatch(requiresInliningDisabled, name, compilerUnderTest, dexVm, compilationMode)) {
+ if (testMatch(timeoutsWithArt, name, compilerUnderTest, dexVm, compilationMode)) {
assert outcome == null;
outcome = Outcome.TIMEOUTS_WITH_ART;
}
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index fabf8e7..da561f8 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -752,12 +752,19 @@
"488-checker-inline-recursive-calls",
"487-checker-inline-calls",
"122-npe",
+ "141-class-unload",
// Calls some internal art methods that cannot tolerate inlining.
"466-get-live-vreg",
// Requires a certain call pattern to surface an Art bug.
- "534-checker-bce-deoptimization"
+ "534-checker-bce-deoptimization",
+
+ // Requires something to be allocated in a method so that it goes out of scope.
+ "059-finalizer-throw",
+
+ // Has tests in submethods, which we should not inline.
+ "625-checker-licm-regressions"
);
private static List<String> failuresToTriage = ImmutableList.of(
@@ -1087,49 +1094,47 @@
throws IOException, ProguardRuleParserException, ExecutionException, CompilationException {
assert mode != null;
switch (compilerUnderTest) {
- case D8:
- {
- assert keepRulesFile == null : "Keep-rules file specified for D8.";
- D8Command.Builder builder =
- D8Command.builder()
- .setMode(mode)
- .addProgramFiles(ListUtils.map(fileNames, Paths::get));
- Integer minSdkVersion = needMinSdkVersion.get(name);
- if (minSdkVersion != null) {
- builder.setMinApiLevel(minSdkVersion);
- }
- D8Output output = D8.run(builder.build());
- output.write(Paths.get(resultPath));
- break;
+ case D8: {
+ assert keepRulesFile == null : "Keep-rules file specified for D8.";
+ D8Command.Builder builder =
+ D8Command.builder()
+ .setMode(mode)
+ .addProgramFiles(ListUtils.map(fileNames, Paths::get));
+ Integer minSdkVersion = needMinSdkVersion.get(name);
+ if (minSdkVersion != null) {
+ builder.setMinApiLevel(minSdkVersion);
}
- case R8:
- {
- R8Command.Builder builder =
- R8Command.builder()
- .setMode(mode)
- .setOutputPath(Paths.get(resultPath))
- .addProgramFiles(ListUtils.map(fileNames, Paths::get))
- .setIgnoreMissingClasses(true);
- Integer minSdkVersion = needMinSdkVersion.get(name);
- if (minSdkVersion != null) {
- builder.setMinApiLevel(minSdkVersion);
- }
- if (keepRulesFile != null) {
- builder.addProguardConfigurationFiles(Paths.get(keepRulesFile));
- }
- // Add internal flags for testing purposes.
- ToolHelper.runR8(
- builder.build(),
- options -> {
- if (enableInterfaceMethodDesugaring.contains(name)) {
- options.interfaceMethodDesugaring = OffOrAuto.Auto;
- }
- if (disableInlining) {
- options.inlineAccessors = false;
- }
- });
- break;
+ D8Output output = D8.run(builder.build());
+ output.write(Paths.get(resultPath));
+ break;
+ }
+ case R8: {
+ R8Command.Builder builder =
+ R8Command.builder()
+ .setMode(mode)
+ .setOutputPath(Paths.get(resultPath))
+ .addProgramFiles(ListUtils.map(fileNames, Paths::get))
+ .setIgnoreMissingClasses(true);
+ Integer minSdkVersion = needMinSdkVersion.get(name);
+ if (minSdkVersion != null) {
+ builder.setMinApiLevel(minSdkVersion);
}
+ if (keepRulesFile != null) {
+ builder.addProguardConfigurationFiles(Paths.get(keepRulesFile));
+ }
+ // Add internal flags for testing purposes.
+ ToolHelper.runR8(
+ builder.build(),
+ options -> {
+ if (enableInterfaceMethodDesugaring.contains(name)) {
+ options.interfaceMethodDesugaring = OffOrAuto.Auto;
+ }
+ if (disableInlining) {
+ options.inlineAccessors = false;
+ }
+ });
+ break;
+ }
default:
assert false : compilerUnderTest;
}
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index e4b37f2..562518f 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -658,18 +658,36 @@
Consumer<ArtCommandBuilder> extras,
DexVm version)
throws IOException {
- // Run art on original.
- for (String file : files1) {
- assertTrue("file1 " + file + " must exists", Files.exists(Paths.get(file)));
+ return checkArtOutputIdentical(
+ version,
+ mainClass,
+ extras,
+ ImmutableList.of(ListUtils.map(files1, Paths::get), ListUtils.map(files2, Paths::get)));
+ }
+
+ public static String checkArtOutputIdentical(
+ DexVm version,
+ String mainClass,
+ Consumer<ArtCommandBuilder> extras,
+ Collection<Collection<Path>> programs)
+ throws IOException {
+ for (Collection<Path> program : programs) {
+ for (Path path : program) {
+ assertTrue("File " + path + " must exist", Files.exists(path));
+ }
}
- String output1 = ToolHelper.runArtNoVerificationErrors(files1, mainClass, extras, version);
- // Run art on R8 processed version.
- for (String file : files2) {
- assertTrue("file2 " + file + " must exists", Files.exists(Paths.get(file)));
+ String output = null;
+ for (Collection<Path> program : programs) {
+ String result =
+ ToolHelper.runArtNoVerificationErrors(
+ ListUtils.map(program, Path::toString), mainClass, extras, version);
+ if (output != null) {
+ assertEquals(output, result);
+ } else {
+ output = result;
+ }
}
- String output2 = ToolHelper.runArtNoVerificationErrors(files2, mainClass, extras, version);
- assertEquals(output1, output2);
- return output1;
+ return output;
}
public static void runDex2Oat(Path file, Path outFile) throws IOException {
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestoremove/RemoveVisibilityBridgeMethodsTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
similarity index 87%
rename from src/test/java/com/android/tools/r8/bridgeremoval/bridgestoremove/RemoveVisibilityBridgeMethodsTest.java
rename to src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
index c33d5b4..6e3c114 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestoremove/RemoveVisibilityBridgeMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
@@ -2,11 +2,13 @@
// 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.bridgeremoval.bridgestoremove;
+package com.android.tools.r8.bridgeremoval;
import static org.junit.Assert.assertFalse;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.bridgeremoval.bridgestoremove.Main;
+import com.android.tools.r8.bridgeremoval.bridgestoremove.Outer;
import com.android.tools.r8.utils.DexInspector;
import com.google.common.collect.ImmutableList;
import java.lang.reflect.Method;
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java
new file mode 100644
index 0000000..a56c0c8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java
@@ -0,0 +1,74 @@
+// Copyright (c) 2017, 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.checkdiscarded;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.checkdiscarded.testclasses.Main;
+import com.android.tools.r8.checkdiscarded.testclasses.UnusedClass;
+import com.android.tools.r8.checkdiscarded.testclasses.UsedClass;
+import com.android.tools.r8.checkdiscarded.testclasses.WillBeGone;
+import com.android.tools.r8.checkdiscarded.testclasses.WillStay;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class CheckDiscardedTest extends TestBase {
+
+ private void run(boolean obfuscate, Class annotation, boolean checkMembers, boolean shouldFail)
+ throws Exception {
+ List<Class> classes = ImmutableList.of(
+ UnusedClass.class,
+ UsedClass.class,
+ Main.class);
+ String proguardConfig = keepMainProguardConfiguration(Main.class, true, obfuscate)
+ + checkDiscardRule(checkMembers, annotation);
+ try {
+ compileWithR8(classes, proguardConfig, this::noInlining);
+ } catch (CompilationError e) {
+ Assert.assertTrue(shouldFail);
+ return;
+ }
+ Assert.assertFalse(shouldFail);
+ }
+
+ private void noInlining(InternalOptions options) {
+ options.inlineAccessors = false;
+ }
+
+ private String checkDiscardRule(boolean member, Class annotation) {
+ if (member) {
+ return "-checkdiscard class * { @" + annotation.getCanonicalName() + " *; }";
+ } else {
+ return "-checkdiscard @" + annotation.getCanonicalName() + " class *";
+ }
+ }
+
+ @Test
+ public void classesAreGone() throws Exception {
+ run(false, WillBeGone.class, false, false);
+ run(true, WillBeGone.class, false, false);
+ }
+
+ @Test
+ public void classesAreNotGone() throws Exception {
+ run(false, WillStay.class, false, true);
+ run(true, WillStay.class, false, true);
+ }
+
+ @Test
+ public void membersAreGone() throws Exception {
+ run(false, WillBeGone.class, true, false);
+ run(true, WillBeGone.class, true, false);
+ }
+
+ @Test
+ public void membersAreNotGone() throws Exception {
+ run(false, WillStay.class, true, true);
+ run(true, WillStay.class, true, true);
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/Main.java b/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/Main.java
new file mode 100644
index 0000000..677330a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/Main.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2017, 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.checkdiscarded.testclasses;
+
+@WillStay
+public class Main {
+
+ public static void main(String... args) {
+ UsedClass usedClass = new UsedClass();
+ System.out.println(usedClass.hello());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/UnusedClass.java b/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/UnusedClass.java
new file mode 100644
index 0000000..1b1689b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/UnusedClass.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2017, 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.checkdiscarded.testclasses;
+
+@WillBeGone
+public class UnusedClass {
+
+}
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/UsedClass.java b/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/UsedClass.java
new file mode 100644
index 0000000..253c114
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/UsedClass.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2017, 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.checkdiscarded.testclasses;
+
+@WillStay
+public class UsedClass {
+
+ @WillStay
+ public String hello() {
+ return "hello";
+ }
+
+ @WillBeGone
+ public String world() {
+ return "world";
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/WillBeGone.java b/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/WillBeGone.java
new file mode 100644
index 0000000..9176911
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/WillBeGone.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2017, 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.checkdiscarded.testclasses;
+
+public @interface WillBeGone {
+
+}
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/WillStay.java b/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/WillStay.java
new file mode 100644
index 0000000..e1925c3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/testclasses/WillStay.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2017, 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.checkdiscarded.testclasses;
+
+public @interface WillStay {
+
+}
diff --git a/src/test/java/com/android/tools/r8/d8/D8FrameworkTest.java b/src/test/java/com/android/tools/r8/d8/D8FrameworkTest.java
new file mode 100644
index 0000000..2878ed0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/d8/D8FrameworkTest.java
@@ -0,0 +1,79 @@
+// Copyright (c) 2017, the Rex 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.d8;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.dex.Marker;
+import com.android.tools.r8.dex.Marker.Tool;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Timing;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * Simple test that compiles framework.jar with D8 a number of times with
+ * various number of threads available to the compiler.
+ * This test also tests the hidden marker inserted into classes.dex.
+ */
+@RunWith( Parameterized.class )
+public class D8FrameworkTest {
+
+ private static final Path FRAMEWORK_JAR =
+ Paths.get("tools/linux/art-5.1.1/product/mako/system/framework/framework.jar");
+
+ @Rule
+ public TemporaryFolder output = ToolHelper.getTemporaryFolderForTest();
+
+ @Parameters(name = "Number of threads = {0}")
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][] { {1}, {2}, {4}, {8}, {16} });
+ }
+
+ private final int threads;
+
+ public D8FrameworkTest(int threads) {
+ this.threads = threads;
+ }
+
+ @Test
+ public void compile() throws CompilationException, IOException, ExecutionException {
+ D8Command command = D8Command.builder()
+ .setMinApiLevel(Constants.ANDROID_N_API)
+ .addProgramFiles(FRAMEWORK_JAR)
+ .build();
+ Marker marker = new Marker(Tool.D8)
+ .put("revision", "1.0.0")
+ .put("threads", threads);
+ Marker selfie = Marker.parse(marker.toString());
+ assert marker.equals(selfie);
+ AndroidApp app = ToolHelper.runD8(command, options -> {
+ options.customizedMarker = marker;
+ options.numberOfThreads = threads;
+ });
+ DexApplication dexApp =
+ new ApplicationReader(app, new InternalOptions(), new Timing("D8FrameworkTest")).read();
+ Marker readMarker = dexApp.dexItemFactory.extractMarker();
+ assertEquals(marker, readMarker);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index 6a5f738..57a1ce8 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -623,15 +623,24 @@
));
}
+ private void failNoLocal(String localName) {
+ Assert.fail(
+ "line " + getLineNumber() + ": Expected local '" + localName + "' not present");
+ }
+
public void checkLocal(String localName) {
Optional<Variable> localVar = JUnit3Wrapper
.getVariableAt(mirror, getLocation(), localName);
- Assert.assertTrue("No local '" + localName + "'", localVar.isPresent());
+ if (!localVar.isPresent()) {
+ failNoLocal(localName);
+ }
}
public void checkLocal(String localName, Value expectedValue) {
Optional<Variable> localVar = getVariableAt(mirror, getLocation(), localName);
- Assert.assertTrue("No local '" + localName + "'", localVar.isPresent());
+ if (!localVar.isPresent()) {
+ failNoLocal(localName);
+ }
// Get value
CommandPacket commandPacket = new CommandPacket(
diff --git a/src/test/java/com/android/tools/r8/debug/LocalsTest.java b/src/test/java/com/android/tools/r8/debug/LocalsTest.java
index 1fd4bc9..16d1a60 100644
--- a/src/test/java/com/android/tools/r8/debug/LocalsTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LocalsTest.java
@@ -348,4 +348,79 @@
run());
}
+ @Test
+ public void testStepEmptyForLoopBody1() throws Throwable {
+ runDebugTest(
+ "Locals",
+ breakpoint("Locals", "stepEmptyForLoopBody1"),
+ run(),
+ checkLocal("n", Value.createInt(3)),
+ checkNoLocal("i"),
+ stepOver(),
+ checkLocal("n", Value.createInt(3)),
+ checkLocal("i", Value.createInt(3)),
+ run());
+ }
+
+ @Test
+ public void testStepEmptyForLoopBody2() throws Throwable {
+ runDebugTest(
+ "Locals",
+ breakpoint("Locals", "stepEmptyForLoopBody2"),
+ run(),
+ checkLocal("n", Value.createInt(3)),
+ checkNoLocal("i"),
+ stepOver(),
+ checkLocal("n", Value.createInt(3)),
+ checkLocal("i", Value.createInt(3)),
+ run());
+ }
+
+ @Test
+ public void testStepNonEmptyForLoopBody() throws Throwable {
+ final int LOOP_HEADER_LINE = 207;
+ final int LOOP_BODY_LINE = 208;
+ final int RETURN_LINE = 209;
+ final Value N = Value.createInt(3);
+ final Value I0 = Value.createInt(0);
+ final Value I1 = Value.createInt(1);
+ final Value I2 = Value.createInt(2);
+ final Value I3 = Value.createInt(3);
+ runDebugTest(
+ "Locals",
+ breakpoint("Locals", "stepNonEmptyForLoopBody"),
+ run(),
+ checkLine(SOURCE_FILE, LOOP_HEADER_LINE),
+ checkLocal("n", N),
+ checkNoLocal("i"),
+ stepOver(),
+ checkLine(SOURCE_FILE, LOOP_BODY_LINE),
+ checkLocal("n", N),
+ checkLocal("i", I0),
+ stepOver(),
+ checkLine(SOURCE_FILE, LOOP_HEADER_LINE),
+ checkLocal("n", N),
+ checkLocal("i", I0),
+ stepOver(),
+ checkLine(SOURCE_FILE, LOOP_BODY_LINE),
+ checkLocal("n", N),
+ checkLocal("i", I1),
+ stepOver(),
+ checkLine(SOURCE_FILE, LOOP_HEADER_LINE),
+ checkLocal("n", N),
+ checkLocal("i", I1),
+ stepOver(),
+ checkLine(SOURCE_FILE, LOOP_BODY_LINE),
+ checkLocal("n", N),
+ checkLocal("i", I2),
+ stepOver(),
+ checkLine(SOURCE_FILE, LOOP_HEADER_LINE),
+ checkLocal("n", N),
+ checkLocal("i", I2),
+ stepOver(),
+ checkLine(SOURCE_FILE, RETURN_LINE),
+ checkLocal("n", N),
+ checkLocal("i", I3),
+ run());
+ }
}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/ConditionalLocalTest.java b/src/test/java/com/android/tools/r8/debuginfo/ConditionalLocalTest.java
new file mode 100644
index 0000000..5a83545
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/ConditionalLocalTest.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2017, 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.debuginfo;
+
+public class ConditionalLocalTest {
+
+ public void foo(int x) {
+ if (x % 2 != 0) {
+ Integer obj = new Integer(x + x);
+ long l = obj.longValue();
+ x = (int) l;
+ System.out.print(obj);
+ }
+ return;
+ }
+
+ public static void main(String[] args) {
+ new ConditionalLocalTest().foo(21);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/ConditionalLocalTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/ConditionalLocalTestRunner.java
new file mode 100644
index 0000000..8348c0b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/ConditionalLocalTestRunner.java
@@ -0,0 +1,41 @@
+// Copyright (c) 2017, 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.debuginfo;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.utils.AndroidApp;
+import org.junit.Test;
+
+public class ConditionalLocalTestRunner extends DebugInfoTestBase {
+
+ @Test
+ public void testConditionalLocal() throws Exception {
+ Class clazz = ConditionalLocalTest.class;
+
+ AndroidApp d8App = compileWithD8(clazz);
+ AndroidApp dxApp = getDxCompiledSources();
+
+ String expected = "42";
+ assertEquals(expected, runOnJava(clazz));
+ assertEquals(expected, runOnArt(d8App, clazz.getCanonicalName()));
+ assertEquals(expected, runOnArt(dxApp, clazz.getCanonicalName()));
+
+ checkConditonalLocal(inspectMethod(d8App, clazz, "void", "foo", "int"));
+ checkConditonalLocal(inspectMethod(dxApp, clazz, "void", "foo", "int"));
+ }
+
+ private void checkConditonalLocal(DebugInfoInspector info) {
+ String self = ConditionalLocalTest.class.getCanonicalName();
+ String Integer = "java.lang.Integer";
+ info.checkStartLine(9);
+ info.checkLineHasExactLocals(9, "this", self, "x", "int");
+ info.checkLineHasExactLocals(10, "this", self, "x", "int");
+ info.checkLineHasExactLocals(11, "this", self, "x", "int", "obj", Integer);
+ info.checkLineHasExactLocals(12, "this", self, "x", "int", "obj", Integer, "l", "long");
+ info.checkLineHasExactLocals(13, "this", self, "x", "int", "obj", Integer, "l", "long");
+ info.checkNoLine(14);
+ info.checkLineHasExactLocals(15, "this", self, "x", "int");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/LocalsAtThrowTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/LocalsAtThrowTestRunner.java
index 8df6f79..8a5b77d 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/LocalsAtThrowTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/LocalsAtThrowTestRunner.java
@@ -22,11 +22,11 @@
assertEquals(expected, runOnArt(d8App, clazz.getCanonicalName()));
assertEquals(expected, runOnArt(dxApp, clazz.getCanonicalName()));
- checkBackBranchToSelf(inspectMethod(d8App, clazz, "int", "localsAtThrow", "int"));
- checkBackBranchToSelf(inspectMethod(dxApp, clazz, "int", "localsAtThrow", "int"));
+ checkLocalsAtThrow(inspectMethod(d8App, clazz, "int", "localsAtThrow", "int"));
+ checkLocalsAtThrow(inspectMethod(dxApp, clazz, "int", "localsAtThrow", "int"));
}
- private void checkBackBranchToSelf(DebugInfoInspector info) {
+ private void checkLocalsAtThrow(DebugInfoInspector info) {
info.checkStartLine(9);
info.checkLineHasExactLocals(9, "x", "int");
info.checkLineHasExactLocals(10, "x", "int", "a", "int");
diff --git a/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java b/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java
new file mode 100644
index 0000000..98f1071
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java
@@ -0,0 +1,108 @@
+// Copyright (c) 2017, 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.dex;
+
+import com.android.tools.r8.code.Const4;
+import com.android.tools.r8.code.ConstString;
+import com.android.tools.r8.code.Goto32;
+import com.android.tools.r8.code.IfEq;
+import com.android.tools.r8.code.IfEqz;
+import com.android.tools.r8.code.IfNe;
+import com.android.tools.r8.code.IfNez;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.ReturnVoid;
+import com.android.tools.r8.graph.DexAccessFlags;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexCode.Try;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.naming.NamingLens;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+
+public class JumboStringProcessing {
+
+ @Test
+ public void branching() {
+ DexItemFactory factory = new DexItemFactory();
+ DexString string = factory.createString("turn into jumbo");
+ factory.sort(NamingLens.getIdentityLens());
+ Instruction[] instructions = buildInstructions(string, false);
+ DexCode code = jumboStringProcess(factory, string, instructions);
+ Instruction[] rewrittenInstructions = code.instructions;
+ assert rewrittenInstructions[1] instanceof IfEq;
+ IfEq condition = (IfEq) rewrittenInstructions[1];
+ assert condition.getOffset() + condition.CCCC == rewrittenInstructions[3].getOffset();
+ assert rewrittenInstructions[2] instanceof Goto32;
+ Goto32 jump = (Goto32) rewrittenInstructions[2];
+ Instruction lastInstruction = rewrittenInstructions[rewrittenInstructions.length - 1];
+ assert jump.getOffset() + jump.AAAAAAAA == lastInstruction.getOffset();
+ }
+
+ @Test
+ public void branching2() {
+ DexItemFactory factory = new DexItemFactory();
+ DexString string = factory.createString("turn into jumbo");
+ factory.sort(NamingLens.getIdentityLens());
+ Instruction[] instructions = buildInstructions(string, true);
+ DexCode code = jumboStringProcess(factory, string, instructions);
+ Instruction[] rewrittenInstructions = code.instructions;
+ assert rewrittenInstructions[1] instanceof IfEqz;
+ IfEqz condition = (IfEqz) rewrittenInstructions[1];
+ assert condition.getOffset() + condition.BBBB == rewrittenInstructions[3].getOffset();
+ assert rewrittenInstructions[2] instanceof Goto32;
+ Goto32 jump = (Goto32) rewrittenInstructions[2];
+ Instruction lastInstruction = rewrittenInstructions[rewrittenInstructions.length - 1];
+ assert jump.getOffset() + jump.AAAAAAAA == lastInstruction.getOffset();
+ }
+
+ private Instruction[] buildInstructions(DexString string, boolean zeroCondition) {
+ List<Instruction> instructions = new ArrayList<>();
+ int offset = 0;
+ Instruction instr = new Const4(0, 0);
+ instr.setOffset(offset);
+ instructions.add(instr);
+ offset += instr.getSize();
+ int lastInstructionOffset = 15000 * 2 + 2 + offset;
+ if (zeroCondition) {
+ instr = new IfNez(0, lastInstructionOffset - offset);
+ } else {
+ instr = new IfNe(0, 0, lastInstructionOffset - offset);
+ }
+ instr.setOffset(offset);
+ instructions.add(instr);
+ offset += instr.getSize();
+ for (int i = 0; i < 15000; i++) {
+ instr = new ConstString(0, string);
+ instr.setOffset(offset);
+ instructions.add(instr);
+ offset += instr.getSize();
+ }
+ instr = new ReturnVoid();
+ instr.setOffset(offset);
+ instructions.add(instr);
+ assert instr.getOffset() == lastInstructionOffset;
+ return instructions.toArray(new Instruction[instructions.size()]);
+ }
+
+ private DexCode jumboStringProcess(
+ DexItemFactory factory, DexString string, Instruction[] instructions) {
+ DexCode code = new DexCode(
+ 1,
+ 0,
+ 0,
+ instructions,
+ new Try[0],
+ null,
+ null,
+ null);
+ DexAccessFlags flags = new DexAccessFlags(0);
+ flags.setPublic();
+ DexEncodedMethod method = new DexEncodedMethod(null, flags, null, null, code);
+ new JumboStringRewriter(method, string, factory).rewrite();
+ return method.getCode().asDexCode();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
index f7a9caa..10947a4 100644
--- a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
+++ b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
@@ -157,8 +157,8 @@
throws IOException {
try (Closer closer = Closer.create()) {
if (write) {
- app1.writeToDirectory(Paths.get("app1"), OutputMode.Indexed);
- app2.writeToDirectory(Paths.get("app2"), OutputMode.Indexed);
+ app1.writeToDirectory(temp.newFolder("app1").toPath(), OutputMode.Indexed);
+ app2.writeToDirectory(temp.newFolder("app2").toPath(), OutputMode.Indexed);
}
List<Resource> files1 = app1.getDexProgramResources();
List<Resource> files2 = app2.getDexProgramResources();
diff --git a/src/test/java/com/android/tools/r8/jasmin/JumboStringTests.java b/src/test/java/com/android/tools/r8/jasmin/JumboStringTests.java
new file mode 100644
index 0000000..61b624e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/jasmin/JumboStringTests.java
@@ -0,0 +1,104 @@
+// Copyright (c) 2017, 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.jasmin;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.debuginfo.DebugInfoInspector;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map.Entry;
+import org.junit.Test;
+
+public class JumboStringTests extends JasminTestBase {
+
+ // String constants are split into several class files to ensure both the constant-pool and
+ // instruction count are below the class-file limits.
+ private static int CLASSES_COUNT = 10;
+ private static int MIN_STRING_COUNT = Constants.FIRST_JUMBO_INDEX + 1;
+ private static int EXTRA_STRINGS_PER_CLASSES_COUNT = MIN_STRING_COUNT % CLASSES_COUNT;
+ private static int STRINGS_PER_CLASSES_COUNT =
+ EXTRA_STRINGS_PER_CLASSES_COUNT + MIN_STRING_COUNT / CLASSES_COUNT;
+
+ @Test
+ public void test() throws Exception {
+ JasminBuilder builder = new JasminBuilder();
+ LinkedHashMap<String, MethodSignature> classes = new LinkedHashMap<>(CLASSES_COUNT);
+ for (int i = 0; i < CLASSES_COUNT; i++) {
+ JasminBuilder.ClassBuilder clazz = builder.addClass("Test" + i);
+ List<String> lines = new ArrayList<>(STRINGS_PER_CLASSES_COUNT + 100);
+ lines.addAll(
+ ImmutableList.of(
+ ".limit locals 3",
+ ".limit stack 4",
+ ".var 0 is this LTest; from L0 to L2",
+ ".var 1 is i I from L0 to L2",
+ ".var 2 is strings [Ljava/lang/String; from L1 to L2",
+ "L0:",
+ ".line 1",
+ " ldc " + STRINGS_PER_CLASSES_COUNT,
+ " anewarray java/lang/String",
+ " astore 2",
+ "L1:",
+ ".line 2"));
+ for (int j = 0; j < STRINGS_PER_CLASSES_COUNT; j++) {
+ lines.add(" aload 2");
+ lines.add(" ldc " + j);
+ lines.add(" ldc \"string" + i + "_" + j + "\"");
+ lines.add(" aastore");
+ }
+ lines.addAll(
+ ImmutableList.of(
+ "L2:",
+ " .line 3",
+ " aload 2",
+ " iload 1",
+ " aaload",
+ " checkcast java/lang/String",
+ " areturn"));
+ MethodSignature foo =
+ clazz.addVirtualMethod(
+ "foo", ImmutableList.of("I"), "Ljava/lang/String;", lines.toArray(new String[0]));
+ classes.put(clazz.name, foo);
+ }
+
+ JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+ clazz.addMainMethod(
+ ".limit stack 3",
+ ".limit locals 1",
+ " new Test0",
+ " dup",
+ " invokespecial Test0/<init>()V",
+ " ldc 42",
+ " invokevirtual Test0/foo(I)Ljava/lang/String;",
+ " getstatic java/lang/System/out Ljava/io/PrintStream;",
+ " swap",
+ " invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+ " return");
+
+ String expected = "string0_42";
+ assertEquals(expected, runOnJava(builder, clazz.name));
+
+ AndroidApp jasminApp = builder.build();
+ AndroidApp d8App = ToolHelper.runD8(jasminApp);
+ assertEquals(expected, runOnArt(d8App, clazz.name));
+
+ DexInspector inspector = new DexInspector(d8App);
+ for (Entry<String, MethodSignature> entry : classes.entrySet()) {
+ DebugInfoInspector info = new DebugInfoInspector(inspector, entry.getKey(), entry.getValue());
+ info.checkStartLine(1);
+ // If jumbo-string processing fails to keep debug info, some methods will have lost 'i' here.
+ info.checkLineHasExactLocals(1, "this", entry.getKey(), "i", "int");
+ info.checkLineHasExactLocals(
+ 2, "this", entry.getKey(), "i", "int", "strings", "java.lang.String[]");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
index f8ca2a8..dfc20f9 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
@@ -6,43 +6,45 @@
import static org.junit.Assert.assertEquals;
+import com.android.tools.r8.CompilationException;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.utils.FileUtils;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
public class MainDexListOutputTest extends TestBase {
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
@Test
public void testNoMainDex() throws Exception {
- Path mainDexList = temp.newFile().toPath();
- compileWithR8(ImmutableList.of(HelloWorldMain.class),
- options -> {
- options.printMainDexList = true;
- options.printMainDexListFile = mainDexList;
- });
- // Empty main dex list.
- assertEquals(0, FileUtils.readTextFile(mainDexList).size());
+ thrown.expect(CompilationException.class);
+ Path mainDexListOutput = temp.getRoot().toPath().resolve("main-dex-output.txt");
+ R8Command command =
+ ToolHelper.prepareR8CommandBuilder(readClasses(HelloWorldMain.class))
+ .setMainDexListOutputPath(mainDexListOutput)
+ .build();
+ ToolHelper.runR8(command);
}
@Test
public void testWithMainDex() throws Exception {
Path mainDexRules = writeTextToTempFile(keepMainProguardConfiguration(HelloWorldMain.class));
+ Path mainDexListOutput = temp.getRoot().toPath().resolve("main-dex-output.txt");
R8Command command =
ToolHelper.prepareR8CommandBuilder(readClasses(HelloWorldMain.class))
.addMainDexRules(mainDexRules)
+ .setMainDexListOutputPath(mainDexListOutput)
.build();
- Path mainDexList = temp.newFile().toPath();
- ToolHelper.runR8(command,
- options -> {
- options.printMainDexList = true;
- options.printMainDexListFile = mainDexList;
- });
+ ToolHelper.runR8(command);
// Main dex list with the single class.
assertEquals(
ImmutableList.of(HelloWorldMain.class.getTypeName().replace('.', '/') + ".class"),
- FileUtils.readTextFile(mainDexList));
+ FileUtils.readTextFile(mainDexListOutput));
}
}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 714792d..a87985e 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -4,11 +4,14 @@
package com.android.tools.r8.maindexlist;
import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.D8Command;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
@@ -63,6 +66,8 @@
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;
@@ -216,7 +221,9 @@
FileUtils.writeTextFile(mainDexList, list);
Set<DexType> types = MainDexList.parse(mainDexList, factory);
for (String entry : list) {
- assertTrue(types.contains(factory.createType("L" + entry.replace(".class", "") + ";")));
+ DexType type = factory.createType("L" + entry.replace(".class", "") + ";");
+ assertTrue(types.contains(type));
+ assertSame(type, MainDexList.parse(entry, factory));
}
}
@@ -229,34 +236,108 @@
MainDexList.parse(mainDexList, factory);
}
+ private Path runD8WithMainDexList(
+ CompilationMode mode, Path input, List<String> mainDexClasses, boolean useFile)
+ throws Exception {
+ Path testDir = temp.newFolder().toPath();
+ Path listFile = testDir.resolve("main-dex-list.txt");
+ if (mainDexClasses != null && useFile) {
+ FileUtils.writeTextFile(
+ listFile,
+ mainDexClasses
+ .stream()
+ .map(clazz -> clazz.replace('.', '/') + ".class")
+ .collect(Collectors.toList()));
+ }
+
+ D8Command.Builder builder = D8Command.builder()
+ .addProgramFiles(input)
+ .setMode(mode)
+ .setOutputPath(testDir);
+ if (mainDexClasses != null) {
+ if (useFile) {
+ builder.addMainDexListFiles(listFile);
+ } else {
+ builder.addMainDexClasses(mainDexClasses);
+ }
+ }
+ ToolHelper.runD8(builder.build());
+ return testDir;
+ }
+
+ private void runDeterministicTest(Path input, List<String> mainDexClasses, boolean allClasses)
+ throws Exception {
+ // Run test in debug and release mode for minimal vs. non-minimal main-dex.
+ for (CompilationMode mode : CompilationMode.values()) {
+
+ // Build with all different main dex lists.
+ List<Path> testDirs = new ArrayList<>();
+ if (allClasses) {
+ // If all classes are passed add a run without a main-dex list as well.
+ testDirs.add(runD8WithMainDexList(mode, input, null, true));
+ }
+ testDirs.add(runD8WithMainDexList(mode, input, mainDexClasses, true));
+ testDirs.add(runD8WithMainDexList(mode, input, mainDexClasses, false));
+ if (mainDexClasses != null) {
+ testDirs.add(runD8WithMainDexList(mode, input, Lists.reverse(mainDexClasses), true));
+ testDirs.add(runD8WithMainDexList(mode, input, Lists.reverse(mainDexClasses), false));
+ }
+
+ byte[] ref = null;
+ byte[] ref2 = null;
+ for (Path testDir : testDirs) {
+ Path primaryDexFile = testDir.resolve(FileUtils.DEFAULT_DEX_FILENAME);
+ Path secondaryDexFile = testDir.resolve("classes2.dex");
+ assertTrue(Files.exists(primaryDexFile));
+ boolean hasSecondaryDexFile = !allClasses && mode == CompilationMode.DEBUG;
+ assertEquals(hasSecondaryDexFile, Files.exists(testDir.resolve(secondaryDexFile)));
+ byte[] content = Files.readAllBytes(primaryDexFile);
+ if (ref == null) {
+ ref = content;
+ } else {
+ assertArrayEquals(ref, content);
+ }
+ if (hasSecondaryDexFile) {
+ content = Files.readAllBytes(primaryDexFile);
+ if (ref2 == null) {
+ ref2 = content;
+ } else {
+ assertArrayEquals(ref2, content);
+ }
+ }
+ }
+ }
+ }
+
@Test
- public void checkDeterminism() throws Exception {
+ public void deterministicTest() throws Exception {
// Synthesize a dex containing a few empty classes including some in the default package.
- // Everything can fit easaly in a single dex file.
- String[] classes = {
- "A",
- "B",
- "C",
- "D",
- "E",
- "F",
- "A1",
- "A2",
- "A3",
- "A4",
- "A5",
- "maindexlist/A",
- "maindexlist/B",
- "maindexlist/C",
- "maindexlist/D",
- "maindexlist/E",
- "maindexlist/F",
- "maindexlist/A1",
- "maindexlist/A2",
- "maindexlist/A3",
- "maindexlist/A4",
- "maindexlist/A5"
- };
+ // Everything can fit easily in a single dex file.
+ ImmutableList<String> classes = new ImmutableList.Builder<String>()
+ .add("A")
+ .add("B")
+ .add("C")
+ .add("D")
+ .add("E")
+ .add("F")
+ .add("A1")
+ .add("A2")
+ .add("A3")
+ .add("A4")
+ .add("A5")
+ .add("maindexlist.A")
+ .add("maindexlist.B")
+ .add("maindexlist.C")
+ .add("maindexlist.D")
+ .add("maindexlist.E")
+ .add("maindexlist.F")
+ .add("maindexlist.A1")
+ .add("maindexlist.A2")
+ .add("maindexlist.A3")
+ .add("maindexlist.A4")
+ .add("maindexlist.A5")
+ .build();
+
JasminBuilder jasminBuilder = new JasminBuilder();
for (String name : classes) {
jasminBuilder.addClass(name);
@@ -264,69 +345,28 @@
Path input = temp.newFolder().toPath().resolve("input.zip");
ToolHelper.runR8(jasminBuilder.build()).writeToZip(input, OutputMode.Indexed);
- // Prepare different main dex lists.
- ArrayList<Path> mainLists = new ArrayList<>();
- // Lets first without a main dex list.
- mainLists.add(null);
+ // Test with empty main dex list.
+ runDeterministicTest(input, null, true);
- // List with all classes.
- List<String> mainList = new ArrayList<>();
- for (int i = 0; i < classes.length; i++) {
- mainList.add(classes[i] + ".class");
- }
- addMainListFile(mainLists, mainList);
+ // Test with main-dex list with all classes.
+ runDeterministicTest(input, classes, true);
- // Full list in reverse order
- addMainListFile(mainLists, Lists.reverse(mainList));
+ // Test with main-dex list with first and second half of the classes.
+ List<List<String>> partitions = Lists.partition(classes, classes.size() / 2);
+ runDeterministicTest(input, partitions.get(0), false);
+ runDeterministicTest(input, partitions.get(1), false);
- // Partial list without first entries (those in default package).
- mainList.clear();
- for (int i = classes.length / 2; i < classes.length; i++) {
- mainList.add(classes[i] + ".class");
- }
- addMainListFile(mainLists, mainList);
-
- // Same in reverse order
- addMainListFile(mainLists, Lists.reverse(mainList));
-
- // Mixed partial list.
- mainList.clear();
- for (int i = 0; i < classes.length; i += 2) {
- mainList.add(classes[i] + ".class");
- }
- addMainListFile(mainLists, mainList);
-
- // Another different mixed partial list.
- mainList.clear();
- for (int i = 1; i < classes.length; i += 2) {
- mainList.add(classes[i] + ".class");
- }
- addMainListFile(mainLists, mainList);
-
- // Build with all main dex lists.
- Path tmp = temp.getRoot().toPath();
- for (int i = 0; i < mainLists.size(); i++) {
- Path out = tmp.resolve(String.valueOf(i));
- Files.createDirectories(out);
- D8Command.Builder builder = D8Command.builder()
- .addProgramFiles(input)
- .setOutputPath(out);
- if (mainLists.get(i) != null) {
- builder.setMainDexListFile(mainLists.get(i));
- }
- ToolHelper.runD8(builder.build());
- }
-
- // Check: no secondary dex and resulting dex is always the same.
- assertFalse(Files.exists(tmp.resolve(String.valueOf(0)).resolve("classes2.dex")));
- byte[] ref = Files.readAllBytes(
- tmp.resolve(String.valueOf(0)).resolve(FileUtils.DEFAULT_DEX_FILENAME));
- for (int i = 1; i < mainLists.size(); i++) {
- assertFalse(Files.exists(tmp.resolve(String.valueOf(i)).resolve("classes2.dex")));
- byte[] checked = Files.readAllBytes(
- tmp.resolve(String.valueOf(i)).resolve(FileUtils.DEFAULT_DEX_FILENAME));
- assertArrayEquals(ref, checked);
- }
+ // Test with main-dex list with every second of the classes.
+ runDeterministicTest(input,
+ IntStream.range(0, classes.size())
+ .filter(n -> n % 2 == 0)
+ .mapToObj(classes::get)
+ .collect(Collectors.toList()), false);
+ runDeterministicTest(input,
+ IntStream.range(0, classes.size())
+ .filter(n -> n % 2 == 1)
+ .mapToObj(classes::get)
+ .collect(Collectors.toList()), false);
}
@Test
@@ -359,13 +399,6 @@
}
}
- private void addMainListFile(ArrayList<Path> mainLists, List<String> content)
- throws IOException {
- Path listFile = temp.newFile().toPath();
- FileUtils.writeTextFile(listFile, content);
- mainLists.add(listFile);
- }
-
private static String typeToEntry(String type) {
return type.replace(".", "/") + FileUtils.CLASS_EXTENSION;
}
@@ -398,8 +431,16 @@
}
}
+ private enum MultiDexTestMode {
+ SINGLE_FILE,
+ MULTIPLE_FILES,
+ STRINGS,
+ FILES_AND_STRINGS
+ }
+
private void doVerifyMainDexContains(
- List<String> mainDex, Path app, boolean singleDexApp, boolean minimalMainDex)
+ List<String> mainDex, Path app, boolean singleDexApp, boolean minimalMainDex,
+ MultiDexTestMode testMode)
throws IOException, CompilationException, ExecutionException, ProguardRuleParserException {
AndroidApp originalApp = AndroidApp.fromProgramFiles(app);
DexInspector originalInspector = new DexInspector(originalApp);
@@ -408,18 +449,58 @@
originalInspector.clazz(clazz).isPresent());
}
Path outDir = temp.newFolder().toPath();
- Path mainDexList = temp.newFile().toPath();
- FileUtils.writeTextFile(mainDexList, ListUtils.map(mainDex, MainDexListTests::typeToEntry));
- R8Command command =
+ R8Command.Builder builder =
R8Command.builder()
.addProgramFiles(app)
- .setMainDexListFile(mainDexList)
- .setMinimalMainDex(minimalMainDex && mainDex.size() > 0)
+ .setMode(minimalMainDex && mainDex.size() > 0
+ ? CompilationMode.DEBUG : CompilationMode.RELEASE)
.setOutputPath(outDir)
.setTreeShaking(false)
- .setMinification(false)
- .build();
- ToolHelper.runR8(command);
+ .setMinification(false);
+
+ switch (testMode) {
+ case SINGLE_FILE:
+ Path mainDexList = temp.newFile().toPath();
+ FileUtils.writeTextFile(mainDexList, ListUtils.map(mainDex, MainDexListTests::typeToEntry));
+ builder.addMainDexListFiles(mainDexList);
+ break;
+ case MULTIPLE_FILES: {
+ // Partion the main dex list into several files.
+ List<List<String>> partitions = Lists.partition(mainDex, Math.max(mainDex.size() / 3, 1));
+ List<Path> mainDexListFiles = new ArrayList<>();
+ for (List<String> partition : partitions) {
+ Path partialMainDexList = temp.newFile().toPath();
+ FileUtils.writeTextFile(partialMainDexList,
+ ListUtils.map(partition, MainDexListTests::typeToEntry));
+ mainDexListFiles.add(partialMainDexList);
+ }
+ builder.addMainDexListFiles(mainDexListFiles);
+ break;
+ }
+ case STRINGS:
+ builder.addMainDexClasses(mainDex);
+ break;
+ case FILES_AND_STRINGS: {
+ // Partion the main dex list add some parts through files and the other parts using strings.
+ List<List<String>> partitions = Lists.partition(mainDex, Math.max(mainDex.size() / 3, 1));
+ List<Path> mainDexListFiles = new ArrayList<>();
+ for (int i = 0; i < partitions.size(); i++) {
+ List<String> partition = partitions.get(i);
+ if (i % 2 == 0) {
+ Path partialMainDexList = temp.newFile().toPath();
+ FileUtils.writeTextFile(partialMainDexList,
+ ListUtils.map(partition, MainDexListTests::typeToEntry));
+ mainDexListFiles.add(partialMainDexList);
+ } else {
+ builder.addMainDexClasses(mainDex);
+ }
+ }
+ builder.addMainDexListFiles(mainDexListFiles);
+ break;
+ }
+ }
+
+ ToolHelper.runR8(builder.build());
if (!singleDexApp && !minimalMainDex) {
assertTrue("Output run only produced one dex file.",
1 < Files.list(outDir).filter(FileUtils::isDexFile).count());
@@ -431,15 +512,17 @@
failedToFindClassInExpectedFile(outDir, clazz);
}
}
- if (minimalMainDex) {
+ if (minimalMainDex && mainDex.size() > 0) {
inspector.forAllClasses(clazz -> assertMainDexClass(clazz, mainDex));
}
}
private void verifyMainDexContains(List<String> mainDex, Path app, boolean singleDexApp)
throws Throwable {
- doVerifyMainDexContains(mainDex, app, singleDexApp, false);
- doVerifyMainDexContains(mainDex, app, singleDexApp, true);
+ for (MultiDexTestMode multiDexTestMode : MultiDexTestMode.values()) {
+ doVerifyMainDexContains(mainDex, app, singleDexApp, false, multiDexTestMode);
+ doVerifyMainDexContains(mainDex, app, singleDexApp, true, multiDexTestMode);
+ }
}
public static AndroidApp generateApplication(List<String> classes, int minApi, int methodCount)
@@ -498,7 +581,7 @@
DexApplication application = builder.build();
AppInfoWithSubtyping appInfo = new AppInfoWithSubtyping(application);
ApplicationWriter writer = new ApplicationWriter(
- application, appInfo, options, null, NamingLens.getIdentityLens(), null);
+ application, appInfo, options, null, null, NamingLens.getIdentityLens(), null);
ExecutorService executor = ThreadUtils.getExecutorService(options);
try {
return writer.write(null, executor);
diff --git a/src/test/java/com/android/tools/r8/regress/b63935662/Regress63935662.java b/src/test/java/com/android/tools/r8/regress/b63935662/Regress63935662.java
index 70d4efc..8d0e667 100644
--- a/src/test/java/com/android/tools/r8/regress/b63935662/Regress63935662.java
+++ b/src/test/java/com/android/tools/r8/regress/b63935662/Regress63935662.java
@@ -7,9 +7,9 @@
import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.OffOrAuto;
import java.nio.file.Path;
import org.junit.Assert;
import org.junit.Test;
@@ -17,17 +17,15 @@
public class Regress63935662 extends TestBase {
void run(AndroidApp app, Class mainClass) throws Exception {
- if (!ToolHelper.getDexVm().isNewerThan(DexVm.ART_6_0_1)) {
- return;
- }
- Path proguardConfig = writeTextToTempFile(keepMainProguardConfiguration(mainClass, true, false));
+ Path proguardConfig =
+ writeTextToTempFile(keepMainProguardConfiguration(mainClass, true, false));
R8Command command =
ToolHelper.prepareR8CommandBuilder(app)
.addProguardConfigurationFiles(proguardConfig)
- .setMinApiLevel(Constants.ANDROID_N_API)
+ .setMinApiLevel(Constants.ANDROID_L_API)
.build();
String resultFromJava = runOnJava(mainClass);
- app = ToolHelper.runR8(command);
+ app = ToolHelper.runR8(command, options -> options.interfaceMethodDesugaring = OffOrAuto.Auto);
String resultFromArt = runOnArt(app, mainClass);
Assert.assertEquals(resultFromJava, resultFromArt);
}
diff --git a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
index c9b476a..89f0f3b 100644
--- a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
@@ -74,6 +74,8 @@
.addLibraryFiles(Paths.get(ANDROID_JAR))
.build();
ToolHelper.runR8(command, options -> {
+ // Disable inlining to make this test not depend on inlining decisions.
+ options.inlineAccessors = false;
options.printUsage = true;
options.printUsageFile = out.resolve(test + PRINT_USAGE_FILE_SUFFIX);
});
@@ -152,12 +154,11 @@
private static void inspectShaking9(PrintUsageInspector inspector) {
Optional<ClassSubject> superClass = inspector.clazz("shaking9.Superclass");
- assertTrue(superClass.isPresent());
- assertTrue(superClass.get().method("void", "<init>", Collections.emptyList()));
+ assertFalse(superClass.isPresent());
Optional<ClassSubject> subClass = inspector.clazz("shaking9.Subclass");
assertTrue(subClass.isPresent());
assertTrue(subClass.get().method("void", "aMethod", Collections.emptyList()));
- assertTrue(subClass.get().method("void", "<init>", Collections.emptyList()));
+ assertFalse(subClass.get().method("void", "<init>", Collections.emptyList()));
}
private static void inspectShaking12(PrintUsageInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
index 8f46572..795c020 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
@@ -92,7 +92,11 @@
.addProgramFiles(originalDex)
.setOutputPath(out)
.addProguardConfigurationFiles(keepRules, printMapping)
- .build());
+ .build(),
+ options -> {
+ // Turn off inlining, as we want the mapping that is printed to be stable.
+ options.inlineAccessors = false;
+ });
Path outputmapping = out.resolve("mapping.txt");
String actualMapping;
actualMapping = new String(Files.readAllBytes(outputmapping), StandardCharsets.UTF_8);
diff --git a/src/test/java/com/android/tools/r8/utils/D8CommandTest.java b/src/test/java/com/android/tools/r8/utils/D8CommandTest.java
index 443e257..1e8555b 100644
--- a/src/test/java/com/android/tools/r8/utils/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/utils/D8CommandTest.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.google.common.collect.ImmutableList;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -49,7 +50,7 @@
private void verifyEmptyCommand(D8Command command) throws IOException {
assertEquals(0, ToolHelper.getApp(command).getDexProgramResources().size());
assertEquals(0, ToolHelper.getApp(command).getClassProgramResources().size());
- assertFalse(ToolHelper.getApp(command).hasMainDexList());
+ assertFalse(ToolHelper.getApp(command).hasMainDexListResources());
assertFalse(ToolHelper.getApp(command).hasProguardMap());
assertFalse(ToolHelper.getApp(command).hasProguardSeeds());
assertFalse(ToolHelper.getApp(command).hasPackageDistribution());
@@ -157,33 +158,38 @@
@Test
public void mainDexList() throws Throwable {
- Path mailDexList = temp.getRoot().toPath().resolve("main-dex-list.txt");
- D8Command command = parse("--main-dex-list", mailDexList.toString());
- assertTrue(ToolHelper.getApp(command).hasMainDexList());
+ Path mainDexList1 = temp.newFile("main-dex-list-1.txt").toPath();
+ Path mainDexList2 = temp.newFile("main-dex-list-2.txt").toPath();
+
+ D8Command command = parse("--main-dex-list", mainDexList1.toString());
+ assertTrue(ToolHelper.getApp(command).hasMainDexListResources());
+
+ command = parse(
+ "--main-dex-list", mainDexList1.toString(), "--main-dex-list", mainDexList2.toString());
+ assertTrue(ToolHelper.getApp(command).hasMainDexListResources());
}
@Test
- public void multipleMainDexList() throws Throwable {
- thrown.expect(CompilationException.class);
- Path mailDexList1 = temp.getRoot().toPath().resolve("main-dex-list-1.txt");
- Path mailDexList2 = temp.getRoot().toPath().resolve("main-dex-list-2.txt");
- parse("--main-dex-list", mailDexList1.toString(), "--main-dex-list", mailDexList2.toString());
+ public void nonExistingMainDexList() throws Throwable {
+ thrown.expect(FileNotFoundException.class);
+ Path mainDexList = temp.getRoot().toPath().resolve("main-dex-list.txt");
+ parse("--main-dex-list", mainDexList.toString());
}
@Test
public void mainDexListWithFilePerClass() throws Throwable {
thrown.expect(CompilationException.class);
- Path mailDexList = temp.getRoot().toPath().resolve("main-dex-list.txt");
- D8Command command = parse("--main-dex-list", mailDexList.toString(), "--file-per-class");
- assertTrue(ToolHelper.getApp(command).hasMainDexList());
+ Path mainDexList = temp.newFile("main-dex-list.txt").toPath();
+ D8Command command = parse("--main-dex-list", mainDexList.toString(), "--file-per-class");
+ assertTrue(ToolHelper.getApp(command).hasMainDexListResources());
}
@Test
public void mainDexListWithIntermediate() throws Throwable {
thrown.expect(CompilationException.class);
- Path mailDexList = temp.getRoot().toPath().resolve("main-dex-list.txt");
- D8Command command = parse("--main-dex-list", mailDexList.toString(), "--intermediate");
- assertTrue(ToolHelper.getApp(command).hasMainDexList());
+ Path mainDexList = temp.newFile("main-dex-list.txt").toPath();
+ D8Command command = parse("--main-dex-list", mainDexList.toString(), "--intermediate");
+ assertTrue(ToolHelper.getApp(command).hasMainDexListResources());
}
@Test
diff --git a/src/test/java/com/android/tools/r8/utils/R8CommandTest.java b/src/test/java/com/android/tools/r8/utils/R8CommandTest.java
index a1e57ac..fdeb2dc 100644
--- a/src/test/java/com/android/tools/r8/utils/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/utils/R8CommandTest.java
@@ -16,8 +16,10 @@
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.shaking.ProguardRuleParserException;
import com.google.common.collect.ImmutableList;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
@@ -49,7 +51,7 @@
private void verifyEmptyCommand(R8Command command) throws IOException {
assertEquals(0, ToolHelper.getApp(command).getDexProgramResources().size());
assertEquals(0, ToolHelper.getApp(command).getClassProgramResources().size());
- assertFalse(ToolHelper.getApp(command).hasMainDexList());
+ assertFalse(ToolHelper.getApp(command).hasMainDexListResources());
assertFalse(ToolHelper.getApp(command).hasProguardMap());
assertFalse(ToolHelper.getApp(command).hasProguardSeeds());
assertFalse(ToolHelper.getApp(command).hasPackageDistribution());
@@ -90,20 +92,50 @@
@Test
public void mainDexRules() throws Throwable {
- Path mailDexRules = temp.newFile("main-dex.rules").toPath();
- parse("--main-dex-rules", mailDexRules.toString());
+ Path mainDexRules1 = temp.newFile("main-dex-1.rules").toPath();
+ Path mainDexRules2 = temp.newFile("main-dex-2.rules").toPath();
+ parse("--main-dex-rules", mainDexRules1.toString());
+ parse("--main-dex-rules", mainDexRules1.toString(), "--main-dex-rules", mainDexRules2.toString());
}
@Test
- public void minimalMainDex() throws Throwable {
+ public void nonExistingMainDexRules() throws Throwable {
+ thrown.expect(NoSuchFileException.class);
+ Path mainDexRules = temp.getRoot().toPath().resolve("main-dex.rules");
+ parse("--main-dex-rules", mainDexRules.toString());
+ }
+
+ @Test
+ public void mainDexList() throws Throwable {
+ Path mainDexList1 = temp.newFile("main-dex-list-1.txt").toPath();
+ Path mainDexList2 = temp.newFile("main-dex-list-2.txt").toPath();
+ parse("--main-dex-list", mainDexList1.toString());
+ parse("--main-dex-list", mainDexList1.toString(), "--main-dex-list", mainDexList2.toString());
+ }
+
+ @Test
+ public void nonExistingMainDexList() throws Throwable {
+ thrown.expect(FileNotFoundException.class);
+ Path mainDexList = temp.getRoot().toPath().resolve("main-dex-list.txt");
+ parse("--main-dex-list", mainDexList.toString());
+ }
+
+ @Test
+ public void mainDexListOutput() throws Throwable {
+ Path mainDexRules = temp.newFile("main-dex.rules").toPath();
+ Path mainDexList = temp.newFile("main-dex-list.txt").toPath();
+ Path mainDexListOutput = temp.newFile("main-dex-out.txt").toPath();
+ parse("--main-dex-rules", mainDexRules.toString(),
+ "--main-dex-list-output", mainDexListOutput.toString());
+ parse("--main-dex-list", mainDexList.toString(),
+ "--main-dex-list-output", mainDexListOutput.toString());
+ }
+
+ @Test
+ public void mainDexListOutputWithoutAnyMainDexSpecification() throws Throwable {
thrown.expect(CompilationException.class);
- parse("--minimal-main-dex");
- }
-
- @Test
- public void mainDexRulesWithMinimalMainDex() throws Throwable {
- Path mailDexRules = temp.newFile("main-dex.rules").toPath();
- parse("--main-dex-rules", mailDexRules.toString(), "--minimal-main-dex");
+ Path mainDexListOutput = temp.newFile("main-dex-out.txt").toPath();
+ parse("--main-dex-list-output", mainDexListOutput.toString());
}
@Test
diff --git a/third_party/goyt.tar.gz.sha1 b/third_party/goyt.tar.gz.sha1
index 873f829..0630836 100644
--- a/third_party/goyt.tar.gz.sha1
+++ b/third_party/goyt.tar.gz.sha1
@@ -1 +1 @@
-9ce39f3b3b0db4661946863cbb8fb073df174410
\ No newline at end of file
+9237df6f97a6ef87cab77de8e53abf457793df9a
\ No newline at end of file
diff --git a/tools/test_framework.py b/tools/test_framework.py
index b328375..1ade73d 100755
--- a/tools/test_framework.py
+++ b/tools/test_framework.py
@@ -32,7 +32,7 @@
'dx.jar')
D8_JAR = os.path.join(utils.REPO_ROOT, 'build', 'libs', 'd8.jar')
GOYT_EXE = os.path.join('third_party', 'goyt',
- 'goyt_160525751')
+ 'goyt_164843480')
FRAMEWORK_JAR = os.path.join('third_party', 'framework',
'framework_160115954.jar')
MIN_SDK_VERSION = '24'
@@ -49,9 +49,10 @@
parser.add_argument('--name',
required = True,
help = 'Results will be printed using the specified benchmark name (e.g.'
- ' <NAME>-<segment>(CodeSize): <bytes>)')
+ ' <NAME>-<segment>(CodeSize): <bytes>), the full size is reported'
+ ' with <NAME>-Total(CodeSize)')
parser.add_argument('--print-memoryuse',
- help = 'Prints the line \'<NAME>(MemoryUse):' +
+ help = 'Prints the line \'<NAME>-Total(MemoryUse):' +
' <mem>\' at the end where <mem> is the peak' +
' peak resident set size (VmHWM) in bytes.',
default = False,
@@ -99,7 +100,7 @@
dt = time.time() - t0
if args.print_memoryuse:
- print('{}(MemoryUse): {}'
+ print('{}-Total(MemoryUse): {}'
.format(args.name, utils.grep_memoryuse(track_memory_file)))
dex_files = [f for f in glob(os.path.join(temp_dir, '*.dex'))]