Merge commit '514eacf7544322d7dffc141fd15a934f9784ca4b' into 1.7.6-dev
diff --git a/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java b/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java
index 5651dde..90544cc 100644
--- a/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java
+++ b/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java
@@ -4,8 +4,6 @@
package com.android.tools.r8;
-import java.nio.file.Path;
-
// This class is used by the Android Studio Gradle plugin and is thus part of the R8 API.
@Keep
public class CompatProguardCommandBuilder extends R8Command.Builder {
@@ -34,8 +32,4 @@
setDisableVerticalClassMerging(disableVerticalClassMerging);
setIgnoreDexInArchive(true);
}
-
- public void setProguardCompatibilityRulesOutput(Path path) {
- proguardCompatibilityRulesOutput = path;
- }
}
diff --git a/src/main/java/com/android/tools/r8/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
index 2c63a69..3bd76a6 100644
--- a/src/main/java/com/android/tools/r8/GenerateLintFiles.java
+++ b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
@@ -47,6 +47,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -81,6 +82,8 @@
factory.createProto(streamType),
factory.createString("parallelStream"));
parallelMethods.add(parallelMethod);
+ DexType baseStreamType =
+ factory.createType(factory.createString("Ljava/util/stream/BaseStream;"));
for (String typePrefix : new String[] {"Base", "Double", "Int", "Long"}) {
streamType =
factory.createType(factory.createString("Ljava/util/stream/" + typePrefix + "Stream;"));
@@ -88,6 +91,11 @@
factory.createMethod(
streamType, factory.createProto(streamType), factory.createString("parallel"));
parallelMethods.add(parallelMethod);
+ // Also filter out the generated bridges for the covariant return type.
+ parallelMethod =
+ factory.createMethod(
+ streamType, factory.createProto(baseStreamType), factory.createString("parallel"));
+ parallelMethods.add(parallelMethod);
}
}
@@ -168,7 +176,19 @@
false));
}
- private Map<DexClass, List<DexEncodedMethod>> collectSupportedMethods(
+ public static class SupportedMethods {
+ public final Set<DexClass> classesWithAllMethodsSupported;
+ public final Map<DexClass, List<DexEncodedMethod>> supportedMethods;
+
+ public SupportedMethods(
+ Set<DexClass> classesWithAllMethodsSupported,
+ Map<DexClass, List<DexEncodedMethod>> supportedMethods) {
+ this.classesWithAllMethodsSupported = classesWithAllMethodsSupported;
+ this.supportedMethods = supportedMethods;
+ }
+ }
+
+ private SupportedMethods collectSupportedMethods(
AndroidApiLevel compilationApiLevel, Predicate<DexEncodedMethod> supported)
throws IOException, ExecutionException {
@@ -178,18 +198,25 @@
DirectMappedDexApplication dexApplication =
new ApplicationReader(library, options, new Timing()).read().toDirect();
- // collect all the methods that the library desugar configuration adds support for.
+ // Collect all the methods that the library desugar configuration adds support for.
+ Set<DexClass> classesWithAllMethodsSupported = Sets.newIdentityHashSet();
Map<DexClass, List<DexEncodedMethod>> supportedMethods = new LinkedHashMap<>();
for (DexLibraryClass clazz : dexApplication.libraryClasses()) {
String className = clazz.toSourceString();
// All the methods with the rewritten prefix are supported.
for (String prefix : desugaredLibraryConfiguration.getRewritePrefix().keySet()) {
if (clazz.accessFlags.isPublic() && className.startsWith(prefix)) {
+ boolean allMethodsAddad = true;
for (DexEncodedMethod method : clazz.methods()) {
if (supported.test(method)) {
supportedMethods.computeIfAbsent(clazz, k -> new ArrayList<>()).add(method);
+ } else {
+ allMethodsAddad = false;
}
}
+ if (allMethodsAddad) {
+ classesWithAllMethodsSupported.add(clazz);
+ }
}
}
@@ -209,9 +236,14 @@
}
}
}
- // All emulated interfaces methods are supported.
+
+ // All emulated interfaces static and default methods are supported.
if (desugaredLibraryConfiguration.getEmulateLibraryInterface().containsKey(clazz.type)) {
+ assert clazz.isInterface();
for (DexEncodedMethod method : clazz.methods()) {
+ if (!method.isDefaultMethod() && !method.isStatic()) {
+ continue;
+ }
if (supported.test(method)) {
supportedMethods.computeIfAbsent(clazz, k -> new ArrayList<>()).add(method);
}
@@ -219,7 +251,7 @@
}
}
- return supportedMethods;
+ return new SupportedMethods(classesWithAllMethodsSupported, supportedMethods);
}
private String lintBaseFileName(
@@ -243,22 +275,26 @@
private void writeLintFiles(
AndroidApiLevel compilationApiLevel,
AndroidApiLevel minApiLevel,
- Map<DexClass, List<DexEncodedMethod>> supportedMethods)
+ SupportedMethods supportedMethods)
throws Exception {
// Build a plain text file with the desugared APIs.
List<String> desugaredApisSignatures = new ArrayList<>();
DexApplication.Builder builder = DexApplication.builder(options, new Timing());
- supportedMethods.forEach(
+ supportedMethods.supportedMethods.forEach(
(clazz, methods) -> {
String classBinaryName =
DescriptorUtils.getClassBinaryNameFromDescriptor(clazz.type.descriptor.toString());
- for (DexEncodedMethod method : methods) {
- desugaredApisSignatures.add(
- classBinaryName
- + '/'
- + method.method.name
- + method.method.proto.toDescriptorString());
+ if (!supportedMethods.classesWithAllMethodsSupported.contains(clazz)) {
+ for (DexEncodedMethod method : methods) {
+ desugaredApisSignatures.add(
+ classBinaryName
+ + '#'
+ + method.method.name
+ + method.method.proto.toDescriptorString());
+ }
+ } else {
+ desugaredApisSignatures.add(classBinaryName);
}
addMethodsToHeaderJar(builder, clazz, methods);
@@ -266,6 +302,7 @@
DexApplication app = builder.build();
// Write a plain text file with the desugared APIs.
+ desugaredApisSignatures.sort(Comparator.naturalOrder());
FileUtils.writeTextFile(
lintFile(compilationApiLevel, minApiLevel, ".txt"), desugaredApisSignatures);
@@ -293,16 +330,20 @@
Predicate<AndroidApiLevel> generateForThisMinApiLevel,
BiPredicate<AndroidApiLevel, DexEncodedMethod> supportedForMinApiLevel)
throws Exception {
- for (AndroidApiLevel value : AndroidApiLevel.values()) {
- if (!generateForThisMinApiLevel.test(value)) {
+ System.out.print(" - generating for min API:");
+ for (AndroidApiLevel minApiLevel : AndroidApiLevel.values()) {
+ if (!generateForThisMinApiLevel.test(minApiLevel)) {
continue;
}
- Map<DexClass, List<DexEncodedMethod>> supportedMethods =
+ System.out.print(" " + minApiLevel);
+
+ SupportedMethods supportedMethods =
collectSupportedMethods(
- compilationApiLevel, (method -> supportedForMinApiLevel.test(value, method)));
- writeLintFiles(compilationApiLevel, value, supportedMethods);
+ compilationApiLevel, (method -> supportedForMinApiLevel.test(minApiLevel, method)));
+ writeLintFiles(compilationApiLevel, minApiLevel, supportedMethods);
}
+ System.out.println();
}
private void run() throws Exception {
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index d1a8e6a..f3b7e05 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringDiagnostic;
+import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -150,7 +151,8 @@
@Keep
public static class Builder extends BaseCompilerCommand.Builder<L8Command, Builder> {
- private final List<Pair<List<String>, Origin>> proguardConfigs = new ArrayList<>();
+ private final List<Pair<List<String>, Origin>> proguardConfigStrings = new ArrayList<>();
+ private final List<Path> proguardConfigFiles = new ArrayList<>();
private Builder() {
this(new DefaultL8DiagnosticsHandler());
@@ -161,8 +163,15 @@
}
public boolean isShrinking() {
+ // TODO(b/139273544): Re-enable shrinking once fixed.
+ getReporter()
+ .warning(
+ new StringDiagnostic(
+ "Shrinking of desugared library has been temporarily disabled due to known bugs"
+ + " being fixed."));
+ return false;
// Answers true if keep rules, even empty, are provided.
- return !proguardConfigs.isEmpty();
+ // return !proguardConfigStrings.isEmpty() || !proguardConfigFiles.isEmpty();
}
@Override
@@ -175,16 +184,28 @@
return CompilationMode.DEBUG;
}
+ /** Add proguard configuration-file resources. */
+ public Builder addProguardConfigurationFiles(Path... paths) {
+ Collections.addAll(proguardConfigFiles, paths);
+ return self();
+ }
+
+ /** Add proguard configuration-file resources. */
+ public Builder addProguardConfigurationFiles(List<Path> paths) {
+ proguardConfigFiles.addAll(paths);
+ return self();
+ }
+
/** Add proguard configuration. */
public Builder addProguardConfiguration(List<String> lines, Origin origin) {
- proguardConfigs.add(new Pair<>(lines, origin));
- return this;
+ proguardConfigStrings.add(new Pair<>(lines, origin));
+ return self();
}
@Override
void validate() {
Reporter reporter = getReporter();
- if (!hasDesugaredLibraryConfiguration()){
+ if (!hasDesugaredLibraryConfiguration()) {
reporter.error("L8 requires a desugared library configuration");
}
if (getProgramConsumer() instanceof ClassFileConsumer) {
@@ -238,9 +259,10 @@
inputs.getLibraryResourceProviders()) {
r8Builder.addLibraryResourceProvider(libraryResourceProvider);
}
- for (Pair<List<String>, Origin> proguardConfig : proguardConfigs) {
+ for (Pair<List<String>, Origin> proguardConfig : proguardConfigStrings) {
r8Builder.addProguardConfiguration(proguardConfig.getFirst(), proguardConfig.getSecond());
}
+ r8Builder.addProguardConfigurationFiles(proguardConfigFiles);
r8Command = r8Builder.makeCommand();
} else {
D8Command.Builder d8Builder =
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 4cd2f7b..91b6ab7 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -62,7 +62,6 @@
import com.android.tools.r8.shaking.MainDexClasses;
import com.android.tools.r8.shaking.MainDexListBuilder;
import com.android.tools.r8.shaking.ProguardClassFilter;
-import com.android.tools.r8.shaking.ProguardConfiguration;
import com.android.tools.r8.shaking.ProguardConfigurationRule;
import com.android.tools.r8.shaking.ProguardConfigurationUtils;
import com.android.tools.r8.shaking.RootSetBuilder;
@@ -76,7 +75,6 @@
import com.android.tools.r8.utils.CfgPrinter;
import com.android.tools.r8.utils.CollectionUtils;
import com.android.tools.r8.utils.ExceptionUtils;
-import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOptions.AssertionProcessing;
import com.android.tools.r8.utils.LineNumberOptimizer;
@@ -88,15 +86,12 @@
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.io.ByteStreams;
-import com.google.common.io.Closer;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
-import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
@@ -297,9 +292,6 @@
// kotlin metadata annotation is removed.
computeKotlinInfoForProgramClasses(application, appView);
- ProguardConfiguration.Builder compatibility =
- ProguardConfiguration.builder(application.dexItemFactory, options.reporter);
-
// Add synthesized -assumenosideeffects from min api if relevant.
if (options.isGeneratingDex()) {
if (!ProguardConfigurationUtils.hasExplicitAssumeValuesOrAssumeNoSideEffectsRuleForMinSdk(
@@ -318,7 +310,7 @@
options.getProguardConfiguration().getRules(), synthesizedProguardRules))
.run(executorService));
- Enqueuer enqueuer = EnqueuerFactory.createForInitialTreeShaking(appView, compatibility);
+ Enqueuer enqueuer = EnqueuerFactory.createForInitialTreeShaking(appView);
if (appView.options().enableInitializedClassesInInstanceMethodsAnalysis) {
enqueuer.registerAnalysis(new InitializedClassesInInstanceMethodsAnalysis(appView));
@@ -376,26 +368,9 @@
classesToRetainInnerClassAttributeFor =
AnnotationRemover.computeClassesToRetainInnerClassAttributeFor(appView.withLiveness());
new AnnotationRemover(appView.withLiveness(), classesToRetainInnerClassAttributeFor)
- .ensureValid(compatibility)
+ .ensureValid()
.run();
- // TODO(69445518): This is still work in progress, and this file writing is currently used
- // for testing.
- if (options.forceProguardCompatibility
- && options.proguardCompatibilityRulesOutput != null) {
- try (Closer closer = Closer.create()) {
- OutputStream outputStream =
- FileUtils.openPath(
- closer,
- options.proguardCompatibilityRulesOutput,
- StandardOpenOption.CREATE,
- StandardOpenOption.TRUNCATE_EXISTING,
- StandardOpenOption.WRITE);
- try (PrintStream ps = new PrintStream(outputStream)) {
- ps.println(compatibility.buildRaw().toString());
- }
- }
- }
} finally {
timing.end();
}
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 5c90ae8..ae68adb 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -100,9 +100,6 @@
private BiFunction<String, Long, Boolean> dexClassChecksumFilter = (name, checksum) -> true;
private final List<FeatureSplit> featureSplits = new ArrayList<>();
- // Internal compatibility mode for use from CompatProguard tool.
- Path proguardCompatibilityRulesOutput = null;
-
private boolean allowPartiallyImplementedProguardOptions = false;
private boolean allowTestProguardOptions = false;
@@ -541,7 +538,6 @@
proguardUsageConsumer,
proguardSeedsConsumer,
proguardConfigurationConsumer,
- proguardCompatibilityRulesOutput,
keptGraphConsumer,
mainDexKeptGraphConsumer,
syntheticProguardRulesConsumer,
@@ -626,7 +622,6 @@
private final StringConsumer proguardUsageConsumer;
private final StringConsumer proguardSeedsConsumer;
private final StringConsumer proguardConfigurationConsumer;
- private final Path proguardCompatibilityRulesOutput;
private final GraphConsumer keptGraphConsumer;
private final GraphConsumer mainDexKeptGraphConsumer;
private final Consumer<List<ProguardConfigurationRule>> syntheticProguardRulesConsumer;
@@ -699,7 +694,6 @@
StringConsumer proguardUsageConsumer,
StringConsumer proguardSeedsConsumer,
StringConsumer proguardConfigurationConsumer,
- Path proguardCompatibilityRulesOutput,
GraphConsumer keptGraphConsumer,
GraphConsumer mainDexKeptGraphConsumer,
Consumer<List<ProguardConfigurationRule>> syntheticProguardRulesConsumer,
@@ -732,7 +726,6 @@
this.proguardUsageConsumer = proguardUsageConsumer;
this.proguardSeedsConsumer = proguardSeedsConsumer;
this.proguardConfigurationConsumer = proguardConfigurationConsumer;
- this.proguardCompatibilityRulesOutput = proguardCompatibilityRulesOutput;
this.keptGraphConsumer = keptGraphConsumer;
this.mainDexKeptGraphConsumer = mainDexKeptGraphConsumer;
this.syntheticProguardRulesConsumer = syntheticProguardRulesConsumer;
@@ -753,7 +746,6 @@
proguardUsageConsumer = null;
proguardSeedsConsumer = null;
proguardConfigurationConsumer = null;
- proguardCompatibilityRulesOutput = null;
keptGraphConsumer = null;
mainDexKeptGraphConsumer = null;
syntheticProguardRulesConsumer = null;
@@ -853,7 +845,6 @@
internal.keptGraphConsumer = keptGraphConsumer;
internal.mainDexKeptGraphConsumer = mainDexKeptGraphConsumer;
- internal.proguardCompatibilityRulesOutput = proguardCompatibilityRulesOutput;
internal.dataResourceConsumer = internal.programConsumer.getDataResourceConsumer();
internal.featureSplitConfiguration = featureSplitConfiguration;
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 bc4f6f0..90de8ca 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -273,7 +273,7 @@
public boolean isDefaultMethod() {
// Assumes holder is an interface
- return !isAbstract() && !isPrivateMethod() && !isInstanceInitializer();
+ return !isStatic() && !isAbstract() && !isPrivateMethod() && !isInstanceInitializer();
}
/**
@@ -600,11 +600,6 @@
return this;
}
- public IRCode buildEmptyThrowingIRCode(AppView<?> appView, Origin origin) {
- DexCode emptyThrowingDexCode = buildEmptyThrowingDexCode();
- return emptyThrowingDexCode.buildIR(this, appView, origin);
- }
-
/**
* Generates a {@link DexCode} object for the given instructions.
*/
@@ -887,7 +882,7 @@
List<Pair<DexType, DexMethod>> extraDispatchCases,
AppView<?> appView) {
// TODO(134732760): Deal with overrides for correct dispatch to implementations of Interfaces
- assert isDefaultMethod();
+ assert isDefaultMethod() || isStatic();
DexEncodedMethod.Builder builder = DexEncodedMethod.builder(this);
builder.setMethod(newMethod);
builder.accessFlags.setSynthetic();
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/Nullability.java b/src/main/java/com/android/tools/r8/ir/analysis/type/Nullability.java
index 11c1c8c..26b9030 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/Nullability.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/Nullability.java
@@ -58,6 +58,19 @@
return MAYBE_NULL;
}
+ public Nullability meet(Nullability other) {
+ if (this == MAYBE_NULL) {
+ return other;
+ }
+ if (other == MAYBE_NULL) {
+ return this;
+ }
+ if (this == other) {
+ return this;
+ }
+ return BOTTOM;
+ }
+
public boolean lessThanOrEqual(Nullability other) {
return join(other) == other;
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeLatticeElement.java
index 8e42de0..f6af1f7 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeLatticeElement.java
@@ -75,8 +75,8 @@
public abstract ReferenceTypeLatticeElement getOrCreateVariant(Nullability nullability);
- public TypeLatticeElement asNotNull() {
- return getOrCreateVariant(Nullability.definitelyNotNull());
+ public TypeLatticeElement asMeetWithNotNull() {
+ return getOrCreateVariant(nullability.meet(Nullability.definitelyNotNull()));
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Assume.java b/src/main/java/com/android/tools/r8/ir/code/Assume.java
index c4dfbc4..9ba29fd 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Assume.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Assume.java
@@ -153,6 +153,10 @@
return self;
}
+ public boolean mayAffectStaticType() {
+ return isAssumeNonNull();
+ }
+
@Override
public boolean couldIntroduceAnAlias(AppView<?> appView, Value root) {
assert root != null && root.getTypeLattice().isReference();
@@ -237,7 +241,7 @@
}
if (assumption.isAssumeNonNull()) {
assert src().getTypeLattice().isReference();
- return src().getTypeLattice().asReferenceTypeLatticeElement().asNotNull();
+ return src().getTypeLattice().asReferenceTypeLatticeElement().asMeetWithNotNull();
}
throw new Unimplemented();
}
@@ -276,7 +280,7 @@
assert isAssumeNonNull() : this;
assert inType.isReference() : inType;
assert inType.isNullType()
- || outType.equals(inType.asReferenceTypeLatticeElement().asNotNull())
+ || outType.equals(inType.asReferenceTypeLatticeElement().asMeetWithNotNull())
: "At " + this + System.lineSeparator() + outType + " != " + inType;
}
return true;
@@ -284,16 +288,20 @@
@Override
public String toString() {
- String originString = "(origin: `" + origin.toString() + "`)";
- if (isAssumeNone()) {
- return super.toString() + "; nothing " + originString;
+ // During branch simplification, the origin `if` could be simplified.
+ // It means the assumption became "truth."
+ assert origin.hasBlock() || isAssumeNonNull();
+ String originString =
+ origin.hasBlock() ? " (origin: `" + origin.toString() + "`)" : " (origin simplified)";
+ if (isAssumeNone() || isAssumeNonNull()) {
+ return super.toString() + originString;
}
if (isAssumeDynamicType()) {
+ DynamicTypeAssumption assumption = asAssumeDynamicType().getAssumption();
return super.toString()
- + "; type: " + asAssumeDynamicType().getAssumption().type + originString;
- }
- if (isAssumeNonNull()) {
- return super.toString() + "; not null " + originString;
+ + "; upper bound: " + assumption.type
+ + (assumption.lowerBoundType != null ? "; lower bound: " + assumption.lowerBoundType : "")
+ + originString;
}
return super.toString();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index fa3074e..cb32015 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -1008,14 +1008,22 @@
}
public void removeAllTrivialPhis() {
- removeAllTrivialPhis(null);
+ removeAllTrivialPhis(null, null);
}
public void removeAllTrivialPhis(IRBuilder builder) {
+ removeAllTrivialPhis(builder, null);
+ }
+
+ public void removeAllTrivialPhis(Set<Value> affectedValues) {
+ removeAllTrivialPhis(null, affectedValues);
+ }
+
+ public void removeAllTrivialPhis(IRBuilder builder, Set<Value> affectedValues) {
for (BasicBlock block : blocks) {
List<Phi> phis = new ArrayList<>(block.getPhis());
for (Phi phi : phis) {
- phi.removeTrivialPhi(builder);
+ phi.removeTrivialPhi(builder, affectedValues);
}
}
}
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 e2f9cf3..95dac01 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
@@ -123,7 +123,7 @@
builder.constrainType(operand, readConstraint);
appendOperand(operand);
}
- removeTrivialPhi(builder);
+ removeTrivialPhi(builder, null);
}
public void addOperands(List<Value> operands) {
@@ -224,10 +224,10 @@
}
public void removeTrivialPhi() {
- removeTrivialPhi(null);
+ removeTrivialPhi(null, null);
}
- public void removeTrivialPhi(IRBuilder builder) {
+ public void removeTrivialPhi(IRBuilder builder, Set<Value> affectedValues) {
Value same = null;
for (Value op : operands) {
if (op == same || op == this) {
@@ -252,6 +252,9 @@
if (builder != null && typeLattice.isPreciseType() && !typeLattice.isBottom()) {
builder.constrainType(same, ValueTypeConstraint.fromTypeLattice(typeLattice));
}
+ if (affectedValues != null) {
+ affectedValues.addAll(this.affectedValues());
+ }
// Removing this phi, so get rid of it as a phi user from all of the operands to avoid
// recursively getting back here with the same phi. If the phi has itself as an operand
// that also removes the self-reference.
@@ -277,7 +280,7 @@
replaceUsers(same);
// Try to simplify phi users that might now have become trivial.
for (Phi user : phiUsersToSimplify) {
- user.removeTrivialPhi(builder);
+ user.removeTrivialPhi(builder, affectedValues);
}
}
// Get rid of the phi itself.
diff --git a/src/main/java/com/android/tools/r8/ir/code/Position.java b/src/main/java/com/android/tools/r8/ir/code/Position.java
index ec923ee..51196ea 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Position.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Position.java
@@ -108,6 +108,7 @@
return line == o.line
&& file == o.file
&& method == o.method
+ && synthetic == o.synthetic
&& Objects.equals(callerPosition, o.callerPosition);
}
return false;
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 2881de7..8e57306 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
@@ -19,6 +19,7 @@
import com.android.tools.r8.position.MethodPosition;
import com.android.tools.r8.utils.LongInterval;
import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.StringDiagnostic;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableSet;
@@ -424,6 +425,22 @@
return users.getFirst();
}
+ public Set<Instruction> aliasedUsers() {
+ Set<Instruction> users = SetUtils.newIdentityHashSet(uniqueUsers());
+ collectAliasedUsersViaAssume(uniqueUsers(), users);
+ return users;
+ }
+
+ private static void collectAliasedUsersViaAssume(
+ Set<Instruction> usersToTest, Set<Instruction> collectedUsers) {
+ for (Instruction user : usersToTest) {
+ if (user.isAssume()) {
+ collectedUsers.addAll(user.outValue().uniqueUsers());
+ collectAliasedUsersViaAssume(user.outValue().uniqueUsers(), collectedUsers);
+ }
+ }
+ }
+
public Phi firstPhiUser() {
assert !phiUsers.isEmpty();
return phiUsers.getFirst();
@@ -1142,9 +1159,9 @@
// i.e., towards something wider.
assert this.typeLattice.lessThanOrEqual(newType, appView)
: "During WIDENING, "
- + typeLattice
- + " < "
+ newType
+ + " < "
+ + typeLattice
+ " at "
+ (isPhi() ? asPhi().printPhi() : definition.toString());
setTypeLattice(newType);
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 dc2516c..1b31d62 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
@@ -1249,26 +1249,6 @@
assert code.verifyTypes(appView);
- if (nonNullTracker != null) {
- // TODO(b/139246447): Once we extend this optimization to, e.g., constants of primitive args,
- // this may not be the right place to collect call site optimization info.
- // Collecting call-site optimization info depends on the existence of non-null IRs.
- // Arguments can be changed during the debug mode.
- if (!isDebugMode && appView.callSiteOptimizationInfoPropagator() != null) {
- appView.callSiteOptimizationInfoPropagator().collectCallSiteOptimizationInfo(code);
- }
- // Computation of non-null parameters on normal exits rely on the existence of non-null IRs.
- nonNullTracker.computeNonNullParamOnNormalExits(feedback, code);
- }
- if (aliasIntroducer != null || nonNullTracker != null || dynamicTypeOptimization != null) {
- codeRewriter.removeAssumeInstructions(code);
- assert code.isConsistentSSA();
- }
- // Assert that we do not have unremoved non-sense code in the output, e.g., v <- non-null NULL.
- assert code.verifyNoNullabilityBottomTypes();
-
- assert code.verifyTypes(appView);
-
previous = printMethod(code, "IR before class inlining (SSA)", previous);
if (classInliner != null) {
@@ -1293,6 +1273,7 @@
Integer.MAX_VALUE / 2,
Integer.MAX_VALUE / 2)));
assert code.isConsistentSSA();
+ assert code.verifyTypes(appView);
}
previous = printMethod(code, "IR after class inlining (SSA)", previous);
@@ -1323,13 +1304,6 @@
twrCloseResourceRewriter.rewriteMethodCode(code);
}
- previous = printMethod(code, "IR after twr close resource rewriter (SSA)", previous);
-
- if (lambdaMerger != null) {
- lambdaMerger.processMethodCode(method, code);
- assert code.isConsistentSSA();
- }
-
if (nonNullTracker != null) {
// TODO(b/139246447): Once we extend this optimization to, e.g., constants of primitive args,
// this may not be the right place to collect call site optimization info.
@@ -1350,6 +1324,13 @@
assert code.verifyTypes(appView);
+ previous = printMethod(code, "IR after twr close resource rewriter (SSA)", previous);
+
+ if (lambdaMerger != null) {
+ lambdaMerger.processMethodCode(method, code);
+ assert code.isConsistentSSA();
+ }
+
previous = printMethod(code, "IR after lambda merger (SSA)", previous);
if (options.outline.enabled) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
index 376e186..08aefe9 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
@@ -19,6 +19,8 @@
public interface MethodOptimizationFeedback {
+ void markForceInline(DexEncodedMethod method);
+
void markInlinedIntoSingleCallSite(DexEncodedMethod method);
void markMethodCannotBeKept(DexEncodedMethod method);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index db8f582..1f049e5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -139,6 +139,10 @@
initializeEmulatedInterfaceVariables();
}
+ private boolean isDefaultOrStatic(DexEncodedMethod method) {
+ return method.isDefaultMethod() || method.isStatic();
+ }
+
private void initializeEmulatedInterfaceVariables() {
Map<DexType, DexType> emulateLibraryInterface =
options.desugaredLibraryConfiguration.getEmulateLibraryInterface();
@@ -148,7 +152,7 @@
DexClass emulatedInterfaceClass = appView.definitionFor(interfaceType);
if (emulatedInterfaceClass != null) {
for (DexEncodedMethod encodedMethod :
- emulatedInterfaceClass.methods(DexEncodedMethod::isDefaultMethod)) {
+ emulatedInterfaceClass.methods(this::isDefaultOrStatic)) {
emulatedMethods.add(encodedMethod.method.name);
}
}
@@ -635,7 +639,7 @@
DexProgramClass theInterface, Map<DexType, List<DexType>> emulatedInterfacesHierarchy) {
List<DexEncodedMethod> emulationMethods = new ArrayList<>();
for (DexEncodedMethod method : theInterface.methods()) {
- if (method.isDefaultMethod()) {
+ if (isDefaultOrStatic(method)) {
DexMethod libraryMethod =
factory.createMethod(
emulatedInterfaces.get(theInterface.type), method.method.proto, method.method.name);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
index 45f8c45..8d3f18f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
@@ -197,12 +197,12 @@
} else {
specializedArg = originalArg;
}
+ assert specializedArg != null && specializedArg.getTypeLattice().isReference();
if (dynamicType.isDefinitelyNotNull()) {
// If we already knew `arg` is never null, e.g., receiver, skip adding non-null.
if (!specializedArg.getTypeLattice().isDefinitelyNotNull()) {
- Value nonNullArg =
- code.createValue(
- specializedArg.getTypeLattice().asReferenceTypeLatticeElement().asNotNull());
+ Value nonNullArg = code.createValue(
+ specializedArg.getTypeLattice().asReferenceTypeLatticeElement().asMeetWithNotNull());
affectedValues.addAll(specializedArg.affectedValues());
specializedArg.replaceUsers(nonNullArg);
Assume<NonNullAssumption> assumeNotNull =
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 fba5ae7..1f36d25 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
@@ -218,7 +218,7 @@
// Therefore, Assume elimination may result in a trivial phi:
// z <- phi(x, x)
if (needToCheckTrivialPhis) {
- code.removeAllTrivialPhis();
+ code.removeAllTrivialPhis(valuesThatRequireWidening);
}
if (!valuesThatRequireWidening.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 9d2c5ad..545b624 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -357,46 +357,37 @@
info.include(invoke.getType(), candidate);
}
+ Value receiver = invoke.getReceiver();
+ if (receiver.getTypeLattice().isDefinitelyNull()) {
+ // A definitely null receiver will throw an error on call site.
+ return null;
+ }
InlineAction action = new InlineAction(candidate, invoke, reason);
- Value receiver = invoke.getReceiver();
if (receiver.getTypeLattice().isNullable()) {
- InternalOptions options = appView.options();
- if (receiver.getTypeLattice().isDefinitelyNull()) {
- if (!options.enableInliningOfInvokesWithDefinitelyNullReceivers) {
+ assert !receiver.getTypeLattice().isDefinitelyNull();
+ // When inlining an instance method call, we need to preserve the null check for the
+ // receiver. Therefore, if the receiver may be null and the candidate inlinee does not
+ // throw if the receiver is null before any other side effect, then we must synthesize a
+ // null check.
+ if (!candidate.getOptimizationInfo().checksNullReceiverBeforeAnySideEffect()) {
+ InternalOptions options = appView.options();
+ if (!options.enableInliningOfInvokesWithNullableReceivers) {
+ return null;
+ }
+ if (!options.nullableReceiverInliningFilter.isEmpty()
+ && !options.nullableReceiverInliningFilter.contains(
+ invoke.getInvokedMethod().toSourceString())) {
return null;
}
if (Log.ENABLED && Log.isLoggingEnabledFor(Inliner.class)) {
Log.debug(
Inliner.class,
- "Inlining method `%s` with definitely null receiver into `%s`",
+ "Inlining method `%s` with nullable receiver into `%s`",
invoke.getInvokedMethod().toSourceString(),
invocationContext.toSourceString());
}
- action.setShouldReturnEmptyThrowingCode();
- } else {
- // When inlining an instance method call, we need to preserve the null check for the
- // receiver. Therefore, if the receiver may be null and the candidate inlinee does not
- // throw if the receiver is null before any other side effect, then we must synthesize a
- // null check.
- if (!candidate.getOptimizationInfo().checksNullReceiverBeforeAnySideEffect()) {
- if (!options.enableInliningOfInvokesWithNullableReceivers) {
- return null;
- }
- if (!options.nullableReceiverInliningFilter.isEmpty()
- && !options.nullableReceiverInliningFilter.contains(
- invoke.getInvokedMethod().toSourceString())) {
- return null;
- }
- if (Log.ENABLED && Log.isLoggingEnabledFor(Inliner.class)) {
- Log.debug(
- Inliner.class,
- "Inlining method `%s` with nullable receiver into `%s`",
- invoke.getInvokedMethod().toSourceString(),
- invocationContext.toSourceString());
- }
- action.setShouldSynthesizeNullCheckForReceiver();
- }
+ action.setShouldSynthesizeNullCheckForReceiver();
}
}
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 c35cd11..b9c16e7 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
@@ -536,7 +536,6 @@
public final Invoke invoke;
final Reason reason;
- private boolean shouldReturnEmptyThrowingCode;
private boolean shouldSynthesizeNullCheckForReceiver;
InlineAction(DexEncodedMethod target, Invoke invoke, Reason reason) {
@@ -545,10 +544,6 @@
this.reason = reason;
}
- void setShouldReturnEmptyThrowingCode() {
- shouldReturnEmptyThrowingCode = true;
- }
-
void setShouldSynthesizeNullCheckForReceiver() {
shouldSynthesizeNullCheckForReceiver = true;
}
@@ -561,53 +556,48 @@
LensCodeRewriter lensCodeRewriter) {
Origin origin = appView.appInfo().originFor(target.method.holder);
- IRCode code;
- if (shouldReturnEmptyThrowingCode) {
- code = target.buildEmptyThrowingIRCode(appView, origin);
- } else {
- // Build the IR for a yet not processed method, and perform minimal IR processing.
- code = target.buildInliningIR(context, appView, generator, callerPosition, origin);
+ // Build the IR for a yet not processed method, and perform minimal IR processing.
+ IRCode code = target.buildInliningIR(context, appView, generator, callerPosition, origin);
- // Insert a null check if this is needed to preserve the implicit null check for the
- // receiver.
- if (shouldSynthesizeNullCheckForReceiver) {
- List<Value> arguments = code.collectArguments();
- if (!arguments.isEmpty()) {
- Value receiver = arguments.get(0);
- assert receiver.isThis();
+ // Insert a null check if this is needed to preserve the implicit null check for the
+ // receiver.
+ if (shouldSynthesizeNullCheckForReceiver) {
+ List<Value> arguments = code.collectArguments();
+ if (!arguments.isEmpty()) {
+ Value receiver = arguments.get(0);
+ assert receiver.isThis();
- BasicBlock entryBlock = code.entryBlock();
+ BasicBlock entryBlock = code.entryBlock();
- // Insert a new block between the last argument instruction and the first actual
- // instruction of the method.
- BasicBlock throwBlock =
- entryBlock.listIterator(code, arguments.size()).split(code, 0, null);
- assert !throwBlock.hasCatchHandlers();
+ // Insert a new block between the last argument instruction and the first actual
+ // instruction of the method.
+ BasicBlock throwBlock =
+ entryBlock.listIterator(code, arguments.size()).split(code, 0, null);
+ assert !throwBlock.hasCatchHandlers();
- // Link the entry block to the successor of the newly inserted block.
- BasicBlock continuationBlock = throwBlock.unlinkSingleSuccessor();
- entryBlock.link(continuationBlock);
+ // Link the entry block to the successor of the newly inserted block.
+ BasicBlock continuationBlock = throwBlock.unlinkSingleSuccessor();
+ entryBlock.link(continuationBlock);
- // Replace the last instruction of the entry block, which is now a goto instruction,
- // with an `if-eqz` instruction that jumps to the newly inserted block if the receiver
- // is null.
- If ifInstruction = new If(If.Type.EQ, receiver);
- entryBlock.replaceLastInstruction(ifInstruction, code);
- assert ifInstruction.getTrueTarget() == throwBlock;
- assert ifInstruction.fallthroughBlock() == continuationBlock;
+ // Replace the last instruction of the entry block, which is now a goto instruction,
+ // with an `if-eqz` instruction that jumps to the newly inserted block if the receiver
+ // is null.
+ If ifInstruction = new If(If.Type.EQ, receiver);
+ entryBlock.replaceLastInstruction(ifInstruction, code);
+ assert ifInstruction.getTrueTarget() == throwBlock;
+ assert ifInstruction.fallthroughBlock() == continuationBlock;
- // Replace the single goto instruction in the newly inserted block by `throw null`.
- InstructionListIterator iterator = throwBlock.listIterator(code);
- Value nullValue = iterator.insertConstNullInstruction(code, appView.options());
- iterator.next();
- iterator.replaceCurrentInstruction(new Throw(nullValue));
- } else {
- assert false : "Unable to synthesize a null check for the receiver";
- }
+ // Replace the single goto instruction in the newly inserted block by `throw null`.
+ InstructionListIterator iterator = throwBlock.listIterator(code);
+ Value nullValue = iterator.insertConstNullInstruction(code, appView.options());
+ iterator.next();
+ iterator.replaceCurrentInstruction(new Throw(nullValue));
+ } else {
+ assert false : "Unable to synthesize a null check for the receiver";
}
- if (!target.isProcessed()) {
- lensCodeRewriter.rewrite(code, target);
- }
+ }
+ if (!target.isProcessed()) {
+ lensCodeRewriter.rewrite(code, target);
}
return new InlineeWithReason(code, reason);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
index 02cfe3a..0adf3f7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
@@ -227,7 +227,7 @@
TypeLatticeElement typeLattice = knownToBeNonNullValue.getTypeLattice();
Value nonNullValue =
code.createValue(
- typeLattice.asReferenceTypeLatticeElement().asNotNull(),
+ typeLattice.asReferenceTypeLatticeElement().asMeetWithNotNull(),
knownToBeNonNullValue.getLocalInfo());
affectedValues.addAll(knownToBeNonNullValue.affectedValues());
Assume<NonNullAssumption> nonNull =
@@ -359,7 +359,7 @@
assert typeLattice.isReference();
Value nonNullValue =
code.createValue(
- typeLattice.asReferenceTypeLatticeElement().asNotNull(),
+ typeLattice.asReferenceTypeLatticeElement().asMeetWithNotNull(),
knownToBeNonNullValue.getLocalInfo());
affectedValues.addAll(knownToBeNonNullValue.affectedValues());
Assume<NonNullAssumption> nonNull =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index b656678..f550309 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -8,9 +8,11 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionOrPhi;
+import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.desugar.LambdaRewriter;
import com.android.tools.r8.ir.optimize.CodeRewriter;
import com.android.tools.r8.ir.optimize.Inliner;
@@ -18,9 +20,11 @@
import com.android.tools.r8.ir.optimize.string.StringOptimizer;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import java.util.Iterator;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
import java.util.function.Supplier;
@@ -34,8 +38,15 @@
NON_CLASS_TYPE,
UNKNOWN_TYPE,
+ // Used by isClassEligible
+ NON_PROGRAM_CLASS,
+ ABSTRACT_OR_INTERFACE,
+ NEVER_CLASS_INLINE,
+ IS_PINNED_TYPE,
+ HAS_FINALIZER,
+ TRIGGER_CLINIT,
+
// Used by InlineCandidateProcessor#isClassAndUsageEligible
- INELIGIBLE_CLASS,
HAS_CLINIT,
HAS_INSTANCE_FIELDS,
NON_FINAL_TYPE,
@@ -46,7 +57,8 @@
}
private final LambdaRewriter lambdaRewriter;
- private final ConcurrentHashMap<DexClass, Boolean> knownClasses = new ConcurrentHashMap<>();
+ private final ConcurrentHashMap<DexClass, EligibilityStatus> knownClasses =
+ new ConcurrentHashMap<>();
public ClassInliner(LambdaRewriter lambdaRewriter) {
this.lambdaRewriter = lambdaRewriter;
@@ -56,7 +68,15 @@
DexEncodedMethod context, Instruction root, EligibilityStatus status) {
if (Log.ENABLED && Log.isLoggingEnabledFor(ClassInliner.class)) {
Log.info(getClass(), "At %s,", context.toSourceString());
- Log.info(getClass(), "ClassInlining eligibility of %s: %s,", root, status);
+ Log.info(getClass(), "ClassInlining eligibility of `%s`: %s.", root, status);
+ }
+ }
+
+ private void logIneligibleUser(
+ DexEncodedMethod context, Instruction root, InstructionOrPhi ineligibleUser) {
+ if (Log.ENABLED && Log.isLoggingEnabledFor(ClassInliner.class)) {
+ Log.info(getClass(), "At %s,", context.toSourceString());
+ Log.info(getClass(), "Ineligible user of `%s`: `%s`.", root, ineligibleUser);
}
}
@@ -133,7 +153,7 @@
// return 1;
// }
// static int method3() {
- // return "F::getX";
+ // return 123;
// }
// }
//
@@ -195,6 +215,7 @@
InstructionOrPhi ineligibleUser = processor.areInstanceUsersEligible(defaultOracle);
if (ineligibleUser != null) {
// This root may succeed if users change in future.
+ logIneligibleUser(code.method, root, ineligibleUser);
continue;
}
@@ -208,7 +229,11 @@
anyInlinedMethods |= processor.processInlining(code, defaultOracle);
// Restore normality.
- code.removeAllTrivialPhis();
+ Set<Value> affectedValues = Sets.newIdentityHashSet();
+ code.removeAllTrivialPhis(affectedValues);
+ if (!affectedValues.isEmpty()) {
+ new TypeAnalysis(appView).narrowing(affectedValues);
+ }
assert code.isConsistentSSA();
rootsIterator.remove();
repeat = true;
@@ -233,11 +258,11 @@
}
}
- private boolean isClassEligible(AppView<AppInfoWithLiveness> appView, DexClass clazz) {
- Boolean eligible = knownClasses.get(clazz);
+ private EligibilityStatus isClassEligible(AppView<AppInfoWithLiveness> appView, DexClass clazz) {
+ EligibilityStatus eligible = knownClasses.get(clazz);
if (eligible == null) {
- boolean computed = computeClassEligible(appView, clazz);
- Boolean existing = knownClasses.putIfAbsent(clazz, computed);
+ EligibilityStatus computed = computeClassEligible(appView, clazz);
+ EligibilityStatus existing = knownClasses.putIfAbsent(clazz, computed);
assert existing == null || existing == computed;
eligible = existing == null ? computed : existing;
}
@@ -248,13 +273,22 @@
// - is not an abstract class or interface
// - does not declare finalizer
// - does not trigger any static initializers except for its own
- private boolean computeClassEligible(AppView<AppInfoWithLiveness> appView, DexClass clazz) {
- if (clazz == null
- || clazz.isNotProgramClass()
- || clazz.accessFlags.isAbstract()
- || clazz.accessFlags.isInterface()
- || appView.appInfo().neverClassInline.contains(clazz.type)) {
- return false;
+ private EligibilityStatus computeClassEligible(
+ AppView<AppInfoWithLiveness> appView, DexClass clazz) {
+ if (clazz == null) {
+ return EligibilityStatus.UNKNOWN_TYPE;
+ }
+ if (clazz.isNotProgramClass()) {
+ return EligibilityStatus.NON_PROGRAM_CLASS;
+ }
+ if (clazz.isAbstract() || clazz.isInterface()) {
+ return EligibilityStatus.ABSTRACT_OR_INTERFACE;
+ }
+ if (appView.appInfo().neverClassInline.contains(clazz.type)) {
+ return EligibilityStatus.NEVER_CLASS_INLINE;
+ }
+ if (appView.appInfo().isPinned(clazz.type)) {
+ return EligibilityStatus.IS_PINNED_TYPE;
}
// Class must not define finalizer.
@@ -262,11 +296,14 @@
for (DexEncodedMethod method : clazz.virtualMethods()) {
if (method.method.name == dexItemFactory.finalizeMethodName
&& method.method.proto == dexItemFactory.objectMethods.finalize.proto) {
- return false;
+ return EligibilityStatus.HAS_FINALIZER;
}
}
// Check for static initializers in this class or any of interfaces it implements.
- return !clazz.initializationOfParentTypesMayHaveSideEffects(appView);
+ if (clazz.initializationOfParentTypesMayHaveSideEffects(appView)) {
+ return EligibilityStatus.TRIGGER_CLINIT;
+ }
+ return EligibilityStatus.ELIGIBLE;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/FieldValueHelper.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/FieldValueHelper.java
index e207e50..d06b3f9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/FieldValueHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/FieldValueHelper.java
@@ -41,6 +41,9 @@
this.code = code;
this.root = root;
this.appView = appView;
+ // Verify that `root` is not aliased.
+ assert root.hasOutValue();
+ assert root.outValue() == root.outValue().getAliasedValue();
}
void replaceValue(Value oldValue, Value newValue) {
@@ -124,10 +127,10 @@
Instruction instruction = iterator.previous();
assert instruction != null;
- if (instruction == root ||
- (instruction.isInstancePut() &&
- instruction.asInstancePut().getField() == field &&
- instruction.asInstancePut().object() == root.outValue())) {
+ if (instruction == root
+ || (instruction.isInstancePut()
+ && instruction.asInstancePut().getField() == field
+ && instruction.asInstancePut().object().getAliasedValue() == root.outValue())) {
valueProducingInsn = instruction;
break;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index a2e9358..6fdba3d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
+import com.android.tools.r8.ir.code.Assume;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.IRCode;
@@ -40,16 +41,19 @@
import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
import com.android.tools.r8.kotlin.KotlinInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import java.util.ArrayList;
-import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
@@ -60,7 +64,7 @@
private final AppView<AppInfoWithLiveness> appView;
private final LambdaRewriter lambdaRewriter;
private final Inliner inliner;
- private final Predicate<DexClass> isClassEligible;
+ private final Function<DexClass, EligibilityStatus> isClassEligible;
private final Predicate<DexEncodedMethod> isProcessedConcurrently;
private final DexEncodedMethod method;
private final Instruction root;
@@ -83,7 +87,7 @@
AppView<AppInfoWithLiveness> appView,
LambdaRewriter lambdaRewriter,
Inliner inliner,
- Predicate<DexClass> isClassEligible,
+ Function<DexClass, EligibilityStatus> isClassEligible,
Predicate<DexEncodedMethod> isProcessedConcurrently,
DexEncodedMethod method,
Instruction root) {
@@ -140,8 +144,9 @@
// * class has class initializer marked as TrivialClassInitializer, and
// class initializer initializes the field we are reading here.
EligibilityStatus isClassAndUsageEligible() {
- if (!isClassEligible.test(eligibleClassDefinition)) {
- return EligibilityStatus.INELIGIBLE_CLASS;
+ EligibilityStatus status = isClassEligible.apply(eligibleClassDefinition);
+ if (status != EligibilityStatus.ELIGIBLE) {
+ return status;
}
if (root.isNewInstance()) {
@@ -251,7 +256,7 @@
*
* @return null if all users are eligible, or the first ineligible user.
*/
- protected InstructionOrPhi areInstanceUsersEligible(Supplier<InliningOracle> defaultOracle) {
+ InstructionOrPhi areInstanceUsersEligible(Supplier<InliningOracle> defaultOracle) {
// No Phi users.
if (eligibleInstance.numberOfPhiUsers() > 0) {
return eligibleInstance.firstPhiUser(); // Not eligible.
@@ -259,8 +264,15 @@
Set<Instruction> currentUsers = eligibleInstance.uniqueUsers();
while (!currentUsers.isEmpty()) {
- Set<Instruction> indirectUsers = new HashSet<>();
+ Set<Instruction> indirectUsers = Sets.newIdentityHashSet();
for (Instruction user : currentUsers) {
+ if (user.isAssume()) {
+ if (user.outValue().numberOfPhiUsers() > 0) {
+ return user.outValue().firstPhiUser(); // Not eligible.
+ }
+ indirectUsers.addAll(user.outValue().uniqueUsers());
+ continue;
+ }
// Field read/write.
if (user.isInstanceGet()
|| (user.isInstancePut() && user.asInstancePut().value() != eligibleInstance)) {
@@ -288,7 +300,7 @@
boolean isCorrespondingConstructorCall =
root.isNewInstance()
&& !invoke.inValues().isEmpty()
- && root.outValue() == invoke.inValues().get(0);
+ && root.outValue() == invoke.getReceiver();
if (isCorrespondingConstructorCall) {
InliningInfo inliningInfo =
isEligibleConstructorCall(user.asInvokeDirect(), singleTarget, defaultOracle);
@@ -343,6 +355,7 @@
// Process inlining, includes the following steps:
//
+ // * remove linked assume instructions if any so that users of the eligible field are up-to-date.
// * replace unused instance usages as arguments which are never used
// * inline extra methods if any, collect new direct method calls
// * inline direct methods if any
@@ -352,6 +365,8 @@
//
// Returns `true` if at least one method was inlined.
boolean processInlining(IRCode code, Supplier<InliningOracle> defaultOracle) {
+ // Verify that `eligibleInstance` is not aliased.
+ assert eligibleInstance == eligibleInstance.getAliasedValue();
replaceUsagesAsUnusedArgument(code);
boolean anyInlinedMethods = forceInlineExtraMethodInvocations(code);
@@ -374,11 +389,14 @@
// methods that need to be inlined anyway.
return true;
}
- assert extraMethodCalls.isEmpty();
- assert unusedArguments.isEmpty();
+ assert extraMethodCalls.isEmpty()
+ : "Remaining extra method calls: " + StringUtils.join(extraMethodCalls.entrySet(), ", ");
+ assert unusedArguments.isEmpty()
+ : "Remaining unused arg: " + StringUtils.join(unusedArguments, ", ");
}
anyInlinedMethods |= forceInlineDirectMethodInvocations(code);
+ removeAssumeInstructionsLinkedToEligibleInstance();
removeMiscUsages(code);
removeFieldReads(code);
removeFieldWrites();
@@ -419,6 +437,22 @@
return true;
}
+ private void removeAssumeInstructionsLinkedToEligibleInstance() {
+ for (Instruction user : eligibleInstance.aliasedUsers()) {
+ if (!user.isAssume()) {
+ continue;
+ }
+ Assume<?> assumeInstruction = user.asAssume();
+ Value src = assumeInstruction.src();
+ Value dest = assumeInstruction.outValue();
+ assert dest.numberOfPhiUsers() == 0;
+ dest.replaceUsers(src);
+ removeInstruction(user);
+ }
+ // Verify that no more assume instructions are left as users.
+ assert eligibleInstance.aliasedUsers().stream().noneMatch(Instruction::isAssume);
+ }
+
// Remove miscellaneous users before handling field reads.
private void removeMiscUsages(IRCode code) {
boolean needToRemoveUnreachableBlocks = false;
@@ -495,8 +529,8 @@
}
}
- private void replaceFieldRead(IRCode code,
- InstanceGet fieldRead, Map<DexField, FieldValueHelper> fieldHelpers) {
+ private void replaceFieldRead(
+ IRCode code, InstanceGet fieldRead, Map<DexField, FieldValueHelper> fieldHelpers) {
Value value = fieldRead.outValue();
if (value != null) {
FieldValueHelper helper =
@@ -508,7 +542,11 @@
fieldValueHelper.replaceValue(value, newValue);
}
assert value.numberOfAllUsers() == 0;
- new TypeAnalysis(appView).narrowing(newValue.affectedValues());
+ // `newValue` could be a phi introduced by FieldValueHelper. Its initial type is set as the
+ // type of read field, but it could be more precise than that due to (multiple) inlining.
+ // In addition to values affected by `newValue`, it's necessary to revisit `newValue` itself.
+ new TypeAnalysis(appView).narrowing(
+ Iterables.concat(ImmutableSet.of(newValue), newValue.affectedValues()));
}
removeInstruction(fieldRead);
}
@@ -539,7 +577,8 @@
assert isEligibleSingleTarget(singleTarget);
// Must be a constructor called on the receiver.
- if (invoke.inValues().lastIndexOf(eligibleInstance) != 0) {
+ if (ListUtils.lastIndexMatching(
+ invoke.inValues(), v -> v.getAliasedValue() == eligibleInstance) != 0) {
return null;
}
@@ -594,7 +633,7 @@
: null;
}
- // An invoke is eligible for inlinining in the following cases:
+ // An invoke is eligible for inlining in the following cases:
//
// - if it does not return the receiver
// - if there are no uses of the out value
@@ -645,7 +684,8 @@
DexEncodedMethod singleTarget,
Set<Instruction> indirectUsers) {
assert isEligibleSingleTarget(singleTarget);
- if (invoke.inValues().lastIndexOf(eligibleInstance) > 0) {
+ if (ListUtils.lastIndexMatching(
+ invoke.inValues(), v -> v.getAliasedValue() == eligibleInstance) > 0) {
return null; // Instance passed as an argument.
}
return isEligibleVirtualMethodCall(
@@ -714,7 +754,8 @@
return false;
}
if (invoke.isInvokeMethodWithReceiver()
- && invoke.asInvokeMethodWithReceiver().getReceiver() == eligibleInstance) {
+ && invoke.asInvokeMethodWithReceiver().getReceiver().getAliasedValue()
+ == eligibleInstance) {
return false;
}
if (invoke.isInvokeSuper()) {
@@ -755,7 +796,7 @@
// If we got here with invocation on receiver the user is ineligible.
if (invoke.isInvokeMethodWithReceiver()) {
- if (arguments.get(0) == eligibleInstance) {
+ if (arguments.get(0).getAliasedValue() == eligibleInstance) {
return false;
}
@@ -796,7 +837,7 @@
Supplier<InliningOracle> defaultOracle) {
// Go through all arguments, see if all usages of eligibleInstance are good.
for (int argIndex = 0; argIndex < arguments.size(); argIndex++) {
- Value argument = arguments.get(argIndex);
+ Value argument = arguments.get(argIndex).getAliasedValue();
if (argument != eligibleInstance) {
continue; // Nothing to worry about.
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index 162642c..d185a37 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -107,6 +107,11 @@
// METHOD OPTIMIZATION INFO:
@Override
+ public void markForceInline(DexEncodedMethod method) {
+ getMethodOptimizationInfoForUpdating(method).markForceInline();
+ }
+
+ @Override
public synchronized void markInlinedIntoSingleCallSite(DexEncodedMethod method) {
getMethodOptimizationInfoForUpdating(method).markInlinedIntoSingleCallSite();
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
index ea5afa0..79b2e7f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
@@ -44,6 +44,9 @@
// METHOD OPTIMIZATION INFO:
@Override
+ public void markForceInline(DexEncodedMethod method) {}
+
+ @Override
public void markInlinedIntoSingleCallSite(DexEncodedMethod method) {}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index 537cbd1..9119b4b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -52,6 +52,11 @@
// METHOD OPTIMIZATION INFO.
@Override
+ public void markForceInline(DexEncodedMethod method) {
+ // Ignored.
+ }
+
+ @Override
public synchronized void markInlinedIntoSingleCallSite(DexEncodedMethod method) {
method.getMutableOptimizationInfo().markInlinedIntoSingleCallSite();
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java
index a90efc7..4ce3033 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java
@@ -79,6 +79,10 @@
}
}
+ public final boolean allLambdas(Predicate<LambdaInfo> predicate) {
+ return !anyLambda(lambda -> !predicate.test(lambda));
+ }
+
public final boolean anyLambda(Predicate<LambdaInfo> predicate) {
assert verifyLambdaIds(false);
for (LambdaInfo info : lambdas.values()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
index f399ad6..89451c9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
@@ -228,6 +228,11 @@
// sequential lambda ids, create group lambda classes.
Map<LambdaGroup, DexProgramClass> lambdaGroupsClasses = finalizeLambdaGroups();
+ // Mark all the implementation methods for force inlining.
+ for (LambdaGroup group : lambdaGroupsClasses.keySet()) {
+ group.forEachLambda(info -> info.clazz.virtualMethods().forEach(feedback::markForceInline));
+ }
+
// Fixup optimization info to ensure that the optimization info does not refer to any merged
// lambdas.
LambdaMergerOptimizationInfoFixer optimizationInfoFixer =
@@ -238,7 +243,6 @@
this.strategyFactory = (method, code) -> new ApplyStrategy(method, code, optimizationInfoFixer);
// Add synthesized lambda group classes to the builder.
-
for (Entry<LambdaGroup, DexProgramClass> entry : lambdaGroupsClasses.entrySet()) {
DexProgramClass synthesizedClass = entry.getValue();
appView.appInfo().addSynthesizedClass(synthesizedClass);
@@ -254,6 +258,18 @@
synthesizedClass.forEachMethod(
encodedMethod -> encodedMethod.markProcessed(ConstraintWithTarget.NEVER));
}
+
+ // Verify that all implementation methods are marked for force inlining (i.e., check that the
+ // delayed optimization feedback has been flushed).
+ assert lambdaGroupsClasses.keySet().stream()
+ .allMatch(
+ group ->
+ group.allLambdas(
+ lambda ->
+ lambda.clazz.virtualMethods().stream()
+ .map(DexEncodedMethod::getOptimizationInfo)
+ .allMatch(MethodOptimizationInfo::forceInline)));
+
converter.optimizeSynthesizedClasses(lambdaGroupsClasses.values(), executorService);
// Rewrite lambda class references into lambda group class
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
index 4226daa..15d4160 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
@@ -105,14 +105,6 @@
// anyways and our new method is a product of inlining.
MethodAccessFlags accessFlags = MAIN_METHOD_FLAGS.copy();
- // Mark all the impl methods for force inlining
- // LambdaGroupVirtualMethodSourceCode relies on.
- for (DexEncodedMethod implMethod : implMethods) {
- if (implMethod != null) {
- implMethod.getMutableOptimizationInfo().markForceInline();
- }
- }
-
DexMethod method = factory.createMethod(group.getGroupClassType(), methodProto, methodName);
result.add(
new DexEncodedMethod(
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index cadd59c..693058b 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -139,8 +139,8 @@
return isAnnotationTypeLive(annotation);
}
- public AnnotationRemover ensureValid(ProguardConfiguration.Builder compatibility) {
- keep.ensureValid(appView.options().forceProguardCompatibility, compatibility);
+ public AnnotationRemover ensureValid() {
+ keep.ensureValid(appView.options().forceProguardCompatibility);
return this;
}
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 2e7ed67..65b708c 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -276,9 +276,6 @@
/** A queue of items that need processing. Different items trigger different actions. */
private final EnqueuerWorklist workList;
- /** A queue of items that have been added to try to keep Proguard compatibility. */
- private final EnqueuerWorklist proguardCompatibilityWorkList;
-
/**
* A set of methods that need code inspection for Java reflection in use.
*/
@@ -305,11 +302,6 @@
*/
private final Map<DexType, Set<DexAnnotation>> deferredAnnotations = new IdentityHashMap<>();
- /**
- * Set of keep rules generated for Proguard compatibility in Proguard compatibility mode.
- */
- private final ProguardConfiguration.Builder compatibility;
-
/** Map of active if rules to speed up aapt2 generated keep rules. */
private Map<Wrapper<ProguardIfRule>, Set<ProguardIfRule>> activeIfRules;
@@ -328,21 +320,17 @@
Enqueuer(
AppView<? extends AppInfoWithSubtyping> appView,
GraphConsumer keptGraphConsumer,
- ProguardConfiguration.Builder compatibility,
Mode mode) {
assert appView.appServices() != null;
InternalOptions options = appView.options();
this.appInfo = appView.appInfo();
this.appView = appView;
- this.compatibility = compatibility;
this.forceProguardCompatibility = options.forceProguardCompatibility;
this.graphReporter = new GraphReporter();
this.keptGraphConsumer = recordKeptGraph(options, keptGraphConsumer);
this.mode = mode;
this.options = options;
this.workList = EnqueuerWorklist.createWorklist(appView);
- this.proguardCompatibilityWorkList =
- EnqueuerWorklist.createProguardCompatibilityWorklist(appView);
if (options.enableGeneratedMessageLiteShrinking && mode.isInitialOrFinalTreeShaking()) {
registerAnalysis(new ProtoEnqueuerExtension(appView));
@@ -458,15 +446,14 @@
} else {
workList.enqueueMarkInstantiatedAction(clazz, witness);
if (clazz.hasDefaultInitializer()) {
+ DexEncodedMethod defaultInitializer = clazz.getDefaultInitializer();
if (forceProguardCompatibility) {
- ProguardKeepRule compatRule =
- ProguardConfigurationUtils.buildDefaultInitializerKeepRule(clazz);
- proguardCompatibilityWorkList.enqueueMarkMethodKeptAction(
- clazz.getDefaultInitializer(),
- KeepReason.dueToProguardCompatibilityKeepRule(compatRule));
+ workList.enqueueMarkMethodKeptAction(
+ defaultInitializer,
+ graphReporter.reportCompatKeepDefaultInitializer(clazz, defaultInitializer));
}
if (clazz.isExternalizable(appView)) {
- enqueueMarkMethodLiveAction(clazz, clazz.getDefaultInitializer(), witness);
+ enqueueMarkMethodLiveAction(clazz, defaultInitializer, witness);
}
}
}
@@ -1055,10 +1042,8 @@
DexProgramClass baseClass = getProgramClassOrNull(baseType);
if (baseClass != null) {
// Don't require any constructor, see b/112386012.
- markClassAsInstantiatedWithCompatRule(baseClass);
- } else {
- // This also handles reporting of missing classes.
- markTypeAsLive(baseType, this::reportClassReferenced);
+ markClassAsInstantiatedWithCompatRule(
+ baseClass, graphReporter.reportCompatInstantiated(baseClass, currentMethod));
}
return true;
}
@@ -1463,7 +1448,6 @@
populateInstantiatedTypesCache(clazz);
- collectProguardCompatibilityRule(reason);
if (Log.ENABLED) {
Log.verbose(getClass(), "Class `%s` is instantiated, processing...", clazz);
}
@@ -1700,7 +1684,6 @@
}
processAnnotations(encodedField, encodedField.annotations.annotations);
liveFields.add(encodedField, reason);
- collectProguardCompatibilityRule(reason);
// Add all dependent members to the workqueue.
enqueueRootItems(rootSet.getDependentItems(encodedField));
@@ -1719,7 +1702,6 @@
}
processAnnotations(field, field.annotations.annotations);
liveFields.add(field, reason);
- collectProguardCompatibilityRule(reason);
// Add all dependent members to the workqueue.
enqueueRootItems(rootSet.getDependentItems(field));
@@ -2342,9 +2324,6 @@
pendingReflectiveUses.forEach(this::handleReflectiveBehavior);
pendingReflectiveUses.clear();
}
- if (!proguardCompatibilityWorkList.isEmpty()) {
- proguardCompatibilityWorkList.transferTo(workList, liveMethods::add);
- }
if (!workList.isEmpty()) {
continue;
}
@@ -2493,8 +2472,6 @@
return;
}
- collectProguardCompatibilityRule(reason);
-
Set<DexEncodedMethod> superCallTargets = superInvokeDependencies.get(method);
if (superCallTargets != null) {
for (DexEncodedMethod superCallTarget : superCallTargets) {
@@ -2536,12 +2513,6 @@
clazz -> graphReporter.reportClassReferencedFrom(clazz, method));
}
- private void collectProguardCompatibilityRule(KeepReason reason) {
- if (reason.isDueToProguardCompatibility() && compatibility != null) {
- compatibility.addRule(reason.getProguardKeepRule());
- }
- }
-
private void markClassAsInstantiatedWithReason(DexProgramClass clazz, KeepReason reason) {
workList.enqueueMarkInstantiatedAction(clazz, reason);
if (clazz.hasDefaultInitializer()) {
@@ -2549,26 +2520,22 @@
}
}
- private void markClassAsInstantiatedWithCompatRule(DexProgramClass clazz) {
- ProguardKeepRule rule = ProguardConfigurationUtils.buildDefaultInitializerKeepRule(clazz);
- KeepReason reason = KeepReason.dueToProguardCompatibilityKeepRule(rule);
+ private void markClassAsInstantiatedWithCompatRule(DexProgramClass clazz, KeepReason reason) {
if (clazz.isInterface() && !clazz.accessFlags.isAnnotation()) {
markInterfaceAsInstantiated(clazz, reason);
return;
}
- proguardCompatibilityWorkList.enqueueMarkInstantiatedAction(clazz, reason);
+ workList.enqueueMarkInstantiatedAction(clazz, reason);
if (clazz.hasDefaultInitializer()) {
- proguardCompatibilityWorkList.enqueueMarkReachableDirectAction(
- clazz.getDefaultInitializer().method, reason);
+ DexEncodedMethod defaultInitializer = clazz.getDefaultInitializer();
+ workList.enqueueMarkReachableDirectAction(
+ defaultInitializer.method,
+ graphReporter.reportCompatKeepDefaultInitializer(clazz, defaultInitializer));
}
}
private void markMethodAsLiveWithCompatRule(DexProgramClass clazz, DexEncodedMethod method) {
- proguardCompatibilityWorkList.enqueueMarkMethodLiveAction(
- clazz,
- method,
- KeepReason.dueToProguardCompatibilityKeepRule(
- ProguardConfigurationUtils.buildMethodKeepRule(clazz, method)));
+ enqueueMarkMethodLiveAction(clazz, method, graphReporter.reportCompatKeepMethod(clazz, method));
}
private void handleReflectiveBehavior(DexEncodedMethod method) {
@@ -3173,6 +3140,40 @@
return KeepReasonWitness.INSTANCE;
}
+ public KeepReasonWitness reportCompatKeepDefaultInitializer(
+ DexProgramClass holder, DexEncodedMethod defaultInitializer) {
+ assert holder.type == defaultInitializer.method.holder;
+ assert holder.getDefaultInitializer() == defaultInitializer;
+ if (keptGraphConsumer != null) {
+ reportEdge(
+ getClassGraphNode(holder.type),
+ getMethodGraphNode(defaultInitializer.method),
+ EdgeKind.CompatibilityRule);
+ }
+ return KeepReasonWitness.COMPAT_INSTANCE;
+ }
+
+ public KeepReasonWitness reportCompatKeepMethod(
+ DexProgramClass holder, DexEncodedMethod method) {
+ assert holder.type == method.method.holder;
+ // TODO(b/141729349): This compat rule is from the method to itself and has not edge. Fix it.
+ // The rule is stating that if the method is targeted it is live. Since such an edge does
+ // not contribute to additional information in the kept graph as it stands (no distinction
+ // of targeted vs live edges), there is little point in emitting it.
+ return KeepReasonWitness.COMPAT_INSTANCE;
+ }
+
+ public KeepReason reportCompatInstantiated(
+ DexProgramClass instantiated, DexEncodedMethod method) {
+ if (keptGraphConsumer != null) {
+ reportEdge(
+ getMethodGraphNode(method.method),
+ getClassGraphNode(instantiated.type),
+ EdgeKind.CompatibilityRule);
+ }
+ return KeepReasonWitness.COMPAT_INSTANCE;
+ }
+
public KeepReasonWitness reportClassReferencedFrom(
DexProgramClass clazz, DexEncodedMethod method) {
if (keptGraphConsumer != null) {
@@ -3233,6 +3234,13 @@
private static class KeepReasonWitness extends KeepReason {
private static KeepReasonWitness INSTANCE = new KeepReasonWitness();
+ private static KeepReasonWitness COMPAT_INSTANCE =
+ new KeepReasonWitness() {
+ @Override
+ public boolean isDueToProguardCompatibility() {
+ return true;
+ }
+ };
@Override
public EdgeKind edgeKind() {
@@ -3247,7 +3255,7 @@
private boolean skipReporting(KeepReason reason) {
assert reason != null;
- if (reason == KeepReasonWitness.INSTANCE) {
+ if (reason == KeepReasonWitness.INSTANCE || reason == KeepReasonWitness.COMPAT_INSTANCE) {
return true;
}
assert getSourceNode(reason) != null;
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
index 340b469..e716083 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
@@ -13,18 +13,12 @@
public static Enqueuer createForInitialTreeShaking(
AppView<? extends AppInfoWithSubtyping> appView) {
- return createForInitialTreeShaking(appView, null);
- }
-
- public static Enqueuer createForInitialTreeShaking(
- AppView<? extends AppInfoWithSubtyping> appView,
- ProguardConfiguration.Builder compatibility) {
- return new Enqueuer(appView, null, compatibility, Mode.INITIAL_TREE_SHAKING);
+ return new Enqueuer(appView, null, Mode.INITIAL_TREE_SHAKING);
}
public static Enqueuer createForFinalTreeShaking(
AppView<? extends AppInfoWithSubtyping> appView, GraphConsumer keptGraphConsumer) {
- return new Enqueuer(appView, keptGraphConsumer, null, Mode.FINAL_TREE_SHAKING);
+ return new Enqueuer(appView, keptGraphConsumer, Mode.FINAL_TREE_SHAKING);
}
public static Enqueuer createForMainDexTracing(AppView<? extends AppInfoWithSubtyping> appView) {
@@ -33,11 +27,11 @@
public static Enqueuer createForMainDexTracing(
AppView<? extends AppInfoWithSubtyping> appView, GraphConsumer keptGraphConsumer) {
- return new Enqueuer(appView, keptGraphConsumer, null, Mode.MAIN_DEX_TRACING);
+ return new Enqueuer(appView, keptGraphConsumer, Mode.MAIN_DEX_TRACING);
}
public static Enqueuer createForWhyAreYouKeeping(
AppView<? extends AppInfoWithSubtyping> appView, GraphConsumer keptGraphConsumer) {
- return new Enqueuer(appView, keptGraphConsumer, null, Mode.WHY_ARE_YOU_KEEPING);
+ return new Enqueuer(appView, keptGraphConsumer, Mode.WHY_ARE_YOU_KEEPING);
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
index 86058bd..bde6b8d 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
@@ -12,7 +12,6 @@
import com.android.tools.r8.graph.DexProgramClass;
import java.util.ArrayDeque;
import java.util.Queue;
-import java.util.function.BiPredicate;
public class EnqueuerWorklist {
@@ -46,19 +45,12 @@
private final AppView<?> appView;
private final Queue<Action> queue = new ArrayDeque<>();
- private final boolean restrictToProguardCompatibilityRules;
-
- private EnqueuerWorklist(AppView<?> appView, boolean restrictToProguardCompatibilityRules) {
+ private EnqueuerWorklist(AppView<?> appView) {
this.appView = appView;
- this.restrictToProguardCompatibilityRules = restrictToProguardCompatibilityRules;
}
public static EnqueuerWorklist createWorklist(AppView<?> appView) {
- return new EnqueuerWorklist(appView, false);
- }
-
- public static EnqueuerWorklist createProguardCompatibilityWorklist(AppView<?> appView) {
- return new EnqueuerWorklist(appView, true);
+ return new EnqueuerWorklist(appView);
}
public boolean isEmpty() {
@@ -69,32 +61,15 @@
return queue.poll();
}
- public void transferTo(
- EnqueuerWorklist worklist, BiPredicate<DexEncodedMethod, KeepReason> filter) {
- while (!queue.isEmpty()) {
- Action action = queue.poll();
- if (action.kind == Action.Kind.MARK_METHOD_LIVE) {
- DexEncodedMethod method = (DexEncodedMethod) action.target;
- if (!filter.test(method, action.reason)) {
- continue;
- }
- }
- worklist.queue.add(action);
- }
- }
-
void enqueueMarkReachableDirectAction(DexMethod method, KeepReason reason) {
- assert !restrictToProguardCompatibilityRules || reason.isDueToProguardCompatibility();
queue.add(new Action(Action.Kind.MARK_REACHABLE_DIRECT, method, null, reason));
}
void enqueueMarkReachableVirtualAction(DexMethod method, KeepReason reason) {
- assert !restrictToProguardCompatibilityRules || reason.isDueToProguardCompatibility();
queue.add(new Action(Action.Kind.MARK_REACHABLE_VIRTUAL, method, null, reason));
}
void enqueueMarkReachableInterfaceAction(DexMethod method, KeepReason reason) {
- assert !restrictToProguardCompatibilityRules || reason.isDueToProguardCompatibility();
queue.add(new Action(Action.Kind.MARK_REACHABLE_INTERFACE, method, null, reason));
}
@@ -104,32 +79,27 @@
public void enqueueMarkReachableFieldAction(
DexProgramClass clazz, DexEncodedField field, KeepReason reason) {
- assert !restrictToProguardCompatibilityRules || reason.isDueToProguardCompatibility();
assert field.field.holder == clazz.type;
queue.add(new Action(Action.Kind.MARK_REACHABLE_FIELD, field, null, reason));
}
void enqueueMarkInstantiatedAction(DexProgramClass clazz, KeepReason reason) {
assert !clazz.isInterface() || clazz.accessFlags.isAnnotation();
- assert !restrictToProguardCompatibilityRules || reason.isDueToProguardCompatibility();
queue.add(new Action(Action.Kind.MARK_INSTANTIATED, clazz, null, reason));
}
void enqueueMarkMethodLiveAction(
DexProgramClass clazz, DexEncodedMethod method, KeepReason reason) {
- assert !restrictToProguardCompatibilityRules || reason.isDueToProguardCompatibility();
assert method.method.holder == clazz.type;
queue.add(new Action(Action.Kind.MARK_METHOD_LIVE, method, null, reason));
}
void enqueueMarkMethodKeptAction(DexEncodedMethod method, KeepReason reason) {
- assert !restrictToProguardCompatibilityRules || reason.isDueToProguardCompatibility();
assert method.isProgramMethod(appView);
queue.add(new Action(Action.Kind.MARK_METHOD_KEPT, method, null, reason));
}
void enqueueMarkFieldKeptAction(DexEncodedField field, KeepReason reason) {
- assert !restrictToProguardCompatibilityRules || reason.isDueToProguardCompatibility();
assert field.isProgramField(appView);
queue.add(new Action(Action.Kind.MARK_FIELD_KEPT, field, null, reason));
}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepReason.java b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
index eae4a9b..95769e7 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepReason.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
-import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo;
import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo.EdgeKind;
import com.android.tools.r8.experimental.graphinfo.GraphNode;
@@ -12,9 +11,7 @@
import com.android.tools.r8.graph.DexItem;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
-import java.util.Collection;
// TODO(herhut): Canonicalize reason objects.
public abstract class KeepReason {
@@ -27,10 +24,6 @@
return new AnnotatedOn(definition);
}
- static KeepReason dueToProguardCompatibilityKeepRule(ProguardKeepRule rule) {
- return new DueToProguardCompatibilityKeepRule(rule);
- }
-
static KeepReason instantiatedIn(DexEncodedMethod method) {
return new InstatiatedIn(method);
}
@@ -83,10 +76,6 @@
return null;
}
- public ProguardKeepRuleBase getProguardKeepRule() {
- return null;
- }
-
public static KeepReason targetedBySuperFrom(DexEncodedMethod from) {
return new TargetedBySuper(from);
}
@@ -103,55 +92,6 @@
return new OverridesMethod(method);
}
- public Collection<DexReference> getPreconditions() {
- throw new Unreachable();
- }
-
- private static class DueToKeepRule extends KeepReason {
-
- final ProguardKeepRuleBase keepRule;
-
- private DueToKeepRule(ProguardKeepRuleBase keepRule) {
- this.keepRule = keepRule;
- }
-
- @Override
- public EdgeKind edgeKind() {
- return EdgeKind.KeepRule;
- }
-
- @Override
- public boolean isDueToKeepRule() {
- return true;
- }
-
- @Override
- public ProguardKeepRuleBase getProguardKeepRule() {
- return keepRule;
- }
-
- @Override
- public GraphNode getSourceNode(Enqueuer enqueuer) {
- return enqueuer.getKeepRuleGraphNode(null, keepRule);
- }
- }
-
- private static class DueToProguardCompatibilityKeepRule extends DueToKeepRule {
- private DueToProguardCompatibilityKeepRule(ProguardKeepRule keepRule) {
- super(keepRule);
- }
-
- @Override
- public EdgeKind edgeKind() {
- return EdgeKind.CompatibilityRule;
- }
-
- @Override
- public boolean isDueToProguardCompatibility() {
- return true;
- }
- }
-
private abstract static class BasedOnOtherMethod extends KeepReason {
private final DexEncodedMethod method;
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java
index b5305d5..f1e06d1 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java
@@ -140,16 +140,11 @@
stackMapTable = update(stackMapTable, STACK_MAP_TABLE, patterns);
}
- public void ensureValid(
- boolean forceProguardCompatibility, ProguardConfiguration.Builder compatibility) {
+ public void ensureValid(boolean forceProguardCompatibility) {
if (forceProguardCompatibility && innerClasses != enclosingMethod) {
// If only one is true set both to true in Proguard compatibility mode.
enclosingMethod = true;
innerClasses = true;
- compatibility.addKeepAttributePatterns(
- ImmutableList.of(
- ProguardKeepAttributes.INNER_CLASSES,
- ProguardKeepAttributes.ENCLOSING_METHOD));
}
if (innerClasses && !enclosingMethod) {
throw new CompilationError("Attribute InnerClasses requires EnclosingMethod attribute. "
@@ -164,8 +159,6 @@
if (forceProguardCompatibility && localVariableTable && !lineNumberTable) {
// If locals are kept, assume line numbers should be kept too.
lineNumberTable = true;
- compatibility.addKeepAttributePatterns(
- ImmutableList.of(ProguardKeepAttributes.LINE_NUMBER_TABLE));
}
if (localVariableTable && !lineNumberTable) {
throw new CompilationError(
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 d78cb45..9f2a36b 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -197,9 +197,6 @@
public boolean enableNonNullTracking = true;
public boolean enableInlining =
!Version.isDev() || System.getProperty("com.android.tools.r8.disableinlining") == null;
- public boolean enableInliningOfInvokesWithDefinitelyNullReceivers =
- System.getProperty("com.android.tools.r8.disableInliningOfInvokesWithDefinitelyNullReceivers")
- == null;
public boolean enableInliningOfInvokesWithNullableReceivers = true;
public boolean disableInliningOfLibraryMethodOverrides = true;
public boolean enableClassInlining = true;
@@ -616,7 +613,6 @@
// code objects needed for correct desugaring needs to be provided to the consumer.
public DesugarGraphConsumer desugarGraphConsumer = null;
- public Path proguardCompatibilityRulesOutput = null;
public Consumer<List<ProguardConfigurationRule>> syntheticProguardRulesConsumer = null;
public static boolean assertionsEnabled() {
diff --git a/src/main/java/com/android/tools/r8/utils/ListUtils.java b/src/main/java/com/android/tools/r8/utils/ListUtils.java
index a9c3f98..370d5e2 100644
--- a/src/main/java/com/android/tools/r8/utils/ListUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -8,9 +8,19 @@
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
+import java.util.function.Predicate;
public class ListUtils {
+ public static <T> int lastIndexMatching(List<T> list, Predicate<T> tester) {
+ for (int i = list.size() - 1; i >= 0; i--) {
+ if (tester.test(list.get(i))) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
public static <S, T> List<T> map(Collection<S> list, Function<S, T> fn) {
List<T> result = new ArrayList<>(list.size());
for (S element : list) {
diff --git a/src/main/java/com/android/tools/r8/utils/StringUtils.java b/src/main/java/com/android/tools/r8/utils/StringUtils.java
index cd47dc5..44a26be 100644
--- a/src/main/java/com/android/tools/r8/utils/StringUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/StringUtils.java
@@ -165,6 +165,10 @@
return join(LINE_SEPARATOR, lines);
}
+ public static <T> String joinLines(Collection<T> collection) {
+ return join(collection, LINE_SEPARATOR, BraceType.NONE);
+ }
+
public static List<String> splitLines(String content) {
int length = content.length();
List<String> lines = new ArrayList<>();
diff --git a/src/main/java/com/android/tools/r8/utils/Timing.java b/src/main/java/com/android/tools/r8/utils/Timing.java
index df575a6..7d68954 100644
--- a/src/main/java/com/android/tools/r8/utils/Timing.java
+++ b/src/main/java/com/android/tools/r8/utils/Timing.java
@@ -13,13 +13,9 @@
// Finally a report is printed by:
// t.report();
-import java.lang.management.ManagementFactory;
-import java.lang.management.MemoryMXBean;
-import java.lang.management.MemoryPoolMXBean;
-import java.util.ArrayList;
import java.util.LinkedHashMap;
-import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Stack;
public class Timing {
@@ -41,29 +37,45 @@
stack.push(new Node("Recorded timings for " + title));
}
+ private static class MemInfo {
+ final long used;
+
+ MemInfo(long used) {
+ this.used = used;
+ }
+
+ public static MemInfo fromTotalAndFree(long total, long free) {
+ return new MemInfo(total - free);
+ }
+
+ long usedDelta(MemInfo previous) {
+ return used - previous.used;
+ }
+ }
+
class Node {
final String title;
final Map<String, Node> children = new LinkedHashMap<>();
long duration = 0;
long start_time;
- List<String> startMemory;
- List<String> endMemory;
+ Map<String, MemInfo> startMemory;
+ Map<String, MemInfo> endMemory;
Node(String title) {
this.title = title;
- this.start_time = System.nanoTime();
if (trackMemory) {
startMemory = computeMemoryInformation();
}
+ this.start_time = System.nanoTime();
}
void restart() {
assert start_time == -1;
- start_time = System.nanoTime();
if (trackMemory) {
startMemory = computeMemoryInformation();
}
+ start_time = System.nanoTime();
}
void end() {
@@ -71,7 +83,6 @@
start_time = -1;
assert duration() >= 0;
if (trackMemory) {
- System.gc();
endMemory = computeMemoryInformation();
}
}
@@ -82,13 +93,12 @@
@Override
public String toString() {
- return title + ": " + (duration() / 1000000) + "ms.";
+ return title + ": " + prettyTime(duration());
}
public String toString(Node top) {
if (this == top) return toString();
- long percentage = duration() * 100 / top.duration();
- return toString() + " (" + percentage + "%)";
+ return toString() + " (" + prettyPercentage(duration(), top.duration()) + ")";
}
public void report(int depth, Node top) {
@@ -100,44 +110,64 @@
System.out.print("- ");
}
System.out.println(toString(top));
- System.out.println();
if (trackMemory) {
- printMemoryStart(depth);
- System.out.println();
+ printMemory(depth);
}
children.values().forEach(p -> p.report(depth + 1, top));
- if (trackMemory) {
- printMemoryEnd(depth);
- System.out.println();
- }
}
- private void printMemoryStart(int depth) {
- if (startMemory != null) {
- printMemory(depth, title + "(Memory) Start: ", startMemory);
- }
- }
-
- private void printMemoryEnd(int depth) {
- if (endMemory != null) {
- printMemory(depth, title + "(Memory) End: ", endMemory);
- }
- }
-
- private void printMemory(int depth, String header, List<String> strings) {
- for (int i = 0; i <= depth; i++) {
- System.out.print(" ");
- }
- System.out.println(header);
- for (String memoryInfo : strings) {
- for (int i = 0; i <= depth; i++) {
- System.out.print(" ");
+ private void printMemory(int depth) {
+ for (Entry<String, MemInfo> start : startMemory.entrySet()) {
+ if (start.getKey().equals("Memory")) {
+ for (int i = 0; i <= depth; i++) {
+ System.out.print(" ");
+ }
+ MemInfo endValue = endMemory.get(start.getKey());
+ MemInfo startValue = start.getValue();
+ System.out.println(
+ start.getKey()
+ + " start: "
+ + prettySize(startValue.used)
+ + ", end: "
+ + prettySize(endValue.used)
+ + ", delta: "
+ + prettySize(endValue.usedDelta(startValue)));
}
- System.out.println(memoryInfo);
}
}
}
+ private static String prettyPercentage(long part, long total) {
+ return (part * 100 / total) + "%";
+ }
+
+ private static String prettyTime(long value) {
+ return (value / 1000000) + "ms";
+ }
+
+ private static String prettySize(long value) {
+ return prettyNumber(value / 1024) + "k";
+ }
+
+ private static String prettyNumber(long value) {
+ String printed = "" + Math.abs(value);
+ if (printed.length() < 4) {
+ return "" + value;
+ }
+ StringBuilder builder = new StringBuilder();
+ if (value < 0) {
+ builder.append('-');
+ }
+ int prefix = printed.length() % 3;
+ builder.append(printed, 0, prefix);
+ for (int i = prefix; i < printed.length(); i += 3) {
+ if (i > 0) {
+ builder.append('.');
+ }
+ builder.append(printed, i, i + 3);
+ }
+ return builder.toString();
+ }
public void begin(String title) {
Node parent = stack.peek();
@@ -177,22 +207,13 @@
void apply();
}
- private List<String> computeMemoryInformation() {
- List<String> strings = new ArrayList<>();
- strings.add(
- "Free memory: "
- + Runtime.getRuntime().freeMemory()
- + "\tTotal memory: "
- + Runtime.getRuntime().totalMemory()
- + "\tMax memory: "
- + Runtime.getRuntime().maxMemory());
- MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
- strings.add("Heap summary: " + memoryMXBean.getHeapMemoryUsage().toString());
- strings.add("Non-heap summary: " + memoryMXBean.getNonHeapMemoryUsage().toString());
- // Print out the memory information for all managed memory pools.
- for (MemoryPoolMXBean memoryPoolMXBean : ManagementFactory.getMemoryPoolMXBeans()) {
- strings.add(memoryPoolMXBean.getName() + ": " + memoryPoolMXBean.getUsage().toString());
- }
- return strings;
+ private Map<String, MemInfo> computeMemoryInformation() {
+ System.gc();
+ Map<String, MemInfo> info = new LinkedHashMap<>();
+ info.put(
+ "Memory",
+ MemInfo.fromTotalAndFree(
+ Runtime.getRuntime().totalMemory(), Runtime.getRuntime().freeMemory()));
+ return info;
}
}
diff --git a/src/test/java/com/android/tools/r8/L8CommandTest.java b/src/test/java/com/android/tools/r8/L8CommandTest.java
index 525a9ec..97569ab 100644
--- a/src/test/java/com/android/tools/r8/L8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/L8CommandTest.java
@@ -8,9 +8,16 @@
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.dex.Marker;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApiLevel;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
@@ -117,4 +124,49 @@
StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING))
.build());
}
+
+ // TODO(b/139273544): Re-enable shrinking once fixed and re-enable tests using shrinking.
+ @Test
+ @Ignore
+ public void addProguardConfigurationString() throws Throwable {
+ String keepRule = "-keep class java.time.*";
+ List<String> keepRules = new ArrayList<>();
+ keepRules.add(keepRule);
+ L8Command.Builder builder =
+ prepareBuilder(new TestDiagnosticMessagesImpl())
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+ .addDesugaredLibraryConfiguration(
+ StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING))
+ .addProguardConfiguration(keepRules, Origin.unknown());
+ assertTrue(builder.isShrinking());
+ assertNotNull(builder.build().getR8Command());
+ }
+
+ @Test
+ @Ignore
+ public void addProguardConfigurationFile() throws Throwable {
+ String keepRule = "-keep class java.time.*";
+ Path keepRuleFile = temp.newFile("keepRuleFile.txt").toPath();
+ Files.write(keepRuleFile, Collections.singletonList(keepRule), StandardCharsets.UTF_8);
+
+ L8Command.Builder builder1 =
+ prepareBuilder(new TestDiagnosticMessagesImpl())
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+ .addDesugaredLibraryConfiguration(
+ StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING))
+ .addProguardConfigurationFiles(keepRuleFile);
+ assertTrue(builder1.isShrinking());
+ assertNotNull(builder1.build().getR8Command());
+
+ List<Path> keepRuleFiles = new ArrayList<>();
+ keepRuleFiles.add(keepRuleFile);
+ L8Command.Builder builder2 =
+ prepareBuilder(new TestDiagnosticMessagesImpl())
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+ .addDesugaredLibraryConfiguration(
+ StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING))
+ .addProguardConfigurationFiles(keepRuleFiles);
+ assertTrue(builder2.isShrinking());
+ assertNotNull(builder2.build().getR8Command());
+ }
}
diff --git a/src/test/java/com/android/tools/r8/R8CompatTestBuilder.java b/src/test/java/com/android/tools/r8/R8CompatTestBuilder.java
index 43f1ffa..568aa74 100644
--- a/src/test/java/com/android/tools/r8/R8CompatTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8CompatTestBuilder.java
@@ -20,13 +20,6 @@
return new R8CompatTestBuilder(state, builder, backend);
}
- public R8CompatTestBuilder setProguardCompatibilityRulesOutput(
- Path proguardCompatibilityRulesOutput) {
- assert builder.proguardCompatibilityRulesOutput == null;
- builder.proguardCompatibilityRulesOutput = proguardCompatibilityRulesOutput;
- return self();
- }
-
@Override
R8CompatTestBuilder self() {
return this;
diff --git a/src/test/java/com/android/tools/r8/R8TestRunResult.java b/src/test/java/com/android/tools/r8/R8TestRunResult.java
index ecf247c..835c959 100644
--- a/src/test/java/com/android/tools/r8/R8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestRunResult.java
@@ -7,10 +7,14 @@
import static org.junit.Assert.assertNotNull;
import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.retrace.Retrace;
+import com.android.tools.r8.retrace.RetraceCommand;
import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.graphinspector.GraphInspector;
import java.io.IOException;
+import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
@@ -60,4 +64,18 @@
public String proguardMap() {
return proguardMap;
}
+
+ public List<String> retrace() {
+ class Box {
+ List<String> result;
+ }
+ Box box = new Box();
+ Retrace.run(
+ RetraceCommand.builder()
+ .setProguardMapProducer(() -> proguardMap)
+ .setStackTrace(StringUtils.splitLines(getStdErr()))
+ .setRetracedStackTraceConsumer(retraced -> box.result = retraced)
+ .build());
+ return box.result;
+ }
}
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/LintFilesTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/LintFilesTest.java
index 87a6140..146707f 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/LintFilesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/LintFilesTest.java
@@ -21,6 +21,8 @@
import com.android.tools.r8.utils.Reporter;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Comparator;
import java.util.List;
import org.junit.Assume;
import org.junit.Test;
@@ -30,19 +32,49 @@
private void checkFileContent(AndroidApiLevel minApiLevel, Path lintFile) throws Exception {
// Just do some light probing in the generated lint files.
List<String> methods = FileUtils.readAllLines(lintFile);
- assertTrue(methods.contains("java/util/List/spliterator()Ljava/util/Spliterator;"));
- assertTrue(methods.contains("java/util/Optional/empty()Ljava/util/Optional;"));
- assertTrue(methods.contains("java/util/OptionalInt/empty()Ljava/util/OptionalInt;"));
+
+ // All methods supported on Optional*.
+ assertTrue(methods.contains("java/util/Optional"));
+ assertTrue(methods.contains("java/util/OptionalInt"));
+
+ // No parallel* methods pre L, and all stream methods supported from L.
assertEquals(
minApiLevel == AndroidApiLevel.L,
- methods.contains("java/util/Collection/parallelStream()Ljava/util/stream/Stream;"));
+ methods.contains("java/util/Collection#parallelStream()Ljava/util/stream/Stream;"));
assertEquals(
- minApiLevel == AndroidApiLevel.L,
+ minApiLevel == AndroidApiLevel.L, methods.contains("java/util/stream/DoubleStream"));
+ assertFalse(
methods.contains(
- "java/util/stream/DoubleStream/parallel()Ljava/util/stream/DoubleStream;"));
+ "java/util/stream/DoubleStream#parallel()Ljava/util/stream/DoubleStream;"));
+ assertFalse(
+ methods.contains("java/util/stream/DoubleStream#parallel()Ljava/util/stream/BaseStream;"));
assertEquals(
- minApiLevel == AndroidApiLevel.L,
- methods.contains("java/util/stream/IntStream/parallel()Ljava/util/stream/IntStream;"));
+ minApiLevel == AndroidApiLevel.B,
+ methods.contains(
+ "java/util/stream/DoubleStream#allMatch(Ljava/util/function/DoublePredicate;)Z"));
+ assertEquals(minApiLevel == AndroidApiLevel.L, methods.contains("java/util/stream/IntStream"));
+ assertFalse(
+ methods.contains("java/util/stream/IntStream#parallel()Ljava/util/stream/IntStream;"));
+ assertFalse(
+ methods.contains("java/util/stream/IntStream#parallel()Ljava/util/stream/BaseStream;"));
+ assertEquals(
+ minApiLevel == AndroidApiLevel.B,
+ methods.contains(
+ "java/util/stream/IntStream#allMatch(Ljava/util/function/IntPredicate;)Z"));
+
+ // Emulated interface default method.
+ assertTrue(methods.contains("java/util/List#spliterator()Ljava/util/Spliterator;"));
+
+ // Emulated interface static method.
+ assertTrue(methods.contains("java/util/Map$Entry#comparingByValue()Ljava/util/Comparator;"));
+
+ // No no-default method from emulated interface.
+ assertFalse(methods.contains("java/util/List#size()I"));
+
+ // File should be sorted.
+ List<String> sorted = new ArrayList<>(methods);
+ sorted.sort(Comparator.naturalOrder());
+ assertEquals(methods, sorted);
}
@Test
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
index 25988b8..42908d2 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
@@ -554,7 +554,9 @@
@Test
public void testNotNullOfNullGivesBottom() {
- assertEquals(Nullability.bottom(), ReferenceTypeLatticeElement.NULL.asNotNull().nullability());
+ assertEquals(
+ Nullability.bottom(),
+ ReferenceTypeLatticeElement.NULL.asMeetWithNotNull().nullability());
}
@Test
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/KeptClassInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/KeptClassInliningTest.java
new file mode 100644
index 0000000..c8d287b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/KeptClassInliningTest.java
@@ -0,0 +1,73 @@
+// Copyright (c) 2019, 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.ir.optimize.classinliner;
+
+import static com.android.tools.r8.references.Reference.methodFromMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class KeptClassInliningTest extends TestBase {
+
+ public static class KeptClass {
+
+ // Annotate with never-inline to avoid the method inliner from eliminating the call, which in
+ // turn allows removing the instantiation.
+ @NeverInline
+ public void used() {
+ System.out.println("used()");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ new KeptClass().used();
+ }
+ }
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public final TestParameters parameters;
+
+ public KeptClassInliningTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ CodeInspector inspector =
+ testForR8(parameters.getBackend())
+ .enableInliningAnnotations()
+ .addProgramClasses(KeptClass.class, Main.class)
+ .addKeepMainRule(Main.class)
+ .addKeepClassRules(KeptClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("used()")
+ .inspector();
+ assertThat(inspector.clazz(KeptClass.class), isPresent());
+ MethodSubject main =
+ inspector.method(methodFromMethod(Main.class.getMethod("main", String[].class)));
+ // Check the instantiation of the class remains in 'main'.
+ assertTrue(
+ main.streamInstructions()
+ .anyMatch(i -> i.isInvoke() && i.getMethod().name.toString().equals("<init>")));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTest.java
index e3beb92..dcfe291 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTest.java
@@ -9,16 +9,14 @@
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.TestParameters;
import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
import com.android.tools.r8.utils.codeinspector.InvokeInstructionSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
-import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.junit.Test;
@@ -36,17 +34,17 @@
"normalInlinedControl",
"classInlinedControl");
- @Parameterized.Parameters(name = "Backend: {0}, ClassInlining: {1}")
- public static Collection data() {
- return buildParameters(ToolHelper.getBackends(), BooleanUtils.values());
+ @Parameterized.Parameters(name = "{1}")
+ public static List<Object[]> data() {
+ return buildParameters(getTestParameters().withNoneRuntime().build(), Backend.values());
}
private final Backend backend;
- private final boolean classInlining;
+ private final TestParameters parameters;
- public InlineSynchronizedTest(Backend backend, boolean classInlining) {
+ public InlineSynchronizedTest(TestParameters parameters, Backend backend) {
+ this.parameters = parameters;
this.backend = backend;
- this.classInlining = classInlining;
}
@Test
@@ -55,7 +53,6 @@
testForR8(backend)
.addProgramClasses(InlineSynchronizedTestClass.class)
.addKeepMainRule(InlineSynchronizedTestClass.class)
- .addOptionsModification(o -> o.enableClassInlining = classInlining)
.enableInliningAnnotations()
.noMinification()
.compile()
@@ -80,9 +77,9 @@
// Synchronized methods can never be inlined.
assertCount(counts, "normalInlinedSynchronized", 1);
assertCount(counts, "classInlinedSynchronized", 1);
- // Control methods must be inlined, only the normal one or both, depending on classInlining.
+ // Only the never-merge method is inlined, classInlining should run on the kept class.
assertCount(counts, "normalInlinedControl", 0);
- assertCount(counts, "classInlinedControl", classInlining ? 0 : 1);
+ assertCount(counts, "classInlinedControl", 1);
// Double check the total.
int total = 0;
for (int i = 0; i < counts.length; ++i) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlinerShouldNotInlineDefinitelyNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlinerShouldNotInlineDefinitelyNullTest.java
new file mode 100644
index 0000000..654e0f2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlinerShouldNotInlineDefinitelyNullTest.java
@@ -0,0 +1,90 @@
+// Copyright (c) 2019, 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.ir.optimize.inliner;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.StringContains.containsString;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/** This is a small reproduction of b/140851070 */
+@RunWith(Parameterized.class)
+public class InlinerShouldNotInlineDefinitelyNullTest extends TestBase {
+
+ public static String EXPECTED_STACK_TRACE = StringUtils.joinLines(
+ "java.lang.NullPointerException", " at " + Main.class.getTypeName() + ".main(");
+
+ public static class A {
+
+ void foo() {
+ System.out.println("This will never be called");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ A a = null;
+ a.foo(); // <-- will insert empty throwing code because a is definitely null.
+ }
+ }
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InlinerShouldNotInlineDefinitelyNullTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void ensureThrowNullInliningsHaveInlinePositions()
+ throws CompilationFailedException, IOException, ExecutionException {
+ R8TestRunResult result =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Main.class, A.class)
+ .addKeepMainRule(Main.class)
+ .addKeepAllAttributes()
+ .addKeepClassRules(A.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ // Do a manual check that the invoke to foo is replaced by throw.
+ assertTrue(
+ inspector
+ .clazz(Main.class)
+ .uniqueMethodWithName("main")
+ .streamInstructions()
+ .anyMatch(InstructionSubject::isThrow));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailure();
+ String[] split = result.proguardMap().split("\n");
+ assertTrue(Arrays.stream(split).noneMatch(l -> l.contains(A.class.getTypeName() + ".foo")));
+ assertThat(
+ StringUtils.joinLines(result.retrace())
+ .replace("\tat", " at")
+ .replace(": throw with null exception", ""),
+ containsString(EXPECTED_STACK_TRACE));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/nonnull/B141654799.java b/src/test/java/com/android/tools/r8/ir/optimize/nonnull/B141654799.java
new file mode 100644
index 0000000..de6f2e7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/nonnull/B141654799.java
@@ -0,0 +1,71 @@
+// Copyright (c) 2019, 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.ir.optimize.nonnull;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class B141654799 extends TestBase {
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().build();
+ }
+
+ public B141654799(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(B141654799.class)
+ .enableInliningAnnotations()
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getRuntime())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("The end")
+ .inspect(inspector -> {
+ ClassSubject main = inspector.clazz(TestClass.class);
+ assertThat(main, isPresent());
+ MethodSubject mainMethod = main.mainMethod();
+ assertThat(mainMethod, isPresent());
+ assertTrue(mainMethod.streamInstructions().noneMatch(i -> i.isIfEqz() || i.isIfNez()));
+ });
+ }
+
+ static class TestClass {
+ public static void main(String... args) {
+ TestClass x = null;
+ if (System.currentTimeMillis() > 0) {
+ TestClass y = (TestClass) returnsNull();
+ if (y != null) {
+ x = y;
+ }
+ }
+ if (x != null) {
+ System.out.println("Dead code: " + x.toString());
+ }
+ System.out.println("The end");
+ }
+
+ @NeverInline
+ static Object returnsNull() {
+ return null;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderRewritingTest.java b/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderRewritingTest.java
index 91411e5..14af67a 100644
--- a/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderRewritingTest.java
@@ -22,12 +22,12 @@
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ServiceLoader;
import java.util.concurrent.ExecutionException;
import java.util.zip.ZipFile;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -52,6 +52,14 @@
}
}
+ public static class ServiceImpl2 implements Service {
+
+ @Override
+ public void print() {
+ System.out.println("Hello World 2!");
+ }
+ }
+
public static class MainRunner {
public static void main(String[] args) {
@@ -67,6 +75,18 @@
}
}
+ public static class MainWithTryCatchRunner {
+
+ public static void main(String[] args) {
+ try {
+ ServiceLoader.load(Service.class, Service.class.getClassLoader()).iterator().next().print();
+ } catch (Throwable e) {
+ System.out.println(e);
+ throw e;
+ }
+ }
+ }
+
public static class OtherRunner {
public static void main(String[] args) {
@@ -144,13 +164,66 @@
.inspect(
inspector -> {
// Check that we have actually rewritten the calls to ServiceLoader.load.
- ClassSubject clazz = inspector.clazz(MainRunner.class);
- assertTrue(clazz.isPresent());
- MethodSubject main = clazz.uniqueMethodWithName("main");
- assertTrue(main.isPresent());
- assertTrue(
- main.streamInstructions()
- .noneMatch(ServiceLoaderRewritingTest::isServiceLoaderLoad));
+ assertEquals(0, getServiceLoaderLoads(inspector, MainRunner.class));
+ });
+
+ // Check that we have removed the service configuration from META-INF/services.
+ ZipFile zip = new ZipFile(path.toFile());
+ assertNull(zip.getEntry("META-INF/services"));
+ }
+
+ @Test
+ public void testRewritingWithMultiple()
+ throws IOException, CompilationFailedException, ExecutionException {
+ Path path = temp.newFile("out.zip").toPath();
+ testForR8(parameters.getBackend())
+ .addInnerClasses(ServiceLoaderRewritingTest.class)
+ .addKeepMainRule(MainRunner.class)
+ .setMinApi(parameters.getRuntime())
+ .addDataEntryResources(
+ DataEntryResource.fromBytes(
+ StringUtils.lines(ServiceImpl.class.getTypeName(), ServiceImpl2.class.getTypeName())
+ .getBytes(),
+ "META-INF/services/" + Service.class.getTypeName(),
+ Origin.unknown()))
+ .compile()
+ .writeToZip(path)
+ .run(parameters.getRuntime(), MainRunner.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT + StringUtils.lines("Hello World 2!"))
+ .inspect(
+ inspector -> {
+ // Check that we have actually rewritten the calls to ServiceLoader.load.
+ assertEquals(0, getServiceLoaderLoads(inspector, MainRunner.class));
+ });
+
+ // Check that we have removed the service configuration from META-INF/services.
+ ZipFile zip = new ZipFile(path.toFile());
+ assertNull(zip.getEntry("META-INF/services"));
+ }
+
+ @Test
+ @Ignore("b/141290856")
+ public void testRewritingsWithCatchHandlers()
+ throws IOException, CompilationFailedException, ExecutionException {
+ Path path = temp.newFile("out.zip").toPath();
+ testForR8(parameters.getBackend())
+ .addInnerClasses(ServiceLoaderRewritingTest.class)
+ .addKeepMainRule(MainWithTryCatchRunner.class)
+ .setMinApi(parameters.getRuntime())
+ .addDataEntryResources(
+ DataEntryResource.fromBytes(
+ StringUtils.lines(ServiceImpl.class.getTypeName(), ServiceImpl2.class.getTypeName())
+ .getBytes(),
+ "META-INF/services/" + Service.class.getTypeName(),
+ Origin.unknown()))
+ .compile()
+ .writeToZip(path)
+ .run(parameters.getRuntime(), MainRunner.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT + StringUtils.lines("Hello World 2!"))
+ .inspect(
+ inspector -> {
+ // Check that we have actually rewritten the calls to ServiceLoader.load.
+ assertEquals(0, getServiceLoaderLoads(inspector, MainRunner.class));
});
// Check that we have removed the service configuration from META-INF/services.
@@ -178,13 +251,7 @@
.inspector();
// Check that we have not rewritten the calls to ServiceLoader.load.
- ClassSubject clazz = inspector.clazz(OtherRunner.class);
- assertTrue(clazz.isPresent());
- MethodSubject main = clazz.uniqueMethodWithName("main");
- assertTrue(main.isPresent());
- assertEquals(
- 3,
- main.streamInstructions().filter(ServiceLoaderRewritingTest::isServiceLoaderLoad).count());
+ assertEquals(3, getServiceLoaderLoads(inspector, OtherRunner.class));
// Check that we have not removed the service configuration from META-INF/services.
ZipFile zip = new ZipFile(path.toFile());
@@ -216,19 +283,8 @@
.inspector();
// Check that we have not rewritten the calls to ServiceLoader.load.
- ClassSubject clazz = inspector.clazz(EscapingRunner.class);
- assertTrue(clazz.isPresent());
- int allServiceLoaders =
- clazz.allMethods().stream()
- .mapToInt(
- method ->
- (int)
- method
- .streamInstructions()
- .filter(ServiceLoaderRewritingTest::isServiceLoaderLoad)
- .count())
- .sum();
- assertEquals(3, allServiceLoaders);
+ assertEquals(3, getServiceLoaderLoads(inspector, EscapingRunner.class));
+
// Check that we have not removed the service configuration from META-INF/services.
ZipFile zip = new ZipFile(path.toFile());
ClassSubject serviceImpl = inspector.clazz(ServiceImpl.class);
@@ -263,13 +319,7 @@
.inspector();
// Check that we have not rewritten the calls to ServiceLoader.load.
- ClassSubject clazz = inspector.clazz(MainRunner.class);
- assertTrue(clazz.isPresent());
- MethodSubject main = clazz.uniqueMethodWithName("main");
- assertTrue(main.isPresent());
- assertEquals(
- 3,
- main.streamInstructions().filter(ServiceLoaderRewritingTest::isServiceLoaderLoad).count());
+ assertEquals(3, getServiceLoaderLoads(inspector, MainRunner.class));
// Check that we have not removed the service configuration from META-INF/services.
ZipFile zip = new ZipFile(path.toFile());
@@ -278,6 +328,19 @@
assertNotNull(zip.getEntry("META-INF/services/" + service.getFinalName()));
}
+ private static long getServiceLoaderLoads(CodeInspector inspector, Class<?> clazz) {
+ ClassSubject classSubject = inspector.clazz(clazz);
+ assertTrue(classSubject.isPresent());
+ return classSubject.allMethods().stream()
+ .mapToLong(
+ method ->
+ method
+ .streamInstructions()
+ .filter(ServiceLoaderRewritingTest::isServiceLoaderLoad)
+ .count())
+ .sum();
+ }
+
private static boolean isServiceLoaderLoad(InstructionSubject instruction) {
return instruction.isInvokeStatic()
&& instruction.getMethod().qualifiedName().contains("ServiceLoader.load");
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
index fa76296..f28f242 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
@@ -3,13 +3,14 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
+import static org.junit.Assert.assertFalse;
+
import com.android.tools.r8.shaking.TreeShakingTest;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
-import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -43,8 +44,7 @@
}
private static void unusedRemoved(CodeInspector inspector) {
- // TODO(b/80455722): Change to assertFalse when tree-shaking detects this case.
- Assert.assertTrue(
+ assertFalse(
"DerivedUnused should be removed", inspector.clazz("shaking18.DerivedUnused").isPresent());
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
index 6773501..2ac7880 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.shaking.forceproguardcompatibility;
+import static com.android.tools.r8.references.Reference.classFromClass;
+import static com.android.tools.r8.references.Reference.methodFromMethod;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
@@ -15,18 +17,11 @@
import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.invokesuper.Consumer;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.shaking.ProguardClassNameList;
-import com.android.tools.r8.shaking.ProguardConfiguration;
-import com.android.tools.r8.shaking.ProguardConfigurationParser;
-import com.android.tools.r8.shaking.ProguardConfigurationRule;
+import com.android.tools.r8.shaking.CollectingGraphConsumer;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
-import com.android.tools.r8.shaking.ProguardMemberRule;
-import com.android.tools.r8.shaking.ProguardMemberType;
import com.android.tools.r8.shaking.forceproguardcompatibility.defaultmethods.ClassImplementingInterface;
import com.android.tools.r8.shaking.forceproguardcompatibility.defaultmethods.InterfaceWithDefaultMethods;
import com.android.tools.r8.shaking.forceproguardcompatibility.defaultmethods.TestClass;
@@ -34,11 +29,12 @@
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FieldSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.android.tools.r8.utils.graphinspector.GraphInspector;
+import com.android.tools.r8.utils.graphinspector.GraphInspector.QueryNode;
import com.google.common.collect.ImmutableList;
import java.io.File;
import java.nio.file.Path;
@@ -145,31 +141,22 @@
" public void method();",
"}");
builder.addProguardConfiguration(proguardConfig, Origin.unknown());
- Path proguardCompatibilityRules = temp.newFile().toPath();
- builder.setProguardCompatibilityRulesOutput(proguardCompatibilityRules);
builder.setProgramConsumer(emptyConsumer(backend)).addLibraryFiles(runtimeJar(backend));
- CodeInspector inspector = new CodeInspector(ToolHelper.runR8(builder.build()));
- ClassSubject clazz = inspector.clazz(getJavacGeneratedClassName(testClass));
- assertTrue(clazz.isPresent());
- assertEquals(forceProguardCompatibility && hasDefaultConstructor, clazz.init().isPresent());
- // Check the Proguard compatibility rules generated.
- ProguardConfigurationParser parser =
- new ProguardConfigurationParser(new DexItemFactory(), new Reporter());
- parser.parse(proguardCompatibilityRules);
- ProguardConfiguration configuration = parser.getConfigRawForTesting();
- if (forceProguardCompatibility && hasDefaultConstructor) {
- assertEquals(1, configuration.getRules().size());
- ProguardClassNameList classNames = configuration.getRules().get(0).getClassNames();
- assertEquals(1, classNames.size());
- assertEquals(testClass.getCanonicalName(),
- classNames.asSpecificDexTypes().get(0).toSourceString());
- List<ProguardMemberRule> memberRules = configuration.getRules().get(0).getMemberRules();
- assertEquals(1, memberRules.size());
- assertEquals(ProguardMemberType.INIT, memberRules.iterator().next().getRuleType());
- } else {
- assertEquals(0, configuration.getRules().size());
+ CollectingGraphConsumer graphConsumer = new CollectingGraphConsumer(null);
+ builder.setKeptGraphConsumer(graphConsumer);
+
+ GraphInspector inspector =
+ new GraphInspector(graphConsumer, new CodeInspector(ToolHelper.runR8(builder.build())));
+ QueryNode clazzNode = inspector.clazz(classFromClass(testClass)).assertPresent();
+ if (hasDefaultConstructor) {
+ QueryNode initNode = inspector.method(methodFromMethod(testClass.getConstructor()));
+ if (forceProguardCompatibility) {
+ initNode.assertPureCompatKeptBy(clazzNode);
+ } else {
+ initNode.assertAbsent();
+ }
}
if (isRunProguard()) {
@@ -259,8 +246,6 @@
}
List<String> proguardConfig = proguardConfigurationBuilder.build();
builder.addProguardConfiguration(proguardConfig, Origin.unknown());
- Path proguardCompatibilityRules = temp.newFile().toPath();
- builder.setProguardCompatibilityRulesOutput(proguardCompatibilityRules);
if (allowObfuscation) {
builder.setProguardMapOutputPath(temp.newFile().toPath());
}
@@ -319,8 +304,6 @@
}
List<String> proguardConfig = proguardConfigurationBuilder.build();
builder.addProguardConfiguration(proguardConfig, Origin.unknown());
- Path proguardCompatibilityRules = temp.newFile().toPath();
- builder.setProguardCompatibilityRulesOutput(proguardCompatibilityRules);
if (allowObfuscation) {
builder.setProguardMapOutputPath(temp.newFile().toPath());
}
@@ -390,8 +373,6 @@
}
List<String> proguardConfig = proguardConfigurationBuilder.build();
builder.addProguardConfiguration(proguardConfig, Origin.unknown());
- Path proguardCompatibilityRules = temp.newFile().toPath();
- builder.setProguardCompatibilityRulesOutput(proguardCompatibilityRules);
if (allowObfuscation) {
builder.setProguardMapOutputPath(temp.newFile().toPath());
}
@@ -461,7 +442,6 @@
}
keepRules = "-keepattributes " + String.join(",", attributes);
}
- Path proguardCompatibilityRules = temp.newFile().toPath();
CodeInspector inspector;
try {
@@ -477,7 +457,6 @@
keepRules)
.addOptionsModification(options -> options.enableClassInlining = false)
.enableSideEffectAnnotations()
- .setProguardCompatibilityRulesOutput(proguardCompatibilityRules)
.compile()
.run(TestKeepAttributes.class)
.assertSuccessWithOutput(innerClasses || enclosingMethod ? "1" : "0")
@@ -487,20 +466,12 @@
return;
}
- assertThat(inspector.clazz(getJavacGeneratedClassName(TestKeepAttributes.class)), isPresent());
-
- // Check the Proguard compatibility configuration generated.
- ProguardConfigurationParser parser =
- new ProguardConfigurationParser(new DexItemFactory(), new Reporter());
- parser.parse(proguardCompatibilityRules);
- ProguardConfiguration configuration = parser.getConfigRawForTesting();
- assertTrue(configuration.getRules().isEmpty());
- if (innerClasses ^ enclosingMethod) {
- assertTrue(configuration.getKeepAttributes().innerClasses);
- assertTrue(configuration.getKeepAttributes().enclosingMethod);
+ ClassSubject clazz = inspector.clazz(TestKeepAttributes.class);
+ assertThat(clazz, isPresent());
+ if (innerClasses || enclosingMethod) {
+ assertFalse(clazz.getDexClass().getInnerClasses().isEmpty());
} else {
- assertFalse(configuration.getKeepAttributes().innerClasses);
- assertFalse(configuration.getKeepAttributes().enclosingMethod);
+ assertTrue(clazz.getDexClass().getInnerClasses().isEmpty());
}
}
@@ -519,7 +490,8 @@
private void runKeepDefaultMethodsTest(
List<String> additionalKeepRules,
Consumer<CodeInspector> inspection,
- Consumer<ProguardConfiguration> compatInspection) throws Exception {
+ Consumer<GraphInspector> compatInspection)
+ throws Exception {
Class mainClass = TestClass.class;
CompatProguardCommandBuilder builder = new CompatProguardCommandBuilder();
builder.addProgramFiles(ToolHelper.getClassFilesForTestPackage(mainClass.getPackage()));
@@ -532,11 +504,11 @@
Origin.unknown());
builder.addProguardConfiguration(additionalKeepRules, Origin.unknown());
builder.setProgramConsumer(emptyConsumer(backend)).addLibraryFiles(runtimeJar(backend));
+ CollectingGraphConsumer graphConsumer = new CollectingGraphConsumer(null);
+ builder.setKeptGraphConsumer(graphConsumer);
if (backend == Backend.DEX) {
builder.setMinApiLevel(AndroidApiLevel.O.getLevel());
}
- Path proguardCompatibilityRules = temp.newFile().toPath();
- builder.setProguardCompatibilityRulesOutput(proguardCompatibilityRules);
AndroidApp app =
ToolHelper.runR8(
builder.build(),
@@ -547,17 +519,15 @@
// ClassImplementingInterface.
o.enableVerticalClassMerging = false;
});
- inspection.accept(new CodeInspector(app));
- // Check the Proguard compatibility configuration generated.
- ProguardConfigurationParser parser =
- new ProguardConfigurationParser(new DexItemFactory(), new Reporter());
- parser.parse(proguardCompatibilityRules);
- ProguardConfiguration configuration = parser.getConfigRawForTesting();
- compatInspection.accept(configuration);
+
+ CodeInspector inspector = new CodeInspector(app);
+ GraphInspector graphInspector = new GraphInspector(graphConsumer, inspector);
+ inspection.accept(inspector);
+ compatInspection.accept(graphInspector);
}
- private void noCompatibilityRules(ProguardConfiguration configuration) {
- assertEquals(0, configuration.getRules().size());
+ private void noCompatibilityRules(GraphInspector inspector) {
+ inspector.assertNoPureCompatibilityEdges();
}
private void defaultMethodKept(CodeInspector inspector) {
@@ -568,20 +538,8 @@
assertFalse(method.isAbstract());
}
- private void defaultMethodCompatibilityRules(ProguardConfiguration configuration) {
- assertEquals(1, configuration.getRules().size());
- ProguardConfigurationRule rule = configuration.getRules().get(0);
- List<ProguardMemberRule> memberRules = rule.getMemberRules();
- ProguardClassNameList classNames = rule.getClassNames();
- assertEquals(1, classNames.size());
- DexType type = classNames.asSpecificDexTypes().get(0);
- assertEquals(type.toSourceString(), InterfaceWithDefaultMethods.class.getCanonicalName());
- assertEquals(1, memberRules.size());
- ProguardMemberRule memberRule = memberRules.iterator().next();
- assertEquals(ProguardMemberType.METHOD, memberRule.getRuleType());
- assertTrue(memberRule.getName().matches("method"));
- assertTrue(memberRule.getType().matches(configuration.getDexItemFactory().intType));
- assertEquals(0, memberRule.getArguments().size());
+ private void defaultMethodCompatibilityRules(GraphInspector inspector) {
+ // The enqueuer does not add an edge for the referenced => kept edges so we cant check compat.
}
private void defaultMethod2Kept(CodeInspector inspector) {
@@ -593,23 +551,8 @@
assertFalse(method.isAbstract());
}
- private void defaultMethod2CompatibilityRules(ProguardConfiguration configuration) {
- assertEquals(1, configuration.getRules().size());
- ProguardConfigurationRule rule = configuration.getRules().get(0);
- List<ProguardMemberRule> memberRules = rule.getMemberRules();
- ProguardClassNameList classNames = rule.getClassNames();
- assertEquals(1, classNames.size());
- DexType type = classNames.asSpecificDexTypes().get(0);
- assertEquals(type.toSourceString(), InterfaceWithDefaultMethods.class.getCanonicalName());
- assertEquals(1, memberRules.size());
- ProguardMemberRule memberRule = memberRules.iterator().next();
- assertEquals(ProguardMemberType.METHOD, memberRule.getRuleType());
- assertTrue(memberRule.getName().matches("method2"));
- assertTrue(memberRule.getType().matches(configuration.getDexItemFactory().voidType));
- assertEquals(2, memberRule.getArguments().size());
- assertTrue(
- memberRule.getArguments().get(0).matches(configuration.getDexItemFactory().stringType));
- assertTrue(memberRule.getArguments().get(1).matches(configuration.getDexItemFactory().intType));
+ private void defaultMethod2CompatibilityRules(GraphInspector inspector) {
+ // The enqueuer does not add an edge for the referenced => kept edges so we cant check compat.
}
@Test
diff --git a/src/test/java/com/android/tools/r8/utils/ListUtilsTest.java b/src/test/java/com/android/tools/r8/utils/ListUtilsTest.java
new file mode 100644
index 0000000..3a63555
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/ListUtilsTest.java
@@ -0,0 +1,50 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Predicate;
+import org.junit.Test;
+
+public class ListUtilsTest {
+
+ private List<Integer> createInputData(int size) {
+ List<Integer> input = new ArrayList<>(size);
+ for (int i = 0; i < size; i++) {
+ input.add(i);
+ }
+ return input;
+ }
+
+ @Test
+ public void lastIndexOf_outOfRange() {
+ List<Integer> input = createInputData(3);
+ Predicate<Integer> tester = x -> x * x == -1;
+ assertEquals(-1, ListUtils.lastIndexMatching(input, tester));
+ }
+
+ @Test
+ public void lastIndexOf_first() {
+ List<Integer> input = createInputData(3);
+ Predicate<Integer> tester = x -> x * x == 0;
+ assertEquals(0, ListUtils.lastIndexMatching(input, tester));
+ }
+
+ @Test
+ public void lastIndexOf_middle() {
+ List<Integer> input = createInputData(4);
+ Predicate<Integer> tester = x -> x * x == 4;
+ assertEquals(2, ListUtils.lastIndexMatching(input, tester));
+ }
+
+ @Test
+ public void lastIndexOf_last() {
+ List<Integer> input = createInputData(2);
+ Predicate<Integer> tester = x -> x * x == 1;
+ assertEquals(1, ListUtils.lastIndexMatching(input, tester));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
index cbd91f1..59376e6 100644
--- a/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
@@ -54,6 +54,8 @@
new EdgeKindPredicate(EdgeKind.IsLibraryMethod);
public static final EdgeKindPredicate overriding =
new EdgeKindPredicate(EdgeKind.OverridingMethod);
+ public static final EdgeKindPredicate compatibilityRule =
+ new EdgeKindPredicate(EdgeKind.CompatibilityRule);
private final EdgeKind edgeKind;
@@ -141,6 +143,10 @@
public abstract boolean isKeptBy(QueryNode node);
+ public abstract boolean isCompatKeptBy(QueryNode node);
+
+ public abstract boolean isPureCompatKeptBy(QueryNode node);
+
public abstract boolean isKeptByLibraryMethod(QueryNode node);
public abstract boolean isSatisfiedBy(QueryNode... nodes);
@@ -234,6 +240,35 @@
return this;
}
+ public QueryNode assertCompatKeptBy(QueryNode node) {
+ assertTrue(
+ "Invalid call to assertCompatKeptBy with: " + node.getNodeDescription(),
+ node.isPresent());
+ assertTrue(
+ errorMessage("compat kept by " + node.getNodeDescription(), "was not kept by it"),
+ isCompatKeptBy(node));
+ return this;
+ }
+
+ public QueryNode assertNotCompatKeptBy(QueryNode node) {
+ assertTrue(
+ "Invalid call to assertNotKeptBy with: " + node.getNodeDescription(), node.isPresent());
+ assertFalse(
+ errorMessage("not kept by " + node.getNodeDescription(), "was kept by it"),
+ isCompatKeptBy(node));
+ return this;
+ }
+
+ public QueryNode assertPureCompatKeptBy(QueryNode node) {
+ assertTrue(
+ "Invalid call to assertPureCompatKeptBy with: " + node.getNodeDescription(),
+ node.isPresent());
+ assertTrue(
+ errorMessage("compat kept by " + node.getNodeDescription(), "was not kept by it"),
+ isPureCompatKeptBy(node));
+ return this;
+ }
+
public QueryNode assertSatisfiedBy(QueryNode... nodes) {
if (isSatisfiedBy(nodes)) {
return this;
@@ -328,6 +363,18 @@
}
@Override
+ public boolean isCompatKeptBy(QueryNode node) {
+ fail("Invalid call to isCompatKeptBy on " + getNodeDescription());
+ throw new Unreachable();
+ }
+
+ @Override
+ public boolean isPureCompatKeptBy(QueryNode node) {
+ fail("Invalid call to isPureCompatKeptBy on " + getNodeDescription());
+ throw new Unreachable();
+ }
+
+ @Override
public boolean isKeptByLibraryMethod(QueryNode node) {
fail("Invalid call to isKeptByLibrary on " + getNodeDescription());
throw new Unreachable();
@@ -441,6 +488,28 @@
}
@Override
+ public boolean isCompatKeptBy(QueryNode node) {
+ if (!(node instanceof QueryNodeImpl)) {
+ return false;
+ }
+ QueryNodeImpl impl = (QueryNodeImpl) node;
+ return filterSources(
+ (source, infos) ->
+ impl.graphNode == source && EdgeKindPredicate.compatibilityRule.test(infos))
+ .findFirst()
+ .isPresent();
+ }
+
+ @Override
+ public boolean isPureCompatKeptBy(QueryNode node) {
+ if (!isCompatKeptBy(node)) {
+ return false;
+ }
+ QueryNodeImpl impl = (QueryNodeImpl) node;
+ return filterSources((source, infos) -> impl.graphNode != source).count() == 0;
+ }
+
+ @Override
public boolean isKeptByLibraryMethod(QueryNode node) {
assert graphNode instanceof MethodGraphNode;
if (!(node instanceof QueryNodeImpl)) {
@@ -664,4 +733,25 @@
private QueryNode getQueryNode(GraphNode node, String absentString) {
return node == null ? new AbsentQueryNode(absentString) : new QueryNodeImpl(this, node);
}
+
+ private boolean isPureCompatTarget(GraphNode target) {
+ Map<GraphNode, Set<GraphEdgeInfo>> sources = consumer.getSourcesTargeting(target);
+ if (sources == null || sources.isEmpty()) {
+ return false;
+ }
+ for (Entry<GraphNode, Set<GraphEdgeInfo>> edge : sources.entrySet()) {
+ for (GraphEdgeInfo edgeInfo : edge.getValue()) {
+ if (edgeInfo.edgeKind() != EdgeKind.CompatibilityRule) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ public void assertNoPureCompatibilityEdges() {
+ for (GraphNode target : consumer.getTargets()) {
+ assertFalse(isPureCompatTarget(target));
+ }
+ }
}